213 lines
6.9 KiB
Python
213 lines
6.9 KiB
Python
import asyncio
|
|||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
from openpyxl import Workbook
|
||
|
|
|
||
|
|
from apps.web import pipeline_backend as backend
|
||
|
|
|
||
|
|
|
||
|
|
def _write_config(input_dir: Path, headers: list[str], rows: list[list[object]]) -> None:
|
||
|
|
input_dir.mkdir(parents=True, exist_ok=True)
|
||
|
|
workbook = Workbook()
|
||
|
|
sheet = workbook.active
|
||
|
|
sheet.title = "config"
|
||
|
|
sheet.append(headers)
|
||
|
|
for row in rows:
|
||
|
|
sheet.append(row)
|
||
|
|
workbook.save(input_dir / "config.xlsx")
|
||
|
|
|
||
|
|
|
||
|
|
def test_job_identity_reads_unique_setaria_scientific_name(tmp_path: Path) -> None:
|
||
|
|
input_dir = tmp_path / "input"
|
||
|
|
_write_config(
|
||
|
|
input_dir,
|
||
|
|
["Species_Code", "Species_Name", "File_Type"],
|
||
|
|
[
|
||
|
|
["Sv", "Setaria_viridis", "pep"],
|
||
|
|
["Sv", "Setaria viridis", "cds"],
|
||
|
|
],
|
||
|
|
)
|
||
|
|
|
||
|
|
identity = backend.resolve_ppi_target_identity(input_dir, "Sv")
|
||
|
|
|
||
|
|
assert identity["status"] == "resolved"
|
||
|
|
assert identity["scientific_name"] == "Setaria viridis"
|
||
|
|
assert identity["tree_taxon_id"] == ""
|
||
|
|
assert identity["identity_source"] == "job_scientific_name"
|
||
|
|
|
||
|
|
|
||
|
|
def test_request_model_exposes_independent_target_identity_fields() -> None:
|
||
|
|
from apps.web.vue3.main import PpiSpeciesResolutionRequest
|
||
|
|
|
||
|
|
request = PpiSpeciesResolutionRequest(
|
||
|
|
job_id="job_identity",
|
||
|
|
prefix="Sv.HKL",
|
||
|
|
targetScientificName="Setaria viridis",
|
||
|
|
ppiTargetTreeTaxonId="4556",
|
||
|
|
)
|
||
|
|
|
||
|
|
assert request.targetScientificName == "Setaria viridis"
|
||
|
|
assert request.ppiTargetTreeTaxonId == "4556"
|
||
|
|
assert request.ppiSpecies == "auto"
|
||
|
|
|
||
|
|
|
||
|
|
def test_explicit_identity_reaches_backend_without_using_ppi_species(tmp_path: Path) -> None:
|
||
|
|
input_dir = tmp_path / "input"
|
||
|
|
workspace = tmp_path / "workspace"
|
||
|
|
input_dir.mkdir()
|
||
|
|
(workspace / "final").mkdir(parents=True)
|
||
|
|
job = backend.JobInfo(
|
||
|
|
job_id="job_identity",
|
||
|
|
input_dir=str(input_dir),
|
||
|
|
workspace_dir=str(workspace),
|
||
|
|
logs_dir=str(workspace / "logs"),
|
||
|
|
created_at="",
|
||
|
|
)
|
||
|
|
|
||
|
|
result = backend.resolve_ppi_species_resolution(
|
||
|
|
job=job,
|
||
|
|
prefix="Sv.HKL",
|
||
|
|
ppi_species="auto",
|
||
|
|
target_species=["Sv"],
|
||
|
|
requested_stages=["ppi"],
|
||
|
|
target_scientific_name="Setaria viridis",
|
||
|
|
ppi_target_tree_taxon_id="4556",
|
||
|
|
)
|
||
|
|
|
||
|
|
assert result["requested_ppi_species"] == "auto"
|
||
|
|
assert result["target_identity"]["scientific_name"] == "Setaria viridis"
|
||
|
|
assert result["target_identity"]["tree_taxon_id"] == "4556"
|
||
|
|
assert result["target_identity"]["identity_source"] == "explicit_taxon_id"
|
||
|
|
|
||
|
|
|
||
|
|
def test_taxon_id_column_aliases_and_priority(tmp_path: Path) -> None:
|
||
|
|
expected_by_header = {
|
||
|
|
"Taxon_ID": "4556",
|
||
|
|
"TaxID": "4556",
|
||
|
|
"NCBI_TaxID": "4556",
|
||
|
|
}
|
||
|
|
for header, expected in expected_by_header.items():
|
||
|
|
input_dir = tmp_path / header
|
||
|
|
_write_config(
|
||
|
|
input_dir,
|
||
|
|
["Species_Code", header],
|
||
|
|
[["Sv", 4556]],
|
||
|
|
)
|
||
|
|
identity = backend.resolve_ppi_target_identity(input_dir, "Sv")
|
||
|
|
assert identity["tree_taxon_id"] == expected
|
||
|
|
assert identity["identity_source"] == "job_taxon_id"
|
||
|
|
|
||
|
|
priority_dir = tmp_path / "priority"
|
||
|
|
_write_config(
|
||
|
|
priority_dir,
|
||
|
|
["Species_Code", "Taxon_ID", "TaxID", "NCBI_TaxID"],
|
||
|
|
[["Sv", 4556, 4555, 4540]],
|
||
|
|
)
|
||
|
|
priority = backend.resolve_ppi_target_identity(priority_dir, "Sv")
|
||
|
|
assert priority["tree_taxon_id"] == "4556"
|
||
|
|
|
||
|
|
|
||
|
|
def test_explicit_taxon_conflict_with_job_config_is_structured(tmp_path: Path) -> None:
|
||
|
|
input_dir = tmp_path / "input"
|
||
|
|
_write_config(
|
||
|
|
input_dir,
|
||
|
|
["Species_Code", "Species_Name", "TaxID"],
|
||
|
|
[["Sv", "Setaria viridis", 4556]],
|
||
|
|
)
|
||
|
|
|
||
|
|
identity = backend.resolve_ppi_target_identity(
|
||
|
|
input_dir,
|
||
|
|
"Sv",
|
||
|
|
explicit_tree_taxon_id="4555",
|
||
|
|
)
|
||
|
|
|
||
|
|
assert identity["status"] == "conflict"
|
||
|
|
assert identity["issue_code"] == "target_taxon_id_conflict"
|
||
|
|
assert identity["tree_taxon_id"] == ""
|
||
|
|
assert identity["explicit_tree_taxon_id"] == "4555"
|
||
|
|
assert identity["config_tree_taxon_id"] == "4556"
|
||
|
|
|
||
|
|
|
||
|
|
def test_explicit_scientific_name_conflict_never_mixes_with_job_taxon(tmp_path: Path) -> None:
|
||
|
|
input_dir = tmp_path / "input"
|
||
|
|
_write_config(
|
||
|
|
input_dir,
|
||
|
|
["Species_Code", "Species_Name", "TaxID"],
|
||
|
|
[["Sv", "Setaria viridis", 4556]],
|
||
|
|
)
|
||
|
|
|
||
|
|
identity = backend.resolve_ppi_target_identity(
|
||
|
|
input_dir,
|
||
|
|
"Sv",
|
||
|
|
explicit_scientific_name="Setaria_italica",
|
||
|
|
)
|
||
|
|
|
||
|
|
assert identity["status"] == "conflict"
|
||
|
|
assert identity["issue_code"] == "target_scientific_name_conflict"
|
||
|
|
assert identity["scientific_name"] == ""
|
||
|
|
assert identity["tree_taxon_id"] == ""
|
||
|
|
assert identity["explicit_scientific_name"] == "Setaria italica"
|
||
|
|
assert identity["config_scientific_name"] == "Setaria viridis"
|
||
|
|
|
||
|
|
|
||
|
|
def test_species_resolution_api_surfaces_scientific_name_conflict(
|
||
|
|
tmp_path: Path,
|
||
|
|
monkeypatch,
|
||
|
|
) -> None:
|
||
|
|
from apps.web.vue3 import main
|
||
|
|
|
||
|
|
job_id = "job_identity_conflict"
|
||
|
|
input_root = tmp_path / "input_root"
|
||
|
|
workspace_root = tmp_path / "workspace_root"
|
||
|
|
input_dir = input_root / job_id
|
||
|
|
(workspace_root / job_id / "final").mkdir(parents=True)
|
||
|
|
_write_config(
|
||
|
|
input_dir,
|
||
|
|
["Species_Code", "Species_Name", "TaxID"],
|
||
|
|
[["Sv", "Setaria viridis", 4556]],
|
||
|
|
)
|
||
|
|
monkeypatch.setattr(main, "INPUT_ROOT", input_root)
|
||
|
|
monkeypatch.setattr(main, "WORKSPACE_ROOT", workspace_root)
|
||
|
|
|
||
|
|
response = asyncio.run(
|
||
|
|
main.resolve_ppi_species_resolution_api(
|
||
|
|
main.PpiSpeciesResolutionRequest(
|
||
|
|
job_id=job_id,
|
||
|
|
prefix="Sv.HKL",
|
||
|
|
targetSpecies=["Sv"],
|
||
|
|
targetScientificName="Setaria italica",
|
||
|
|
requestedStages=["ppi"],
|
||
|
|
)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
assert response["target_identity"]["status"] == "conflict"
|
||
|
|
assert response["target_identity"]["issue_code"] == "target_scientific_name_conflict"
|
||
|
|
assert response["target_identity"]["scientific_name"] == ""
|
||
|
|
assert response["target_identity"]["tree_taxon_id"] == ""
|
||
|
|
|
||
|
|
|
||
|
|
def test_only_unknown_short_code_does_not_guess_identity(tmp_path: Path) -> None:
|
||
|
|
input_dir = tmp_path / "input"
|
||
|
|
input_dir.mkdir()
|
||
|
|
|
||
|
|
identity = backend.resolve_ppi_target_identity(input_dir, "Sv")
|
||
|
|
|
||
|
|
assert identity["status"] == "unresolved"
|
||
|
|
assert identity["issue_code"] == "target_string_tree_unresolved"
|
||
|
|
assert identity["scientific_name"] == ""
|
||
|
|
assert identity["tree_taxon_id"] == ""
|
||
|
|
|
||
|
|
|
||
|
|
def test_os_and_ft_keep_curated_compatibility_identity(tmp_path: Path) -> None:
|
||
|
|
input_dir = tmp_path / "input"
|
||
|
|
input_dir.mkdir()
|
||
|
|
|
||
|
|
rice = backend.resolve_ppi_target_identity(input_dir, "Os")
|
||
|
|
buckwheat = backend.resolve_ppi_target_identity(input_dir, "Ft")
|
||
|
|
|
||
|
|
assert rice["tree_taxon_id"] == "39947"
|
||
|
|
assert rice["identity_source"] == "project_alias"
|
||
|
|
assert buckwheat["tree_taxon_id"] == "62330"
|
||
|
|
assert buckwheat["identity_source"] == "project_alias"
|