fix: integrate deployed reports with PPI KaKs handoff
This commit is contained in:
@@ -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"],
|
||||
|
||||
+938
-212
File diff suppressed because it is too large
Load Diff
+80
-5
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user