feat: add gene family result reports
This commit is contained in:
@@ -6054,8 +6054,9 @@ def run_stage(
|
||||
|
||||
target_species = kwargs.get("target_species")
|
||||
interspecies_ref_species = kwargs.get("interspecies_ref_species")
|
||||
scope_file = str(kwargs.get("scope_file") or kwargs.get("scopeFile") or "").strip()
|
||||
analysis_route = str(kwargs.get("analysis_route") or kwargs.get("analysisRoute") or "").strip()
|
||||
scope_file = str(kwargs.get("scope_file") or kwargs.get("scopeFile") or "").strip()
|
||||
scope_id = str(kwargs.get("scope_id") or kwargs.get("scopeId") or "").strip()
|
||||
analysis_route = str(kwargs.get("analysis_route") or kwargs.get("analysisRoute") or "").strip()
|
||||
analysis_mode = str(kwargs.get("analysis_mode") or kwargs.get("analysisMode") or "").strip()
|
||||
member_set_id = str(kwargs.get("member_set_id") or kwargs.get("memberSetId") or "").strip()
|
||||
combo_output_label = str(combo_output_label or kwargs.get("comboOutputLabel") or kwargs.get("combo_output_label") or "").strip()
|
||||
@@ -8374,7 +8375,62 @@ def run_stage(
|
||||
else:
|
||||
return f"❌ PEP failed (exit code: {code}). Log: {log_file}", manifest
|
||||
|
||||
if stage == "summary":
|
||||
if stage == "gene_family_report":
|
||||
expected_stages = kwargs.get("expected_stages") or kwargs.get("expectedStages") or []
|
||||
if isinstance(expected_stages, str):
|
||||
expected_stages = [value.strip() for value in expected_stages.split(",") if value.strip()]
|
||||
else:
|
||||
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
|
||||
stage_statuses = kwargs.get("stage_statuses") or kwargs.get("stageStatuses") or {}
|
||||
if isinstance(stage_statuses, str):
|
||||
try:
|
||||
stage_statuses = json.loads(stage_statuses)
|
||||
except json.JSONDecodeError:
|
||||
return "❌ 基因家族结果报告的 stage_statuses 不是合法 JSON", manifest
|
||||
if not isinstance(stage_statuses, dict):
|
||||
return "❌ 基因家族结果报告的 stage_statuses 必须是对象", manifest
|
||||
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 = [
|
||||
sys.executable,
|
||||
"apps/cli/run_gene_family_report.py",
|
||||
"--workspace",
|
||||
str(ws),
|
||||
"--prefix",
|
||||
prefix,
|
||||
"--job-id",
|
||||
job.job_id,
|
||||
"--expected-stages",
|
||||
",".join(expected_stages),
|
||||
"--stage-statuses-json",
|
||||
json.dumps(stage_statuses, ensure_ascii=False, separators=(",", ":")),
|
||||
"--action",
|
||||
report_action,
|
||||
]
|
||||
for flag, value in (
|
||||
("--scope-id", scope_id),
|
||||
("--analysis-route", analysis_route),
|
||||
("--member-set-id", member_set_id),
|
||||
("--rnaseq-scope-id", rnaseq_scope_id),
|
||||
):
|
||||
if value:
|
||||
cmd += [flag, value]
|
||||
if not _truthy(report_use_model_raw):
|
||||
cmd.append("--no-model")
|
||||
code = run_cmd_logged(cmd, log_file, cwd=Path("."))
|
||||
report_manifests = sorted(
|
||||
final_dir.glob(f"{prefix}*.gene_family_report.manifest.json"),
|
||||
key=lambda path: path.stat().st_mtime,
|
||||
)
|
||||
report_manifest = report_manifests[-1] if report_manifests else manifest
|
||||
if code != 0:
|
||||
return f"❌ 基因家族结果报告生成失败,日志:{log_file}", report_manifest
|
||||
return f"✅ 基因家族结果报告已生成。日志:{log_file}", report_manifest
|
||||
|
||||
if stage == "summary":
|
||||
summary_manifest_seed = (
|
||||
ws / "final" / f"{prefix}.identify.manifest.json"
|
||||
if (ws / "final" / f"{prefix}.identify.manifest.json").exists()
|
||||
|
||||
@@ -7950,6 +7950,7 @@ RNA-seq KEGG/fgsea 会优先使用物种级 KEGG gene set;没有物种级缓
|
||||
pop_snp_density: 'SNP密度分析',
|
||||
rnaseq_de: 'RNA-seq DE',
|
||||
combo: '组合图',
|
||||
gene_family_report: '基因家族结果报告',
|
||||
reference_classification: '参考亚家族分类',
|
||||
subfamily_confirm: '亚家族标签确认',
|
||||
ppi_kaks_reference_data: 'PPI/KaKs 参考数据',
|
||||
@@ -10111,6 +10112,7 @@ compare: [[flesh, peel]]`;
|
||||
{ name: 'pep', label: '1️⃣2️⃣ PEP', status: 'pending', images: [], files: [], elapsed_time: 0 },
|
||||
{ name: 'expression', label: '1️⃣3️⃣ Expression (FPKM)', status: 'pending', images: [], files: [], elapsed_time: 0 },
|
||||
{ name: 'combo', label: '6️⃣ Combo', status: 'pending', images: [], files: [], elapsed_time: 0 },
|
||||
{ name: 'gene_family_report', label: '📄 基因家族结果报告', status: 'pending', images: [], files: [], elapsed_time: 0 },
|
||||
// RNA-seq stages
|
||||
{ name: 'rnaseq_qc', label: '1️⃣5️⃣ RNA-seq QC', status: 'pending', images: [], files: [], elapsed_time: 0 },
|
||||
{ name: 'rnaseq_align', label: '1️⃣6️⃣ RNA-seq Align', status: 'pending', images: [], files: [], elapsed_time: 0 },
|
||||
@@ -15080,6 +15082,54 @@ compare: [[flesh, peel]]`;
|
||||
return `⏸️ 批量执行已暂停:成功 ${completedCount}/${totalCount},暂停 ${pausedCount}/${totalCount}。真实原因:${reasonText}。`;
|
||||
};
|
||||
|
||||
const openGeneratedGeneFamilyReport = async () => {
|
||||
await refreshResults(jobInfo.job_id, 'gene_family_report');
|
||||
const reportStage = stages.find(stage => stage.name === 'gene_family_report');
|
||||
const reportFile = (reportStage?.files || []).find(file =>
|
||||
file.role === 'gene_family_report' || String(file.name || '').endsWith('.gene_family_report.md')
|
||||
);
|
||||
if (!reportFile) return false;
|
||||
rightPanelTab.value = 'results';
|
||||
selectFile('gene_family_report', reportFile);
|
||||
return true;
|
||||
};
|
||||
|
||||
const submitGeneFamilyReportForBatch = async (stages, pausedCount) => {
|
||||
if (pausedCount > 0) return false;
|
||||
const plannedIds = (stages || []).map(stage => getStageId(stage)).filter(
|
||||
stageId => stageId && !['summary', 'gene_family_report', 'subfamily_confirm'].includes(stageId)
|
||||
);
|
||||
const geneFamilyStages = new Set([
|
||||
'identify', 'phylogeny', 'motif', 'domain', 'structure', 'promoter',
|
||||
'synteny', 'interspecies_synteny', 'karyotype', 'combo', 'ppi', 'expression', 'pep'
|
||||
]);
|
||||
if (!plannedIds.some(stageId => geneFamilyStages.has(stageId))) return false;
|
||||
const stageStatuses = Object.fromEntries(plannedIds.map(stageId => {
|
||||
const status = stageExecutionStatus.value[stageId];
|
||||
return [stageId, status === 'completed' ? 'success' : status === 'failed' ? 'failed' : 'missing'];
|
||||
}));
|
||||
const batchParams = batchExecuteParams.value || {};
|
||||
const planParams = batchParams.plan?.params || {};
|
||||
const stageParams = (stages || []).find(stage => stage && typeof stage === 'object' && stage.params)?.params || {};
|
||||
try {
|
||||
await executeSingleStageWithParams('gene_family_report', {
|
||||
expectedStages: plannedIds,
|
||||
stageStatuses,
|
||||
reportAction: 'generate',
|
||||
reportUseModel: true,
|
||||
scopeId: batchParams.scopeId || planParams.scopeId || stageParams.scopeId || '',
|
||||
analysisRoute: batchParams.analysisRoute || planParams.analysisRoute || stageParams.analysisRoute || '',
|
||||
memberSetId: batchParams.memberSetId || planParams.memberSetId || stageParams.memberSetId || '',
|
||||
rnaseqScopeId: batchParams.rnaseqScopeId || planParams.rnaseqScopeId || stageParams.rnaseqScopeId || ''
|
||||
});
|
||||
addAgentMessage('assistant', '📄 基因家族结果报告已生成。');
|
||||
return true;
|
||||
} catch (err) {
|
||||
addAgentMessage('assistant', `⚠️ 基因家族结果报告生成失败:${err.message || err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const confirmAndStartBatchExecute = async (event) => {
|
||||
if (!event?.isTrusted) return;
|
||||
batchExecutionUserConfirmed.value = true;
|
||||
@@ -15195,6 +15245,7 @@ compare: [[flesh, peel]]`;
|
||||
}).length;
|
||||
const totalCount = stages.length;
|
||||
|
||||
let continuationOpened = false;
|
||||
if (failedCount > 0) {
|
||||
addAgentMessage('assistant', `⚠️ 批量执行完成:成功 ${completedCount}/${totalCount},失败 ${failedCount}/${totalCount}`);
|
||||
} else if (pausedCount > 0) {
|
||||
@@ -15202,13 +15253,16 @@ compare: [[flesh, peel]]`;
|
||||
} else {
|
||||
addAgentMessage('assistant', `🎉 批量执行完成!成功: ${completedCount}/${totalCount}`);
|
||||
if (isMethodologyDataPreparationContinuationCandidate(stages, completedCount, totalCount, failedCount, pausedCount)) {
|
||||
await openMethodologyDataPreparationContinuation();
|
||||
continuationOpened = await openMethodologyDataPreparationContinuation();
|
||||
} else if (isMethodologyPostConfirmContinuationCandidate(stages, completedCount, totalCount, failedCount, pausedCount)) {
|
||||
await openMethodologyPostConfirmContinuation();
|
||||
continuationOpened = await openMethodologyPostConfirmContinuation();
|
||||
} else if (isContextualPostConfirmContinuationCandidate(stages, completedCount, totalCount, failedCount, pausedCount)) {
|
||||
await openContextualPostConfirmContinuation();
|
||||
continuationOpened = await openContextualPostConfirmContinuation();
|
||||
}
|
||||
}
|
||||
if (pausedCount === 0 && !continuationOpened) {
|
||||
await submitGeneFamilyReportForBatch(stages, pausedCount);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -17496,6 +17550,16 @@ compare: [[flesh, peel]]`;
|
||||
requestBody.geoId = confirmParams.geoId || '';
|
||||
requestBody.idTransform = confirmParams.idTransform || 'remove_T01';
|
||||
}
|
||||
if (stageName === 'gene_family_report') {
|
||||
requestBody.expectedStages = activeStageParams.expectedStages || [];
|
||||
requestBody.stageStatuses = activeStageParams.stageStatuses || {};
|
||||
requestBody.reportAction = activeStageParams.reportAction || 'generate';
|
||||
requestBody.reportUseModel = activeStageParams.reportUseModel !== false;
|
||||
requestBody.scopeId = activeStageParams.scopeId || '';
|
||||
requestBody.analysisRoute = activeStageParams.analysisRoute || '';
|
||||
requestBody.memberSetId = activeStageParams.memberSetId || '';
|
||||
requestBody.rnaseqScopeId = activeStageParams.rnaseqScopeId || '';
|
||||
}
|
||||
if (stageName === 'promoter_plot_revise') {
|
||||
const elements = Array.isArray(confirmParams.promoterElements) ? confirmParams.promoterElements : [];
|
||||
const categories = Array.isArray(confirmParams.promoterCategories) ? confirmParams.promoterCategories : [];
|
||||
@@ -17586,6 +17650,9 @@ compare: [[flesh, peel]]`;
|
||||
} else if (data.async === false) {
|
||||
// 同步执行完成,直接返回结果
|
||||
console.log(`[DEBUG-EXEC] ${stageName}: sync completed`);
|
||||
if (stageName === 'gene_family_report') {
|
||||
await openGeneratedGeneFamilyReport();
|
||||
}
|
||||
return data;
|
||||
} else {
|
||||
throw createStructuredExecuteError(data);
|
||||
@@ -18851,6 +18918,9 @@ compare: [[flesh, peel]]`;
|
||||
result: data
|
||||
});
|
||||
await handleStageCompleted(stageName, data, {});
|
||||
if (stageName === 'gene_family_report') {
|
||||
await openGeneratedGeneFamilyReport();
|
||||
}
|
||||
|
||||
// 更新阶段结果 - 如果有分stage的文件,则更新所有stage的文件
|
||||
if (stageIdx >= 0 && data.result) {
|
||||
|
||||
+305
-5
@@ -6876,9 +6876,14 @@ class ExecuteRequest(BaseModel):
|
||||
domCoverage: float = 30.0
|
||||
idTransform: str = "none"
|
||||
scopeFile: str = ""
|
||||
scopeId: str = ""
|
||||
scopeMode: str = ""
|
||||
analysisRoute: str = ""
|
||||
memberSetId: str = ""
|
||||
expectedStages: List[str] = []
|
||||
stageStatuses: Dict[str, str] = {}
|
||||
reportAction: str = "generate"
|
||||
reportUseModel: bool = True
|
||||
contextRoute: str = ""
|
||||
subfamilyLabelSource: str = ""
|
||||
requiresConfirmedSubfamily: bool = False
|
||||
@@ -7816,8 +7821,13 @@ def run_stage_sync(task_id: str, stage: str, job_id: str, params: dict):
|
||||
# Genome Prepare参数
|
||||
genome_prepare_type=params.get("genomePrepareType", "auto"),
|
||||
scope_file=params.get("scopeFile", ""),
|
||||
scope_id=params.get("scopeId", ""),
|
||||
analysis_route=params.get("analysisRoute", ""),
|
||||
member_set_id=params.get("memberSetId", ""),
|
||||
expected_stages=params.get("expectedStages", []),
|
||||
stage_statuses=params.get("stageStatuses", {}),
|
||||
report_action=params.get("reportAction", "generate"),
|
||||
report_use_model=params.get("reportUseModel", True),
|
||||
analysis_mode=params.get("analysisMode", ""),
|
||||
species_queue=params.get("speciesQueue", []),
|
||||
subfamily_label_source=params.get("subfamilyLabelSource", ""),
|
||||
@@ -11605,7 +11615,7 @@ async def execute_stage(req: ExecuteRequest):
|
||||
})
|
||||
if req.stage == "gwas_fmt":
|
||||
stage_context["genotype_scope_id"] = trusted_genotype_scope
|
||||
if req.stage in {"genome_prepare", "genome_prepare_v2"} or req.stage in RNA_SEQ_STAGES:
|
||||
if req.stage in {"genome_prepare", "genome_prepare_v2", "gene_family_report"} or req.stage in RNA_SEQ_STAGES:
|
||||
stage_context.pop("scope_id", None)
|
||||
stage_context.pop("scope_mode", None)
|
||||
stage_context.pop("analysis_route", None)
|
||||
@@ -11738,6 +11748,7 @@ async def execute_stage(req: ExecuteRequest):
|
||||
skip_completed_check = (
|
||||
is_utility_stage(req.stage)
|
||||
or is_repair_stage(req.stage)
|
||||
or req.stage == "gene_family_report"
|
||||
or force_rerun_requested
|
||||
or (req.stage == "gwas_fmt" and not trusted_genotype_scope)
|
||||
or (req.stage == "gwas_mlm" and not gwas_mlm_full_identity)
|
||||
@@ -11966,9 +11977,15 @@ async def execute_stage(req: ExecuteRequest):
|
||||
"domCoverage": req.domCoverage,
|
||||
"idTransform": req.idTransform,
|
||||
"scopeFile": scope_params.get("scopeFile", ""),
|
||||
"scopeId": getattr(req, "scopeId", ""),
|
||||
"rnaseqScopeId": getattr(req, "rnaseqScopeId", ""),
|
||||
"scopeMode": scope_params.get("scopeMode", ""),
|
||||
"analysisRoute": stage_context.get("analysis_route") or getattr(req, "analysisRoute", ""),
|
||||
"memberSetId": stage_context.get("member_set_id") or getattr(req, "memberSetId", ""),
|
||||
"expectedStages": list(getattr(req, "expectedStages", []) or []),
|
||||
"stageStatuses": dict(getattr(req, "stageStatuses", {}) or {}),
|
||||
"reportAction": getattr(req, "reportAction", "generate"),
|
||||
"reportUseModel": bool(getattr(req, "reportUseModel", True)),
|
||||
"contextRoute": getattr(req, "contextRoute", ""),
|
||||
"subfamilyLabelSource": getattr(req, "subfamilyLabelSource", ""),
|
||||
"requiresConfirmedSubfamily": bool(getattr(req, "requiresConfirmedSubfamily", False)),
|
||||
@@ -12728,6 +12745,69 @@ def _confirmed_execute_request(
|
||||
return ExecuteRequest(**params)
|
||||
|
||||
|
||||
def _confirmed_plan_identity(ordered: List[ConfirmedPlanStage]) -> Dict[str, str]:
|
||||
identity: Dict[str, str] = {}
|
||||
for stage in reversed(ordered):
|
||||
params = stage.params or {}
|
||||
for target, keys in {
|
||||
"scopeId": ("scopeId", "scope_id"),
|
||||
"analysisRoute": ("analysisRoute", "analysis_route"),
|
||||
"memberSetId": ("memberSetId", "member_set_id"),
|
||||
"rnaseqScopeId": ("rnaseqScopeId", "rnaseq_scope_id"),
|
||||
}.items():
|
||||
if identity.get(target):
|
||||
continue
|
||||
identity[target] = next((str(params.get(key) or "").strip() for key in keys if params.get(key)), "")
|
||||
return identity
|
||||
|
||||
|
||||
def _confirmed_plan_report_request(
|
||||
request: ConfirmedPlanExecutionRequest,
|
||||
ordered: List[ConfirmedPlanStage],
|
||||
status: Dict[str, Any],
|
||||
) -> Optional[ExecuteRequest]:
|
||||
"""Build the one formal-report submission for a terminal confirmed plan."""
|
||||
terminal = str(status.get("status") or "")
|
||||
if terminal not in {"success", "failed"}:
|
||||
return None
|
||||
planned = [stage.id for stage in ordered if stage.id not in {"summary", "gene_family_report", "subfamily_confirm"}]
|
||||
if not planned:
|
||||
return None
|
||||
completed = set(status.get("completed_stages") or [])
|
||||
failed_stage = str((status.get("failure") or {}).get("stage") or "")
|
||||
stage_statuses = {
|
||||
stage: "success" if stage in completed else "failed" if stage == failed_stage else "not_run"
|
||||
for stage in planned
|
||||
}
|
||||
if terminal == "success":
|
||||
stage_statuses = {stage: "success" for stage in planned}
|
||||
return ExecuteRequest(
|
||||
stage="gene_family_report",
|
||||
session_id=request.session_id,
|
||||
job_id=request.job_id,
|
||||
prefix=request.prefix,
|
||||
expectedStages=planned,
|
||||
stageStatuses=stage_statuses,
|
||||
reportAction="generate",
|
||||
reportUseModel=True,
|
||||
**_confirmed_plan_identity(ordered),
|
||||
)
|
||||
|
||||
|
||||
async def _submit_confirmed_plan_report(
|
||||
request: ConfirmedPlanExecutionRequest,
|
||||
ordered: List[ConfirmedPlanStage],
|
||||
status: Dict[str, Any],
|
||||
) -> None:
|
||||
report_request = _confirmed_plan_report_request(request, ordered, status)
|
||||
if report_request is None:
|
||||
return
|
||||
try:
|
||||
status["report_submission"] = await execute_stage(report_request)
|
||||
except Exception as exc:
|
||||
status["report_submission"] = {"success": False, "error_type": type(exc).__name__}
|
||||
|
||||
|
||||
async def _wait_for_submitted_stage(
|
||||
task_id: str,
|
||||
stage_id: str,
|
||||
@@ -12777,6 +12857,8 @@ async def _run_confirmed_plan_execution(
|
||||
"run_id": run_id,
|
||||
"plan_id": request.plan_id,
|
||||
"job_id": request.job_id,
|
||||
"prefix": request.prefix,
|
||||
**_confirmed_plan_identity(ordered),
|
||||
"status": "running",
|
||||
"stages": [stage.id for stage in ordered],
|
||||
"completed_stages": [],
|
||||
@@ -12820,9 +12902,11 @@ async def _run_confirmed_plan_execution(
|
||||
},
|
||||
}
|
||||
)
|
||||
await _submit_confirmed_plan_report(request, ordered, status)
|
||||
_write_confirmed_plan_status(status_path, status)
|
||||
return
|
||||
status.update({"status": "success", "current_stage": ""})
|
||||
await _submit_confirmed_plan_report(request, ordered, status)
|
||||
_write_confirmed_plan_status(status_path, status)
|
||||
|
||||
|
||||
@@ -12850,6 +12934,8 @@ async def agent_execute_confirmed(request: ConfirmedPlanExecutionRequest):
|
||||
"run_id": run_id,
|
||||
"plan_id": request.plan_id,
|
||||
"job_id": request.job_id,
|
||||
"prefix": request.prefix,
|
||||
**_confirmed_plan_identity(_confirmed_plan_stages(request)),
|
||||
"status": "queued",
|
||||
"stages": [stage.id for stage in _confirmed_plan_stages(request)],
|
||||
}
|
||||
@@ -16759,6 +16845,93 @@ def _answer_methodology_sandbox_qa(message: str) -> dict[str, Any]:
|
||||
return payload
|
||||
|
||||
|
||||
def _gene_family_report_plan_context(job_id: str, prefix: str = "") -> Optional[Dict[str, Any]]:
|
||||
"""Recover only a user-confirmed or previously reported stage plan."""
|
||||
safe_job_id = str(job_id or "").strip()
|
||||
requested_prefix = str(prefix or "").strip()
|
||||
if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_.-]{0,127}", safe_job_id):
|
||||
return None
|
||||
final_dir = WORKSPACE_ROOT / safe_job_id / "final"
|
||||
excluded = {"summary", "gene_family_report", "subfamily_confirm"}
|
||||
|
||||
confirmed: list[tuple[float, Dict[str, Any]]] = []
|
||||
confirmed_dir = final_dir / ".confirmed_plans"
|
||||
for path in confirmed_dir.glob("*.json") if confirmed_dir.is_dir() else ():
|
||||
try:
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
continue
|
||||
terminal = str(payload.get("status") or "")
|
||||
plan_prefix = str(payload.get("prefix") or "").strip()
|
||||
if (
|
||||
terminal not in {"success", "failed"}
|
||||
or str(payload.get("job_id") or "") != safe_job_id
|
||||
or not plan_prefix
|
||||
or (requested_prefix and plan_prefix != requested_prefix)
|
||||
):
|
||||
continue
|
||||
planned = [
|
||||
str(stage).strip()
|
||||
for stage in (payload.get("stages") or [])
|
||||
if str(stage).strip() and str(stage).strip() not in excluded
|
||||
]
|
||||
if not planned:
|
||||
continue
|
||||
completed = {str(stage) for stage in (payload.get("completed_stages") or [])}
|
||||
failed_stage = str((payload.get("failure") or {}).get("stage") or "")
|
||||
statuses = {
|
||||
stage: "success" if terminal == "success" or stage in completed else "failed" if stage == failed_stage else "not_run"
|
||||
for stage in planned
|
||||
}
|
||||
context = {
|
||||
"prefix": plan_prefix,
|
||||
"expectedStages": planned,
|
||||
"stageStatuses": statuses,
|
||||
"scopeId": str(payload.get("scopeId") or payload.get("scope_id") or ""),
|
||||
"analysisRoute": str(payload.get("analysisRoute") or payload.get("analysis_route") or ""),
|
||||
"memberSetId": str(payload.get("memberSetId") or payload.get("member_set_id") or ""),
|
||||
"rnaseqScopeId": str(payload.get("rnaseqScopeId") or payload.get("rnaseq_scope_id") or ""),
|
||||
}
|
||||
confirmed.append((path.stat().st_mtime, context))
|
||||
if confirmed:
|
||||
return max(confirmed, key=lambda item: item[0])[1]
|
||||
|
||||
pattern = f"{requested_prefix}.gene_family_report.manifest.json" if requested_prefix else "*.gene_family_report.manifest.json"
|
||||
reports: list[tuple[float, Dict[str, Any]]] = []
|
||||
for path in final_dir.glob(pattern) if final_dir.is_dir() else ():
|
||||
try:
|
||||
manifest = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
continue
|
||||
report_prefix = str(manifest.get("prefix") or "").strip()
|
||||
parameters = manifest.get("parameters") or {}
|
||||
planned = [
|
||||
str(stage).strip()
|
||||
for stage in (parameters.get("expected_stages") or [])
|
||||
if str(stage).strip() and str(stage).strip() not in excluded
|
||||
]
|
||||
if (
|
||||
manifest.get("stage") != "gene_family_report"
|
||||
or manifest.get("status") != "success"
|
||||
or not report_prefix
|
||||
or (requested_prefix and report_prefix != requested_prefix)
|
||||
or not planned
|
||||
):
|
||||
continue
|
||||
recorded_statuses = parameters.get("stage_statuses") or {}
|
||||
context = {
|
||||
"prefix": report_prefix,
|
||||
"expectedStages": planned,
|
||||
"stageStatuses": {stage: str(recorded_statuses.get(stage) or "missing") for stage in planned},
|
||||
"scopeId": "",
|
||||
"analysisRoute": "",
|
||||
"memberSetId": "",
|
||||
"rnaseqScopeId": "",
|
||||
}
|
||||
reports.append((path.stat().st_mtime, context))
|
||||
return max(reports, key=lambda item: item[0])[1] if reports else None
|
||||
|
||||
|
||||
async def _run_agent_chat(
|
||||
request: AgentChatRequest,
|
||||
progress_callback=None,
|
||||
@@ -16857,6 +17030,7 @@ async def _run_agent_chat(
|
||||
from gfa_core.mas.utils.intent_parser import (
|
||||
deterministic_agent_chat_intent,
|
||||
parse_sequence_extract_request,
|
||||
resolve_gene_family_report_intent,
|
||||
)
|
||||
|
||||
utility_params = parse_sequence_extract_request(request.message)
|
||||
@@ -16903,11 +17077,10 @@ async def _run_agent_chat(
|
||||
"session_id": session_id,
|
||||
}
|
||||
|
||||
deterministic_intent = deterministic_agent_chat_intent(
|
||||
compact_message = re.sub(r"\s+", "", str(request.message or "").lower())
|
||||
deterministic_intent = None if "基因家族" in compact_message else deterministic_agent_chat_intent(
|
||||
request.message,
|
||||
rnaseq_context=any(
|
||||
str(stage).startswith("rnaseq_") for stage in completed_stages
|
||||
),
|
||||
rnaseq_context=any(str(stage).startswith("rnaseq_") for stage in completed_stages),
|
||||
)
|
||||
if deterministic_intent is not None:
|
||||
report_action = str(
|
||||
@@ -16955,6 +17128,76 @@ async def _run_agent_chat(
|
||||
"handoff_request": request.message,
|
||||
}
|
||||
|
||||
gene_report_intent = resolve_gene_family_report_intent(request.message)
|
||||
if gene_report_intent is not None:
|
||||
plan_context = _gene_family_report_plan_context(
|
||||
request.job_id,
|
||||
str(router_context_terms.get("prefix") or ""),
|
||||
)
|
||||
report_action = str((gene_report_intent.get("params") or {}).get("reportAction") or "generate")
|
||||
if plan_context is None:
|
||||
report_message = "已识别为基因家族结果报告请求,但当前 job 缺少可验证的分析计划,无法生成或打开正式报告。"
|
||||
if conversation_turn is not None:
|
||||
active_store.finish_turn(
|
||||
conversation_turn,
|
||||
assistant_message=report_message,
|
||||
interaction_kind="independent",
|
||||
action="reply",
|
||||
intent=None,
|
||||
)
|
||||
return {
|
||||
"success": False,
|
||||
"status": "report_plan_missing",
|
||||
"action": None,
|
||||
"intent": None,
|
||||
"message": report_message,
|
||||
"sources": [],
|
||||
"tool_trace": [],
|
||||
"mode": "analysis",
|
||||
"completed_stages": completed_stages,
|
||||
"model_used": "rule",
|
||||
"session_id": session_id,
|
||||
}
|
||||
report_message = (
|
||||
"已识别为打开基因家族结果报告。"
|
||||
if report_action == "open"
|
||||
else "已识别为生成基因家族结果报告。"
|
||||
)
|
||||
report_params = {**(gene_report_intent.get("params") or {}), **plan_context}
|
||||
resolved_intent = {
|
||||
**gene_report_intent,
|
||||
"stage": "gene_family_report",
|
||||
"primary_stage": "gene_family_report",
|
||||
"taskType": "analysis",
|
||||
"task_kind": "report",
|
||||
"prefix": plan_context["prefix"],
|
||||
"params": report_params,
|
||||
"job_id": request.job_id,
|
||||
"workspace": str(WORKSPACE_ROOT / request.job_id) if request.job_id else "",
|
||||
"message": report_message,
|
||||
}
|
||||
if conversation_turn is not None:
|
||||
active_store.finish_turn(
|
||||
conversation_turn,
|
||||
assistant_message=report_message,
|
||||
interaction_kind="independent",
|
||||
action="propose_analysis",
|
||||
intent=resolved_intent,
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"status": "ok",
|
||||
"action": "propose_analysis",
|
||||
"intent": resolved_intent,
|
||||
"sources": [],
|
||||
"tool_trace": [],
|
||||
"mode": "analysis",
|
||||
"completed_stages": completed_stages,
|
||||
"model_used": "rule",
|
||||
"session_id": session_id,
|
||||
"handoff_request": request.message,
|
||||
}
|
||||
|
||||
global llm_manager
|
||||
active_llm_manager = llm_manager
|
||||
if active_llm_manager is None:
|
||||
@@ -20060,6 +20303,63 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
extension_intent = _match_extension_stage_intent(request.message, request.job_id or "")
|
||||
if extension_intent:
|
||||
return _extension_stage_plan_response(extension_intent, session_id, request.job_id or "")
|
||||
report_intent = dict(request.intent or {})
|
||||
if report_intent.get("stages") == ["gene_family_report"]:
|
||||
report_params = dict(report_intent.get("params") or {})
|
||||
report_prefix = str(
|
||||
report_intent.get("prefix")
|
||||
or report_params.get("prefix")
|
||||
or request.prefix
|
||||
or ""
|
||||
).strip()
|
||||
if not report_params.get("expectedStages"):
|
||||
recovered = _gene_family_report_plan_context(request.job_id, report_prefix)
|
||||
if recovered:
|
||||
report_params.update(recovered)
|
||||
report_prefix = recovered["prefix"]
|
||||
if not report_params.get("expectedStages"):
|
||||
return {
|
||||
"success": False,
|
||||
"mode": "gene_family_report_deterministic",
|
||||
"status": "report_plan_missing",
|
||||
"message": "当前 job 缺少可验证的基因家族分析计划,无法生成或打开正式报告。",
|
||||
"session_id": session_id,
|
||||
}
|
||||
report_params["prefix"] = report_prefix
|
||||
report_intent.update(
|
||||
{
|
||||
"stage": "gene_family_report",
|
||||
"primary_stage": "gene_family_report",
|
||||
"stages": ["gene_family_report"],
|
||||
"prefix": report_prefix,
|
||||
"params": report_params,
|
||||
}
|
||||
)
|
||||
detail = _stage_detail_defaults("gene_family_report")
|
||||
detail.update({"order": 1, "dependencies": [], "params": dict(report_params)})
|
||||
return {
|
||||
"success": True,
|
||||
"mode": "gene_family_report_deterministic",
|
||||
"plan": {
|
||||
"analysis_type": "gene_family",
|
||||
"prefix": report_prefix,
|
||||
"description": "生成或打开基因家族结果报告",
|
||||
"estimated_time": "2分钟",
|
||||
"estimated_total_time": "2分钟",
|
||||
"execution_strategy": "sequential",
|
||||
"max_parallel": 1,
|
||||
"total_stages": 1,
|
||||
"stages": ["gene_family_report"],
|
||||
"stages_detail": [detail],
|
||||
"all_stages": ["gene_family_report"],
|
||||
"all_stages_detail": [detail],
|
||||
"parallel_groups": [],
|
||||
"params": report_params,
|
||||
},
|
||||
"intent": report_intent,
|
||||
"completed_stages": [],
|
||||
"session_id": session_id,
|
||||
}
|
||||
plan_role_resolution = (
|
||||
_reconcile_confirmed_gene_family_species(
|
||||
request.job_id,
|
||||
|
||||
Reference in New Issue
Block a user