Files
PlantAI-dev/scripts/run_vcf10_threshold_adapter_conversion_test.py
T

262 lines
10 KiB
Python
Raw Normal View History

2026-08-03 18:42:47 +08:00
#!/usr/bin/env python3
"""Run one API conversion test for the VCF-10 threshold adapter."""
from __future__ import annotations
import argparse
import json
import os
import shutil
import sys
import time
import traceback
from datetime import datetime
from pathlib import Path
from typing import Any
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
DEFAULT_CODE = PROJECT_ROOT / "scripts" / "vcf" / "07_10_gvcf_to_clean_vcf" / "run_10_hard_filter_clean_api_adapter.sh"
DEFAULT_DATA = Path("/tmp/vcf_07_10_extracted_test/09.selected")
DEFAULT_OUTPUT = PROJECT_ROOT / "项目记录" / "VCF_10_threshold_parameter_test_20260623" / "api_adapter_conversion_test"
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--code", type=Path, default=DEFAULT_CODE)
parser.add_argument("--data-dir", type=Path, default=DEFAULT_DATA)
parser.add_argument("--output-root", type=Path, default=DEFAULT_OUTPUT)
parser.add_argument("--runtime-env", default="ASAW")
parser.add_argument("--display-name", default="vcf_10_threshold_adapter_relaxed_api_test")
parser.add_argument("--overwrite", action="store_true")
args = parser.parse_args()
from gfa_core.extension_stage import ExtensionStageAgent, ExtensionStageManager
os.environ["BBAV5_EXTENSION_CONVERTER"] = "deepseek_api"
output_root = args.output_root.resolve()
if args.overwrite and output_root.exists():
shutil.rmtree(output_root)
output_root.mkdir(parents=True, exist_ok=True)
row = base_row(args)
started = time.time()
manager = ExtensionStageManager(
PROJECT_ROOT,
store_root=output_root / "extension_stages",
workspace_root=output_root / "workspace",
input_root=output_root / "input",
)
agent = ExtensionStageAgent(manager)
try:
build = agent.create_from_uploads(
display_name=args.display_name,
command="bash run_10_hard_filter_clean_api_adapter.sh --input-dir {input_dir} --output-dir {output_dir}",
runtime_env=args.runtime_env,
code_files=[(args.code.name, args.code.read_bytes())],
data_files=upload_data_files(args.data_dir),
description="VCF-10 hard-filter threshold adapter API conversion test using validated partial 09.selected VCF inputs.",
)
write_json(output_root / "build_response.json", build)
ext = build.get("extension") or {}
eid = str(ext.get("extension_id") or "")
row.update(
{
"extension_id": eid,
"archetype_id": str(ext.get("archetype_id") or ""),
"analysis_type": str(ext.get("analysis_type") or ""),
"tool_family": str(ext.get("tool_family") or ""),
"build_success": bool(build.get("success") and eid),
"build_api_attempt_count": len(ext.get("build_attempts") or []),
"command_template": str((ext.get("entrypoint") or {}).get("command_template") or ""),
}
)
if not row["build_success"]:
row["first_failure_phase"] = "build"
return finish(output_root, row, started)
trial = agent.trial_run(eid)
write_json(output_root / "trial_run.json", trial)
row["trial_success"] = bool(trial.get("success"))
row["trial_output_count"] = len((trial.get("run") or {}).get("outputs") or [])
row["trial_error"] = str((trial.get("run") or {}).get("error") or "")
if not row["trial_success"]:
row["first_failure_phase"] = "trial"
return finish(output_root, row, started)
outputs = (trial.get("extension") or {}).get("outputs_detected") or (trial.get("run") or {}).get("outputs") or []
confirm = agent.confirm(
eid,
outputs=outputs,
nl_triggers=[
"vcf hard filter clean",
"vcf threshold profile",
"VCF拓展分析第十步过滤",
],
)
write_json(output_root / "confirm_response.json", confirm)
row["confirm_success"] = bool(confirm.get("success"))
if not row["confirm_success"]:
row["first_failure_phase"] = "confirm"
return finish(output_root, row, started)
validation = agent.validate(eid)
write_json(output_root / "validate_response.json", validation)
row.update(validation_status(validation))
if not row["validate_success"]:
row["first_failure_phase"] = "validation"
row["validation_error"] = str((validation.get("run") or {}).get("error") or "")
return finish(output_root, row, started)
publish = agent.publish(eid)
write_json(output_root / "publish_response.json", publish)
row["publish_success"] = bool(publish.get("success"))
row["publish_status"] = str((publish.get("extension") or {}).get("status") or "")
if not row["publish_success"]:
row["first_failure_phase"] = "publish"
return finish(output_root, row, started)
job_id = "vcf10_threshold_adapter_published_run"
materialize_job_inputs(agent, job_id, args.data_dir)
published_run = agent.run(extension_id=eid, job_id=job_id)
write_json(output_root / "published_run_response.json", published_run)
row["published_run_success"] = bool(published_run.get("success"))
row["published_run_error"] = str((published_run.get("run") or {}).get("error") or "")
row["published_output_count"] = len((published_run.get("run") or {}).get("outputs") or [])
if not row["published_run_success"]:
row["first_failure_phase"] = "published_run"
except Exception as exc:
row["error"] = str(exc)
row["traceback"] = traceback.format_exc()
if not row.get("first_failure_phase"):
row["first_failure_phase"] = infer_failure_phase(row)
write_json(output_root / "error.json", {"error": row["error"], "traceback": row["traceback"], "row": row})
return finish(output_root, row, started)
def upload_data_files(data_dir: Path) -> list[tuple[str, bytes]]:
names = [
"variants.snp.vcf.gz",
"variants.snp.vcf.gz.tbi",
"variants.indel.vcf.gz",
"variants.indel.vcf.gz.tbi",
]
files = []
for name in names:
path = data_dir / name
if not path.exists():
raise FileNotFoundError(f"missing required input: {path}")
files.append((name, path.read_bytes()))
return files
def materialize_job_inputs(agent: Any, job_id: str, raw_data: Path) -> None:
input_dir = agent.manager.input_root / job_id
input_dir.mkdir(parents=True, exist_ok=True)
for name, payload in upload_data_files(raw_data):
(input_dir / name).write_bytes(payload)
def validation_status(response: dict[str, Any]) -> dict[str, Any]:
run = response.get("run") or {}
golden = run.get("golden_comparison") or {}
gate = run.get("template_gate") or {}
return {
"validate_success": bool(response.get("success")),
"validation_status": str(run.get("status") or ""),
"validation_error": str(run.get("error") or ""),
"golden_status": str(golden.get("status") or ""),
"template_gate_status": str(gate.get("status") or ""),
}
def base_row(args: argparse.Namespace) -> dict[str, Any]:
return {
"schema_version": "vcf10_threshold_adapter_api_conversion_result_v1",
"created_at": now(),
"converter": "deepseek_api",
"codex_manual_spec_edit": False,
"runtime_env": args.runtime_env,
"code_file": str(args.code),
"data_dir": str(args.data_dir),
"validation_scope": "partial real VCF inputs from 09.selected",
"extension_id": "",
"archetype_id": "",
"analysis_type": "",
"tool_family": "",
"build_success": False,
"trial_success": False,
"confirm_success": False,
"validate_success": False,
"golden_status": "",
"template_gate_status": "",
"publish_success": False,
"publish_status": "",
"published_run_success": False,
"api_autonomous_success": False,
"first_failure_phase": "",
"error": "",
"traceback": "",
"wall_sec": 0,
}
def infer_failure_phase(row: dict[str, Any]) -> str:
for phase, key in [
("build", "build_success"),
("trial", "trial_success"),
("confirm", "confirm_success"),
("validation", "validate_success"),
("golden", "golden_status"),
("template_gate", "template_gate_status"),
("publish", "publish_success"),
("published_run", "published_run_success"),
]:
value = row.get(key)
if key.endswith("_status"):
if value != "passed":
return phase
elif not value:
return phase
return ""
def finish(output_root: Path, row: dict[str, Any], started: float) -> int:
row["api_autonomous_success"] = all(
[
row.get("build_success"),
row.get("trial_success"),
row.get("confirm_success"),
row.get("validate_success"),
row.get("golden_status") == "passed",
row.get("template_gate_status") == "passed",
row.get("publish_success"),
row.get("published_run_success"),
not row.get("codex_manual_spec_edit"),
]
)
if not row.get("first_failure_phase") and not row["api_autonomous_success"]:
row["first_failure_phase"] = infer_failure_phase(row)
row["wall_sec"] = round(time.time() - started, 3)
write_json(output_root / "result_row.json", row)
return 0 if row["api_autonomous_success"] else 1
def write_json(path: Path, data: Any) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
def now() -> str:
return datetime.now().replace(microsecond=0).isoformat()
if __name__ == "__main__":
raise SystemExit(main())