262 lines
10 KiB
Python
262 lines
10 KiB
Python
#!/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())
|