176 lines
5.8 KiB
Python
176 lines
5.8 KiB
Python
from __future__ import annotations
|
|||
|
|
|
||
|
|
import json
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from openpyxl import Workbook
|
||
|
|
|
||
|
|
from apps.web import pipeline_backend as backend
|
||
|
|
|
||
|
|
|
||
|
|
def _job(tmp_path: Path, *, code: str = "", name: str = "", taxon_id: str = "") -> backend.JobInfo:
|
||
|
|
input_dir = tmp_path / "input"
|
||
|
|
workspace = tmp_path / "workspace"
|
||
|
|
input_dir.mkdir(parents=True)
|
||
|
|
(workspace / "final").mkdir(parents=True)
|
||
|
|
(workspace / "logs").mkdir(parents=True)
|
||
|
|
if code:
|
||
|
|
workbook = Workbook()
|
||
|
|
sheet = workbook.active
|
||
|
|
sheet.title = "config"
|
||
|
|
sheet.append(["Species_Code", "Species_Name", "TaxID"])
|
||
|
|
sheet.append([code, name, taxon_id])
|
||
|
|
workbook.save(input_dir / "config.xlsx")
|
||
|
|
return backend.JobInfo(
|
||
|
|
job_id="job_task4",
|
||
|
|
input_dir=str(input_dir),
|
||
|
|
workspace_dir=str(workspace),
|
||
|
|
logs_dir=str(workspace / "logs"),
|
||
|
|
created_at="",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_numeric_taxon_requires_snapshot_registry_membership() -> None:
|
||
|
|
from skills.ppi.run import StringAPI
|
||
|
|
|
||
|
|
assert StringAPI.resolve_species_id("3708") == "3708"
|
||
|
|
with pytest.raises(ValueError, match="STRING snapshot registry"):
|
||
|
|
StringAPI.resolve_species_id("999999999")
|
||
|
|
|
||
|
|
|
||
|
|
def test_preflight_taxon_survives_execute_worker_and_backend_command(
|
||
|
|
tmp_path: Path,
|
||
|
|
monkeypatch,
|
||
|
|
) -> None:
|
||
|
|
from apps.web.vue3.main import ExecuteRequest
|
||
|
|
from tasks import normalize_stage_params
|
||
|
|
|
||
|
|
job = _job(tmp_path, code="Bn", name="Brassica napus", taxon_id="3708")
|
||
|
|
final_dir = Path(job.workspace_dir) / "final"
|
||
|
|
(final_dir / "Bn.HKL.final_members.id").write_text("BnaA01g01000D\n", encoding="utf-8")
|
||
|
|
scope_file = final_dir / "Bn.HKL.target.scope.json"
|
||
|
|
scope_file.write_text(
|
||
|
|
json.dumps(
|
||
|
|
{
|
||
|
|
"family": "HKL",
|
||
|
|
"prefix": "Bn.HKL",
|
||
|
|
"scope_id": "HKL.target-Bn.target_only",
|
||
|
|
"scope_mode": "target_only",
|
||
|
|
"target_species": ["Bn"],
|
||
|
|
"reference_species": [],
|
||
|
|
"selected_species": ["Bn"],
|
||
|
|
}
|
||
|
|
),
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
preflight = backend.resolve_ppi_species_resolution(
|
||
|
|
job=job,
|
||
|
|
prefix="Bn.HKL",
|
||
|
|
target_species=["Bn"],
|
||
|
|
requested_stages=["ppi"],
|
||
|
|
)
|
||
|
|
request = ExecuteRequest(
|
||
|
|
stage="ppi",
|
||
|
|
job_id=job.job_id,
|
||
|
|
prefix="Bn.HKL",
|
||
|
|
scopeFile=str(scope_file),
|
||
|
|
ppiSpecies=preflight["selected_taxon_id"],
|
||
|
|
normalizationSpeciesLabel="Bn",
|
||
|
|
)
|
||
|
|
worker_params, _profile = normalize_stage_params("ppi", request.model_dump())
|
||
|
|
assert preflight["selected_taxon_id"] == "3708"
|
||
|
|
assert request.ppiSpecies == "3708"
|
||
|
|
assert worker_params["ppiSpecies"] == "3708"
|
||
|
|
assert worker_params["normalizationSpeciesLabel"] == "Bn"
|
||
|
|
|
||
|
|
captured: dict[str, list[str]] = {}
|
||
|
|
|
||
|
|
def fake_run_cmd_logged(cmd, _log_path, cwd=None):
|
||
|
|
captured["cmd"] = list(cmd)
|
||
|
|
return 1
|
||
|
|
|
||
|
|
monkeypatch.setattr(backend, "can_start_new_task", lambda: (True, "ok"))
|
||
|
|
monkeypatch.setattr(backend, "run_cmd_logged", fake_run_cmd_logged)
|
||
|
|
backend.run_stage(
|
||
|
|
"ppi",
|
||
|
|
job,
|
||
|
|
"Bn.HKL",
|
||
|
|
scope_file=str(scope_file),
|
||
|
|
ppi_species=worker_params["ppiSpecies"],
|
||
|
|
normalization_species_label=worker_params["normalizationSpeciesLabel"],
|
||
|
|
)
|
||
|
|
|
||
|
|
command = captured["cmd"]
|
||
|
|
assert command[command.index("--species") + 1] == "3708"
|
||
|
|
assert command[command.index("--normalization-species-label") + 1] == "Bn"
|
||
|
|
|
||
|
|
|
||
|
|
def test_numeric_taxon_uses_independent_os_normalization_and_records_manifest(
|
||
|
|
tmp_path: Path,
|
||
|
|
) -> None:
|
||
|
|
from skills.ppi.run import PpiSkill
|
||
|
|
|
||
|
|
probes: list[str] = []
|
||
|
|
|
||
|
|
class FakeApi:
|
||
|
|
species_id = "39947"
|
||
|
|
|
||
|
|
def get_string_ids(self, identifiers):
|
||
|
|
probes.extend(identifiers)
|
||
|
|
normalized_index = identifiers.index("Os01g0810800")
|
||
|
|
return [
|
||
|
|
{
|
||
|
|
"queryIndex": normalized_index,
|
||
|
|
"queryItem": "Os01g0810800",
|
||
|
|
"stringId": "39947.Os01g0810800",
|
||
|
|
"preferredName": "Os01g0810800",
|
||
|
|
"ncbiTaxonId": 39947,
|
||
|
|
}
|
||
|
|
]
|
||
|
|
|
||
|
|
def get_network(self, proteins, required_score=200):
|
||
|
|
assert proteins == ["39947.Os01g0810800"]
|
||
|
|
return []
|
||
|
|
|
||
|
|
gene_file = tmp_path / "genes.txt"
|
||
|
|
output_dir = tmp_path / "output"
|
||
|
|
gene_file.write_text("Os01t0810800-01\n", encoding="utf-8")
|
||
|
|
skill = PpiSkill(string_api_factory=lambda _species: FakeApi())
|
||
|
|
|
||
|
|
manifest = skill.run(
|
||
|
|
gene_ids_file=gene_file,
|
||
|
|
species="39947",
|
||
|
|
normalization_species_label="osa",
|
||
|
|
prefix="Os.CRR",
|
||
|
|
output_dir=output_dir,
|
||
|
|
)
|
||
|
|
|
||
|
|
assert "Os01t0810800-01" in probes
|
||
|
|
assert "Os01g0810800" in probes
|
||
|
|
assert manifest["status"] == "success"
|
||
|
|
assert manifest["input"]["species"] == "39947"
|
||
|
|
assert manifest["parameters"]["string_taxon_id"] == "39947"
|
||
|
|
assert manifest["parameters"]["normalization_species_label"] == "osa"
|
||
|
|
saved = json.loads((output_dir / "Os.CRR.manifest.json").read_text(encoding="utf-8"))
|
||
|
|
assert saved["parameters"] == manifest["parameters"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_legacy_species_code_remains_compatible() -> None:
|
||
|
|
from skills.ppi.run import StringAPI
|
||
|
|
|
||
|
|
assert StringAPI.resolve_species_id("ath") == "3702"
|
||
|
|
assert StringAPI.resolve_species_id("osa") == "39947"
|
||
|
|
|
||
|
|
|
||
|
|
def test_skill_config_and_contract_describe_taxon_and_normalization_channels() -> None:
|
||
|
|
project_root = Path(__file__).resolve().parents[2]
|
||
|
|
config = (project_root / "skills/ppi/config.yaml").read_text(encoding="utf-8")
|
||
|
|
contract = (project_root / "gfa_core/schemas/stages/ppi_contract_v1.md").read_text(encoding="utf-8")
|
||
|
|
|
||
|
|
assert "normalization_species_label" in config
|
||
|
|
assert "STRING TaxID" in config
|
||
|
|
assert "normalizationSpeciesLabel" in contract
|
||
|
|
assert "任意数字" in contract
|
||
|
|
|