30 KiB
Executable File
PPI ID Resolution Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Resolve PPI identifiers through job-local biological alias evidence and STRING's official mapping API without rewriting canonical family-member IDs.
Architecture: A focused gfa_core.id_resolution module parses species-scoped local alias evidence only. skills/ppi/run.py retains explicit canonical-to-reference relationship records, owns STRING-specific probing, and deduplicates only final network queries. apps/web/pipeline_backend.py resolves the exact scoped Ortholog relationship and job-local ID map for main and Reset execution, then synchronizes equivalent scientific metrics into all manifests. Existing job files remain the source of truth, and every canonical relationship is written to the PPI resolution TSV.
Tech Stack: Python 3.11, dataclasses/csv/re from the standard library, requests, pandas, pytest, existing bbav5 manifests/contracts, STRING v12 official API.
Global Constraints
- Preserve
final_membersand effective-member IDs as canonical identities. - Treat cross-species Ortholog links as relationships, never aliases.
- Scope all alias evidence by species/taxon and job; never merge across species implicitly.
- Reuse existing
pep_cds_idmap.tsv; do not introduce a database service in this phase. - Never silently choose one of multiple distinct STRING entities.
- Allow partial mapping with explicit metrics; block when zero IDs resolve.
- Preserve one resolution row per canonical-to-reference relationship; never deduplicate provenance rows.
- Treat HTTP/timeout/JSON failures separately from a valid empty network.
- A valid zero-edge network is
status=successwithnetwork_status=empty_network. - Emit a header-only
protein1/protein2/scorenetwork TSV for a valid empty network. - Persist structured manifests for success and failure; a failure must not disappear because the CLI exits nonzero.
- Validate with the full real
job_1783583324146_c5cb74data, not a subset. - The current workspace is not a functional Git repository; replace commit checkpoints with recorded file/test checkpoints.
Phase 0: Documentation and allowed APIs
Local references:
项目记录/2026-07-10_PPI_KaKs状态机与通用IDResolver分期讨论记录.mdgfa_core/schemas/stages/ppi_contract_v1.mdskills/ppi/run.py:43-79,168-270,346-522apps/web/pipeline_backend.py:1542-1600,8607-9080gfa_core/ppi_string_policy.py:140-151,179-220tests/unit/test_ppi_string_policy.pytests/unit/test_ppi_frontend_display_contract.py
Allowed external APIs:
GET/POST https://string-db.org/api/{format}/get_string_idsGET/POST https://string-db.org/api/{format}/networkGET https://string-db.org/api/{format}/version
Required parameters: identifiers, species; PPI network additionally uses required_score, fixed network_type=functional, and explicit add_nodes=0. Mapping requests use echo_query=1, but attribution is keyed by queryIndex, not response order. get_string_ids returns one best match per submitted candidate and does not support limit.
Operational rules: POST identifier lists, include caller_identity=PlantAI, prefer documented string_stable_address while tolerating observed stable_address, use one stable version API for mapping/network, and enforce at least one second between STRING requests through an injected clock/sleeper and a concurrency-safe request gate. Treat mapping-endpoint all-unresolved 404 and observed 200 [] as zero resolution; other transport/HTTP/JSON failures remain external API failures.
Anti-pattern guards:
- Do not scrape the STRING CGI webpage for mapping.
- Do not send an invented
limitparameter toget_string_ids. - Do not infer STRING success from a generated web URL.
- Do not use
interaction_partnersto make an empty family-internal network non-empty. - Do not convert an API exception to
[]. - Do not call NCBI/UniProt when the job-local NCBI GeneID is already present.
- Do not treat an optional latest-version STRING web/image response as scientific mapping/network evidence.
Task 1: Reusable local alias evidence parser
Files:
- Create:
gfa_core/id_resolution.py - Create:
tests/unit/test_id_resolution.py
Interfaces:
-
Produces
IdAliasCandidate(value: str, namespace: str, method: str, evidence_source: str). -
Produces
ReferenceAliasIndex.from_tsv(path: Path, species: str) -> ReferenceAliasIndex. -
Produces
ReferenceAliasIndex.candidates(raw_id: str) -> list[IdAliasCandidate]. -
Consumes existing columns
protein_id,pep_id,cds_id,gene_id,cds_header; optionally consumes newncbi_gene_id. -
Exact versioned accessions are primary. A versionless lookup is valid only when every matching version converges on the same alias entity; otherwise it is ambiguous.
-
Any non-empty row whose
species_codediffers from the declared species invalidates the file rather than being silently ignored. -
Step 1: Write failing parser tests
def test_reference_alias_index_extracts_numeric_geneid_from_legacy_header(tmp_path):
path = tmp_path / "soe.pep_cds_idmap.tsv"
path.write_text(
"species_code\tprotein_id\tpep_id\tcds_id\tgene_id\tcds_header\n"
"soe\tXP_021867507.1\tXP_021867507.1\tXP_021867507.1\tLOC110806180\t"
"XP_021867507.1 [gene=LOC110806180] [db_xref=GeneID:110806180]\n",
encoding="utf-8",
)
index = ReferenceAliasIndex.from_tsv(path, species="soe")
assert [x.value for x in index.candidates("XP_021867507")] == [
"XP_021867507.1", "110806180", "LOC110806180"
]
def test_reference_alias_index_rejects_species_mismatch(tmp_path):
path = write_alias_map(tmp_path, species="ath")
with pytest.raises(IdResolutionError, match="species"):
ReferenceAliasIndex.from_tsv(path, species="soe")
def test_versionless_collision_with_conflicting_geneids_is_ambiguous(tmp_path): ...
def test_mixed_species_rows_in_one_map_are_rejected(tmp_path): ...
def test_duplicate_accession_with_same_geneid_is_deduplicated(tmp_path): ...
- Step 2: Run tests and confirm failure
Run: pytest tests/unit/test_id_resolution.py -q
Expected: collection/import failure because gfa_core.id_resolution does not exist.
- Step 3: Implement the minimum parser
Implement immutable candidates, exact and guarded versionless protein lookup, GeneID:(\d+) extraction, deterministic namespace priority, deduplication, ambiguity reporting, and whole-file species validation. Do not perform online queries or infer aliases from substring similarity.
- Step 4: Run parser tests
Run: pytest tests/unit/test_id_resolution.py -q
Expected: all tests pass.
- Step 5: Record checkpoint
Record created/changed files and passing command in PLAN-REVIEW-LOG.md; Git commit is unavailable in this workspace.
Task 2: Reference-bundle ID evidence schema
Files:
- Modify:
apps/web/pipeline_backend.py:1490-1600 - Modify:
tests/unit/test_pipeline_backend_execution_env.py
Interfaces:
-
Extends
_fasta_header_index()records withncbi_gene_idparsed from[db_xref=GeneID:<digits>]. -
Extends
_build_reference_bundle_id_map()TSV withncbi_gene_idwhile retaininggene_id=LOC...for backward compatibility. -
Step 1: Write a failing ID-map schema test
def test_reference_bundle_id_map_preserves_loc_and_numeric_geneid(tmp_path):
result = pipeline_backend._build_reference_bundle_id_map(
species_code="soe",
pep_path=write_pep(tmp_path, "XP_021867507.1"),
cds_path=write_cds(tmp_path, "XP_021867507.1", "LOC110806180", "110806180"),
output_path=tmp_path / "soe.pep_cds_idmap.tsv",
)
row = next(csv.DictReader(open(result["id_map_path"]), delimiter="\t"))
assert row["gene_id"] == "LOC110806180"
assert row["ncbi_gene_id"] == "110806180"
- Step 2: Verify the new test fails
Run: pytest tests/unit/test_pipeline_backend_execution_env.py -k reference_bundle_id_map -q
Expected: failure because ncbi_gene_id is absent.
- Step 3: Add the schema field without changing existing columns
Parse numeric GeneID separately; do not replace LOC symbols. Add ncbi_gene_id to the manifest id_validation summary only if a count is useful and covered by the test.
- Step 4: Run focused and existing reference-bundle tests
Run: pytest tests/unit/test_pipeline_backend_execution_env.py -k 'reference_bundle or ppi' -q
Expected: all selected tests pass.
Task 3: Typed STRING API and deterministic PPI ID resolution
Files:
- Create:
gfa_core/string_rate_gate.py - Modify:
skills/ppi/run.py:43-445 - Modify:
apps/web/pipeline_backend.py:3202-3263to reuse the same gate for optional STRING image attempts - Create:
tests/unit/test_ppi_id_resolution.py
Interfaces:
-
Adds
StringApiError. -
Adds
StringAPI.get_version() -> dictand accepts bothstable_addressandstring_stable_address. -
Adds
StringAPI.get_string_ids(identifiers: list[str]) -> list[dict]using POST,echo_query=1, andqueryIndexattribution. -
Changes
StringAPI.get_network(...) -> list[dict]to raiseStringApiErroron transport/HTTP/JSON failure while preserving[]as a valid response. -
Adds
PpiQueryOrigin(canonical_target_id, ortholog_reference_id, raw_query_id, relationship_source); parsing Ortholog input returns a list of origins rather than a lossy dictionary. -
Adds
PpiSkill.resolve_query_ids(origins: list[PpiQueryOrigin], species: str, id_map_file: Path | None) -> StringResolutionResult. -
StringResolutionResultexposes selected STRING IDs, per-input rows, and metrics. -
Resolution TSV cardinality is one summary row per canonical-to-reference relationship. Candidate probes may be deduplicated internally, but output rows retain every origin. Sources with multiple distinct references are marked
ambiguous_orthologand excluded unless they converge to the same STRING entity. -
Step 1: Write failing tests with a fake STRING client
Cover these exact cases:
def test_raw_xp_failure_falls_back_to_ncbi_geneid_and_selects_string_id(...): ...
def test_raw_and_geneid_mapping_to_same_string_id_is_not_ambiguous(...): ...
def test_one_input_mapping_to_two_distinct_string_ids_is_ambiguous(...): ...
def test_partial_resolution_continues_with_explicit_unresolved_rows(...): ...
def test_zero_resolved_ids_returns_id_resolution_failure(...): ...
def test_network_empty_list_is_success_empty_network(...): ...
def test_network_http_error_is_external_api_failure(...): ...
def test_get_string_ids_uses_query_index_not_response_position(...): ...
def test_string_version_accepts_actual_and_documented_stable_address_fields(...): ...
def test_invalid_network_parameters_fail_before_http(...): ...
def test_24_origins_collapse_to_12_queries_without_losing_provenance(...): ...
def test_many_canonical_targets_sharing_one_reference_keep_separate_rows(...): ...
def test_one_canonical_source_with_conflicting_references_is_excluded(...): ...
def test_mapping_404_and_200_empty_both_mean_zero_resolution(...): ...
def test_duplicate_out_of_range_and_wrong_taxon_query_index_are_rejected(...): ...
def test_request_throttle_uses_injected_clock_and_sleeper(...): ...
def test_two_reference_queries_converging_on_one_string_id_are_both_resolved(...): ...
def test_interprocess_rate_gate_serializes_concurrent_clients(tmp_path): ...
def test_interprocess_gate_serializes_scientific_and_image_requests(tmp_path): ...
The fake client must reproduce the real soe evidence: raw XP candidates resolve 5/12, GeneID candidates resolve 12/12, and the network response is [].
- Step 2: Run tests and confirm failure
Run: pytest tests/unit/test_ppi_id_resolution.py -q
Expected: failures for missing API/resolution interfaces.
- Step 3: Implement typed API calls
Use the existing requests dependency, a 30-second timeout, POST for identifier lists, caller_identity=PlantAI, stable-version URL discovery, one-second request throttling, client-side parameter validation, and explicit JSON shape validation. Make single-client pacing testable through an injected clock/sleeper. Implement the gate once in gfa_core.string_rate_gate and apply it to every active PPI-path STRING HTTP request: stable version, mapping, network, and each optional image/SVG fallback attempt in main and Reset. The gate uses a host-shared fcntl.flock lock plus atomic wall-clock timestamp file (BBAV5_STRING_RATE_GATE, default under /tmp); keep the production interval at one second. Use a configurable short interval and real multiprocessing in the concurrency test. Align active PPI callers on caller_identity=PlantAI. Classify mapping-endpoint all-unresolved 404 and observed 200 [] as zero resolution; do not convert other failures to an empty mapping or network. The legacy standalone apps/cli/string_api.py is not in the active PPI path and is out of scope.
- Step 4: Implement deterministic candidate probing
Probe unique raw/normalized/local-alias candidates in one batch; retain queryItem/queryIndex/stringId/preferredName/ncbiTaxonId. Treat omitted valid indices as unresolved. Reject duplicate, negative/out-of-range, malformed, or wrong-taxon result rows. Select a result only when all successful local candidates for one biological query converge on one STRING ID; mark candidates resolving to distinct STRING IDs as conflicting. Do not assume the API exposes all biological alternatives.
- Step 5: Preserve canonical-to-reference provenance
When an Ortholog mapping exists, retain each canonical target ID and the exact versioned mapped reference protein ID before any deduplication. Build the resolution TSV from these origins, then deduplicate selected STRING IDs for the network call. Direct-target PPI creates one origin per canonical target ID with an empty Ortholog field. Add explicit assertions for the real 24-origin/12-query cardinality.
- Step 6: Run focused tests
Run: pytest tests/unit/test_ppi_id_resolution.py tests/unit/test_ppi_string_policy.py -q
Expected: all pass; no network access occurs in unit tests.
Task 4: Backend discovery, scoped Reset identity, and CLI wiring
Files:
- Modify:
skills/ppi/run.py:525-559 - Modify:
apps/web/pipeline_backend.py:3432-3650,8607-8902 - Modify:
apps/web/vue3/main.py:5067-5074,11380-11440,11733-11789 - Modify:
apps/web/vue3/index.htmlat the PPI Reset request construction - Modify:
tests/unit/test_pipeline_backend_execution_env.py - Create or modify:
tests/unit/test_ppi_reset_api_contract.py
Interfaces:
-
Adds CLI option
--id-map <path>. -
Adds backend helper
resolve_ppi_id_map(in_dir: Path, species: str) -> Path | None. -
Passes
input/<job>/<species>/<species>.pep_cds_idmap.tsvonly when its species matches the selected STRING/reference species. -
Main PPI records the canonical input path/hash, exact Ortholog path/hash, and ID-map path/hash used by the scoped manifest.
-
Reset request requires
scopeId(or exact source-manifest identity) andinputNamespace=canonical_target. It selects the exact scoped successful PPI manifest; if scope is omitted and multiple candidates exist, return a conflict instead of preferring the generic manifest. -
Projected Reset loads the canonical input, Ortholog relationship, and species-matched ID map recorded by that manifest; verifies hashes/species/job containment; copies all three into the isolated Reset directory; verifies the snapshot hashes; and executes only against those snapshots with both
--mappingand--id-map. -
Direct-target Reset has no Ortholog input: it snapshots the recorded canonical member file, requires submitted IDs to belong to that recorded member set, and uses the species-matched ID map only when one was recorded. It never applies the projected-path “must be an Ortholog source” rule.
-
sourceGeneIdFileis removed from authoritative client control. During compatibility transition it is accepted only when its normalized path exactly equals the job-contained path resolved from the selected manifest. -
Projected-Reset added IDs must be target canonical IDs present as sources in the exact Ortholog relationship. Direct-target Reset accepts only IDs present in the recorded canonical snapshot. Reference IDs, STRING IDs, mixed namespaces, and out-of-policy target IDs are rejected with structured diagnostics.
-
Step 1: Write failing backend wiring tests
Assert that soe scope selects soe/soe.pep_cds_idmap.tsv, ath scope does not reuse soe evidence, and direct-target PPI still runs without an ID map. For Ft→soe Reset, assert exact scoped-manifest selection, job containment, canonical-target namespace validation, canonical/Ortholog/ID-map hash validation, evidence snapshots, and command inclusion of snapshot --mapping and --id-map paths. Add rejection tests for wrong scope, stale hash, cross-job path, cross-species map, reference/STRING IDs, and an added Ft ID absent from the mapping. Add a mutation-between-validation-and-launch test proving the executed snapshot is unchanged. Add ath/osa direct-target Reset tests proving submitted IDs are validated against the canonical snapshot without requiring Ortholog.
- Step 2: Verify failure
Run: pytest tests/unit/test_pipeline_backend_execution_env.py tests/unit/test_ppi_reset_api_contract.py -k 'ppi_id_map or ppi_reset' -q
- Step 3: Implement discovery and command wiring
Add --id-map only when the verified file exists. Keep --mapping for Ortholog relationships and do not merge these parameters. Preserve the versioned Ortholog target accession for evidence; use a versionless form only as an additional lookup alias. Resolve Reset inputs exclusively from the exact selected scoped manifest, snapshot verified evidence before execution, and fail closed when recorded or snapshot hashes do not match, without attempting the out-of-scope Ortholog artifact redesign.
- Step 4: Verify focused backend tests
Run: pytest tests/unit/test_pipeline_backend_execution_env.py tests/unit/test_ppi_reset_api_contract.py -k 'ppi or reference_bundle' -q
Expected: all selected tests pass.
Task 5: Evidence, manifest, contract, diagnosis, and frontend labels
Files:
- Modify:
skills/ppi/run.py:447-522,565-568 - Modify:
skills/ppi/SKILL.md - Modify:
skills/ppi/config.yaml - Modify:
gfa_core/ppi_string_policy.py:179-194 - Modify:
apps/web/pipeline_backend.py:8878-9066 - Modify:
gfa_core/schemas/stages/ppi_contract_v1.md - Modify:
gfa_core/mas/agents/diagnosis.pyonly at the existing PPI issue-code mapping - Modify:
gfa_core/mas/tools/stage_status.pyonly if required to accept a schema-valid header-only empty-network artifact - Modify:
apps/web/vue3/index.htmlat PPI result metrics/summary rendering - Modify:
tests/unit/test_ppi_frontend_display_contract.py - Modify:
tests/unit/test_ppi_string_policy.py - Modify or create: PPI stage-status/manifest contract tests
Interfaces:
-
Resolution TSV columns:
canonical_target_id,ortholog_reference_id,raw_query_id,candidate_gene_id,selected_query_id,string_id,string_preferred_name,mapping_status,method,evidence_source,species,taxon_id,reason. -
Resolution TSV has one row per canonical-to-reference relationship. For the real job it has 24 data rows even though only 12 unique STRING queries are submitted.
-
metrics.id_resolutionexact fields:input_member_count: distinct canonical target members presented to PPI (real job: 24).relationship_count: retained canonical-to-reference relationship rows (real job: 24).unique_reference_query_count: distinct versioned reference/raw queries before STRING probing (real job: 12).raw_resolved_query_count: unique reference queries resolved directly by raw/normalized identifier (real job: 5).geneid_fallback_resolved_query_count: additional unique queries resolved only through NCBI GeneID (real job: 7).resolved_reference_query_count: unique reference/raw queries that resolve unambiguously, before STRING-ID deduplication (real job: 12).selected_string_query_count: distinct unambiguous STRING IDs submitted to the network endpoint (real job: 12).resolved_member_count,unresolved_member_count,ambiguous_member_count: canonical-member denominators after propagating query results back through relationship rows.member_mapping_rate = resolved_member_count / input_member_countandquery_mapping_rate = resolved_reference_query_count / unique_reference_query_count.probe_status=completewhen every reference query resolves unambiguously,partialwhen at least one but not all resolve, andfailedwhen none resolve.selected_string_query_countmay be smaller thanresolved_reference_query_countwhen multiple reference queries converge on one STRING entity.
-
metrics.networkexact fields:submitted_string_query_count,nodes_in_returned_edges,edge_count, score summaries, andnetwork_status=network_detected|empty_network. -
parameters.id_resolutionexact fields:string_species,string_taxon_id,string_database_version,stable_api_base,canonical_input.path/sha256,ortholog_mapping.path/sha256(empty for direct PPI), andid_map.path/sha256(empty when unavailable). -
Main, scoped, and Reset manifests expose the same
metrics.id_resolution,metrics.network, andparameters.id_resolutionstructures. Reset adds source manifest/scope/reset provenance without renaming the scientific fields. -
Step 1: Write failing contract/report tests
Assert the exact TSV headers, row cardinality, nested manifest locations, and denominators, including two reference queries converging to one STRING ID (resolved_reference_query_count=2, selected_string_query_count=1, query_mapping_rate=1). Assert frontend renders the real-job values 24, 12, 0, 0 from input_member_count, resolved_reference_query_count, nodes_in_returned_edges, and edge_count under labels “输入成员”, “STRING已解析 reference 查询”, “参与互作节点”, and “互作边”. Do not satisfy the test with label-string presence alone, and do not present nodes_in_returned_edges/submitted_string_query_count as an ID mapping rate.
- Step 2: Verify failure
Run: pytest tests/unit/test_ppi_string_policy.py tests/unit/test_ppi_frontend_display_contract.py -q
- Step 3: Update report and manifest
Record the exact nested structures above. Write network output through an explicit schema so zero edges still produce protein1\tprotein2\tscore\n. A zero-edge network keeps stage status=success and metrics.network.network_status=empty_network.
- Step 4: Synchronize final/scoped manifests
Ensure sync_ppi_manifest_final_artifacts() and annotate_stage_manifest_with_scope() retain the new metrics/artifact instead of overwriting them. Make run_ppi_reset_analysis() merge the skill manifest's scientific metrics and ID-resolution parameters into the Reset manifest instead of recording the skill manifest only as an artifact.
- Step 5: Add structured diagnosis
Replace/retire the terminal threshold semantics in evaluate_string_mapping_quality() with complete, nonterminal partial, and terminal failed classification. Use distinct issue codes: ppi_string_id_resolution_failed, ppi_string_id_resolution_partial, and ppi_string_api_failed. Persist partial as a warning issue on a successful manifest; do not label empty_network as an error.
- Step 6: Update contract and frontend labels
Document exact fields and display four distinct counts. Keep raw scientific results downloadable. Update skills/ppi/SKILL.md and skills/ppi/config.yaml so project metadata describes mapping, id_map, actual skill-local output names, relationship provenance, partial success, failure manifests, and empty-network semantics. For this PPI-first phase, lock execution and manifest network_type to functional; remove the misleading claim that users can select physical until the request/CLI/frontend contract is deliberately added in a later change.
- Step 7: Define CLI/backend terminal-state matrix
| Case | CLI exit | Manifest status | Issue/network state |
|---|---|---|---|
| complete mapping, edges present | 0 | success | network_detected |
| complete/partial mapping, zero edges | 0 | success | optional partial warning + empty_network |
| zero unambiguous STRING IDs | 2 | failed | ppi_string_id_resolution_failed |
| STRING transport/HTTP/JSON/protocol failure | 3 | failed | ppi_string_api_failed, retryable |
| invalid input/species/id-map/scope | 4 | failed | specific validation issue |
The skill writes its manifest before exiting. Tests invoke CLI terminal cases and assert exits 2/3/4 occur only after the matching failed manifest exists. Main, scoped, and Reset backend tests assert failed manifests are copied, scope-annotated, and exposed by stage status/diagnosis without fabricating an empty network.
- Step 8: Run all PPI unit/contract tests
Run: pytest tests/unit/test_ppi_string_policy.py tests/unit/test_ppi_id_resolution.py tests/unit/test_ppi_frontend_display_contract.py -q
Expected: all pass.
Task 6: Full real-data/API verification
Files/artifacts checked:
-
input/job_1783583324146_c5cb74/soe/soe.pep_cds_idmap.tsv -
workspace/job_1783583324146_c5cb74/final/Ft.SPL.orthologs.txt -
workspace/job_1783583324146_c5cb74/final/Ft.SPL.ppi_id_resolution.tsv -
workspace/job_1783583324146_c5cb74/final/Ft.SPL.ppi.manifest.json -
scoped PPI manifest for
SPL.target-Ft.ref-soe.target_plus_reference -
isolated scoped Reset directory and
ppi_resetmanifest for the same scope -
Step 1: Static verification
Run:
python -m py_compile gfa_core/id_resolution.py gfa_core/string_rate_gate.py skills/ppi/run.py apps/web/pipeline_backend.py apps/web/vue3/main.py
node -e "const fs=require('fs');const s=fs.readFileSync('apps/web/vue3/index.html','utf8');new Function(s.match(/<script[^>]*>([\s\S]*?)<\/script>/)[1]);console.log('parsed scripts 1')"
Expected: Python compilation succeeds and Node prints parsed scripts 1 (adjust extraction only to the repository's established multi-script parser if required).
- Step 2: Run focused regression suite
Run:
pytest tests/unit/test_id_resolution.py \
tests/unit/test_ppi_id_resolution.py \
tests/unit/test_ppi_string_policy.py \
tests/unit/test_ppi_frontend_display_contract.py \
tests/unit/test_pipeline_backend_execution_env.py \
tests/unit/test_ppi_reset_api_contract.py -q
Expected: all tests pass.
The suite must include explicit CLI/backend/status cases for exits 2/3/4, failed-manifest persistence, scope annotation, Reset failure propagation, two-reference-to-one-STRING metric semantics, direct-target Reset, mutation-safe Reset snapshots, and concurrent-process STRING pacing.
- Step 3: Restart required services
Restart 9000 FastAPI and the 9000 PPI Celery worker using the repository's existing scripts; verify /api/celery/overview reports the expected queue online. Do not start duplicate workers.
- Step 4: Execute full real PPI
Submit PPI through /api/execute for job_1783583324146_c5cb74, Ft.SPL, scope SPL.target-Ft.ref-soe.target_plus_reference. Poll to terminal status.
Expected evidence:
-
actual command contains
--species soe,--mapping ...Ft.SPL.orthologs.txt, and--id-map ...soe.pep_cds_idmap.tsv; -
24 canonical-member provenance rows and 12 unique reference queries;
-
5 raw-resolved queries plus 7 GeneID-fallback queries, yielding 12 unique STRING IDs;
-
24 resolved canonical members, 0 unresolved, and 0 ambiguous;
-
official network response may remain 0 edges;
-
stage is success and
metrics.network.network_status=empty_networkwhen edges are zero. -
Step 5: Execute scoped Ft→soe Reset
Call /api/ppi/reset/input with the exact PPI scope_id, submit a canonical-target subset through /api/ppi/reset/submit, and poll to completion. Verify the selected source manifest is the exact scoped manifest, canonical/Ortholog/ID-map hashes match, snapshots exist under the Reset directory, and the command contains snapshot --mapping and --id-map paths. Verify a deliberately supplied reference/STRING ID and cross-job sourceGeneIdFile are rejected. Do not overwrite the original PPI outputs.
- Step 6: Inspect artifacts and APIs
Check TSV rows, header-only network schema, manifest metrics/hashes/version, scoped manifest identity, logs, /api/stage-status, /api/ppi/reset/input, /api/ppi/reset/submit, and frontend-visible field-to-label values. Confirm no At identifiers or taxon 3702 appear in the soe run. Compare main, scoped, and Reset manifests and require identical scientific metric/parameter locations. Record optional web/image endpoint version separately if it is not pinned to the scientific stable API.
- Step 7: Record final verification
Append exact commands/results and any unvalidated paths to the project discussion record. State whether full data was used and whether service/Celery restart was required.
Final anti-pattern scan
- Search touched files for silent
athdefaults in the PPI path. - Search for
except ...: return []in STRING API calls. - Confirm no code writes external IDs back into
final_members. - Confirm Ortholog mapping and alias mapping remain distinct inputs.
- Confirm no new dependency or global cross-job database was introduced.
- Confirm generic-manifest-first Reset selection and arbitrary client path authority were removed or fail closed.
- Confirm failure manifests survive nonzero CLI exits and partial warnings do not become terminal failures.