fix: integrate deployed reports with PPI KaKs handoff

This commit is contained in:
potaser
2026-08-11 04:48:38 +08:00
parent 8b61269d3f
commit 4a80793b4a
19 changed files with 1654 additions and 231 deletions
+65 -1
View File
@@ -3412,6 +3412,7 @@ RNA_SEQ_STAGES = {
"rnaseq_wgcna",
"rnaseq_candidate_family",
"rnaseq_family_score",
"rnaseq_report",
}
@@ -8441,7 +8442,32 @@ def run_stage(
expected_stages = [str(value).strip() for value in expected_stages if str(value).strip()]
report_action = str(kwargs.get("report_action") or kwargs.get("reportAction") or "generate").strip()
if report_action == "generate" and not expected_stages:
return "❌ 基因家族结果报告生成需要非空 expected_stages", manifest
report_stage_order = (
"identify", "phylogeny", "reference_classification", "motif",
"domain", "structure", "combo", "promoter", "tfbs",
"synteny", "karyotype", "interspecies_synteny", "pep",
)
recovered_stages = set()
for candidate in final_dir.glob("*.manifest.json"):
try:
payload = json.loads(candidate.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
continue
candidate_stage = str(payload.get("stage") or "").strip()
candidate_prefix = str(
payload.get("prefix")
or (payload.get("parameters") or {}).get("prefix")
or ""
).strip()
if (
candidate_stage in report_stage_order
and str(payload.get("status") or "") == "success"
and candidate_prefix == prefix
):
recovered_stages.add(candidate_stage)
expected_stages = [stage_id for stage_id in report_stage_order if stage_id in recovered_stages]
if report_action == "generate" and not expected_stages:
return "❌ 基因家族结果报告生成需要非空 expected_stages,且当前前缀没有可恢复的成功 manifest", manifest
stage_statuses = kwargs.get("stage_statuses") or kwargs.get("stageStatuses") or {}
if isinstance(stage_statuses, str):
try:
@@ -8450,6 +8476,8 @@ def run_stage(
return "❌ 基因家族结果报告的 stage_statuses 不是合法 JSON", manifest
if not isinstance(stage_statuses, dict):
return "❌ 基因家族结果报告的 stage_statuses 必须是对象", manifest
if not stage_statuses:
stage_statuses = {stage_id: "success" for stage_id in expected_stages}
report_use_model_raw = kwargs.get("report_use_model", kwargs.get("reportUseModel", True))
rnaseq_scope_id = str(kwargs.get("rnaseq_scope_id") or kwargs.get("rnaseqScopeId") or "").strip()
cmd = [
@@ -11465,6 +11493,42 @@ def run_stage(
return "✅ RNA-seq WGCNA completed.", wgcna_manifest
return f"❌ RNA-seq WGCNA failed (exit code: {code}). Log: {log_file}", wgcna_manifest if wgcna_manifest.exists() else manifest
if stage == "rnaseq_report":
report_action = str(kwargs.get("rnaseq_report_action") or kwargs.get("report_action") or "generate").strip().lower()
if report_action not in {"generate", "open"}:
report_action = "generate"
expected_raw = kwargs.get("rnaseq_expected_stages") or kwargs.get("expected_stages") or ""
if isinstance(expected_raw, (list, tuple, set)):
expected_arg = ",".join(str(item).strip() for item in expected_raw if str(item).strip())
else:
expected_arg = str(expected_raw or "").strip()
report_scope_id = str(kwargs.get("rnaseq_scope_id") or kwargs.get("rnaseqScopeId") or "").strip()
report_use_model = kwargs.get("rnaseq_report_use_model", kwargs.get("report_use_model", True))
report_cmd = [
sys.executable,
"apps/cli/run_rnaseq_report.py",
"--workspace", str(ws),
"--prefix", prefix,
"--job-id", job.job_id,
"--action", report_action,
"--log-file", str(log_file),
]
if report_scope_id:
report_cmd.extend(["--rnaseq-scope-id", report_scope_id])
if expected_arg:
report_cmd.extend(["--expected-stages", expected_arg])
if not _truthy(report_use_model):
report_cmd.append("--no-model")
print(f"[CMD] {' '.join(report_cmd)}")
code = run_cmd_logged(report_cmd, log_file, cwd=Path("."))
report_manifest = final_dir / f"{prefix}.rnaseq_report.manifest.json"
if code == 0 and report_manifest.exists():
action_label = "打开" if report_action == "open" else "生成"
return f"✅ RNA-seq 结果报告已{action_label}。日志:{log_file}", report_manifest
if code == 2:
return "⚠️ 暂无可生成的 RNA-seq 结果:当前 prefix/scope 下没有成功阶段。", None
return f"❌ RNA-seq 结果报告生成失败(exit code: {code})。日志:{log_file}", report_manifest if report_manifest.exists() else None
if stage == "rnaseq_candidate_family":
_SPECIES_RESOURCE_ALIASES = {
"os": ["Os", "Oryza", "Oryza_sativa", "rice"],
File diff suppressed because it is too large Load Diff
+80 -5
View File
@@ -4746,7 +4746,7 @@ def _celery_start_script_for_current_profile() -> Path:
port = os.environ.get("PLANTAI_WEB_PORT", "").strip()
if prefix.endswith("_9000") or redis_url.endswith("/10") or port == "9000":
return PROJECT_ROOT / "start_celery_9000.sh"
return PROJECT_ROOT / "start_celery_7880.sh"
return PROJECT_ROOT / "scripts" / "legacy_startup" / "start_celery_7880.sh"
def _current_celery_profile() -> Dict[str, Any]:
@@ -4756,7 +4756,11 @@ def _current_celery_profile() -> Dict[str, Any]:
prefix = get_queue_prefix()
except Exception:
prefix = os.environ.get("BBAV5_QUEUE_PREFIX", "plantai_dev_7880").strip() or "plantai_dev_7880"
redis_default = "redis://localhost:6379/10" if prefix.endswith("_9000") or os.environ.get("PLANTAI_WEB_PORT", "").strip() == "9000" else "redis://localhost:6379/9"
current_port = os.environ.get("PLANTAI_WEB_PORT", "").strip()
if prefix.endswith("_9000") or current_port == "9000":
redis_default = "redis://localhost:6379/10"
else:
redis_default = "redis://localhost:6379/9"
redis_url = os.environ.get("BBAV5_REDIS_URL", redis_default).strip() or redis_default
script = _celery_start_script_for_current_profile()
log_dir = Path(os.environ.get("BBAV5_CELERY_LOG_DIR", "") or f"logs/celery_{prefix}")
@@ -8647,7 +8651,7 @@ def run_stage_sync(task_id: str, stage: str, job_id: str, params: dict):
async def root():
"""返回Vue3前端页面"""
return FileResponse(
Path(__file__).parent / "index.html",
Path(__file__).parent / "index_ml.html",
headers={
"Cache-Control": "no-cache, no-store, must-revalidate",
"Pragma": "no-cache",
@@ -17217,7 +17221,78 @@ def _gene_family_report_plan_context(job_id: str, prefix: str = "") -> Optional[
"rnaseqScopeId": "",
}
reports.append((path.stat().st_mtime, context))
return max(reports, key=lambda item: item[0])[1] if reports else None
if reports:
return max(reports, key=lambda item: item[0])[1]
# Older jobs may have complete, successful stage manifests but no saved
# confirmation-plan snapshot. Reconstruct a deterministic report plan from
# one manifest prefix rather than rejecting those verifiable results.
report_stage_order = (
"identify",
"phylogeny",
"reference_classification",
"motif",
"domain",
"structure",
"combo",
"promoter",
"tfbs",
"synteny",
"karyotype",
"interspecies_synteny",
"pep",
)
report_stage_set = set(report_stage_order)
recovered_by_prefix: Dict[str, Dict[str, tuple[float, Dict[str, Any]]]] = {}
for path in final_dir.glob("*.manifest.json") if final_dir.is_dir() else ():
try:
manifest = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
continue
stage = str(manifest.get("stage") or "").strip()
manifest_prefix = str(
manifest.get("prefix")
or (manifest.get("parameters") or {}).get("prefix")
or ""
).strip()
if (
stage not in report_stage_set
or str(manifest.get("status") or "") != "success"
or not manifest_prefix
or (requested_prefix and manifest_prefix != requested_prefix)
):
continue
stage_map = recovered_by_prefix.setdefault(manifest_prefix, {})
candidate = (path.stat().st_mtime, manifest)
if stage not in stage_map or candidate[0] > stage_map[stage][0]:
stage_map[stage] = candidate
if not recovered_by_prefix:
return None
def recovered_rank(item: tuple[str, Dict[str, tuple[float, Dict[str, Any]]]]) -> tuple[int, int, float, str]:
manifest_prefix, stage_map = item
latest_mtime = max((entry[0] for entry in stage_map.values()), default=0.0)
return (
1 if "identify" in stage_map else 0,
len(stage_map),
latest_mtime,
manifest_prefix,
)
recovered_prefix, recovered_stages = max(recovered_by_prefix.items(), key=recovered_rank)
planned = [stage for stage in report_stage_order if stage in recovered_stages]
if not planned:
return None
return {
"prefix": recovered_prefix,
"expectedStages": planned,
"stageStatuses": {stage: "success" for stage in planned},
"scopeId": "",
"analysisRoute": "",
"memberSetId": "",
"rnaseqScopeId": "",
}
async def _run_agent_chat(
@@ -25417,7 +25492,7 @@ async def root():
"""
返回前端首页
"""
index_file = WEB_DIR / "index.html"
index_file = WEB_DIR / "index_ml.html"
if index_file.exists():
return FileResponse(
str(index_file),