feat: add evidence-bound RNA-seq result reports
This commit is contained in:
+136
-2
@@ -6911,6 +6911,10 @@ class ExecuteRequest(BaseModel):
|
||||
speciesCode: str = ""
|
||||
rnaseqPrefix: str = ""
|
||||
rnaseqOutputPrefix: str = ""
|
||||
rnaseqScopeId: str = ""
|
||||
rnaseqExpectedStages: List[str] = Field(default_factory=list)
|
||||
rnaseqReportAction: str = "generate"
|
||||
rnaseqReportUseModel: bool = True
|
||||
# RNA-seq QC 参数
|
||||
rnaseqDataMode: str = "upload"
|
||||
rnaseqDataPath: str = ""
|
||||
@@ -7794,6 +7798,10 @@ def run_stage_sync(task_id: str, stage: str, job_id: str, params: dict):
|
||||
wgcna_powers=params.get("wgcnaPowers", "1,2,3,4,5,6,7,8,9,10,12,14,16,18,20"),
|
||||
candidate_max_deg_proteins=params.get("candidateMaxDegProteins", 500),
|
||||
candidate_run_interproscan=params.get("candidateRunInterproscan", "auto"),
|
||||
rnaseq_scope_id=params.get("rnaseqScopeId", ""),
|
||||
rnaseq_expected_stages=params.get("rnaseqExpectedStages") or params.get("expectedStages") or [],
|
||||
rnaseq_report_action=params.get("rnaseqReportAction", "generate"),
|
||||
rnaseq_report_use_model=_bool_param("rnaseqReportUseModel", "report_use_model", default=True),
|
||||
# PPI参数
|
||||
ppi_species=params.get("ppiSpecies") or params.get("ppi_species") or "auto",
|
||||
normalization_species_label=params.get("normalizationSpeciesLabel") or params.get("normalization_species_label") or "",
|
||||
@@ -12093,6 +12101,10 @@ async def execute_stage(req: ExecuteRequest):
|
||||
"wgcnaPowers": getattr(req, 'wgcnaPowers', "1,2,3,4,5,6,7,8,9,10,12,14,16,18,20"),
|
||||
"candidateMaxDegProteins": getattr(req, 'candidateMaxDegProteins', 500),
|
||||
"candidateRunInterproscan": getattr(req, 'candidateRunInterproscan', "auto"),
|
||||
"rnaseqScopeId": getattr(req, 'rnaseqScopeId', ""),
|
||||
"rnaseqExpectedStages": getattr(req, 'rnaseqExpectedStages', []),
|
||||
"rnaseqReportAction": getattr(req, 'rnaseqReportAction', "generate"),
|
||||
"rnaseqReportUseModel": getattr(req, 'rnaseqReportUseModel', True),
|
||||
"ncbiQuery": getattr(req, 'ncbiQuery', ''),
|
||||
"ncbiGeneIds": getattr(req, 'ncbiGeneIds', ''),
|
||||
"ncbiMode": getattr(req, 'ncbiMode', 'gene'),
|
||||
@@ -13679,6 +13691,7 @@ async def get_results(
|
||||
"rnaseq_fgsea": {"images": [], "files": [], "elapsed_time": 0},
|
||||
"rnaseq_wgcna": {"images": [], "files": [], "elapsed_time": 0},
|
||||
"rnaseq_candidate_family": {"images": [], "files": [], "elapsed_time": 0},
|
||||
"rnaseq_report": {"images": [], "files": [], "elapsed_time": 0},
|
||||
"genome_prepare": {"images": [], "files": [], "elapsed_time": 0},
|
||||
"prepare_organize": {"images": [], "files": [], "elapsed_time": 0},
|
||||
"prepare_normalize_gff": {"images": [], "files": [], "elapsed_time": 0},
|
||||
@@ -13780,6 +13793,7 @@ async def get_results(
|
||||
"rnaseq_fgsea",
|
||||
"rnaseq_wgcna",
|
||||
"rnaseq_candidate_family",
|
||||
"rnaseq_report",
|
||||
"expression",
|
||||
}
|
||||
RNASEQ_FILE_TOKENS = (
|
||||
@@ -14801,6 +14815,7 @@ async def get_results(
|
||||
"genome_prepare": ["genome_prepare", "species_config"],
|
||||
"msa_region_plot": ["msa_region", "region.tsv", "conservation_profile"],
|
||||
"subfamily_confirm": ["subfamily_confirm", "confirmed_subfamily"],
|
||||
"rnaseq_report": ["rnaseq_report"],
|
||||
"rnaseq_fgsea": ["rnaseq_fgsea", "fgsea"],
|
||||
"rnaseq_go_enrich": ["rnaseq_go_enrich", ".go_enrich", "_go_"],
|
||||
"rnaseq_kegg_enrich": ["rnaseq_kegg_enrich", ".kegg_enrich", "_kegg_", "kegg_pathway", "kegg_pathview"],
|
||||
@@ -16029,6 +16044,7 @@ def parse_natural_language(message: str) -> dict:
|
||||
"rnaseq_align": ["rnaseq比对", "rna比对", "hisat2", "比对"],
|
||||
"rnaseq_quant": ["rnaseq定量", "rna定量", "stringtie", "定量"],
|
||||
"rnaseq_sample_qc": ["样本质量", "样本检测", "sample qc", "sample quality", "pca", "样本相关性"],
|
||||
"rnaseq_report": ["rnaseq结果报告", "rna-seq结果报告", "转录组结果报告", "rnaseq报告"],
|
||||
"rnaseq_wgcna": ["wgcna", "共表达", "加权共表达", "模块性状", "module trait"],
|
||||
"rnaseq_de": ["rnaseq差异", "rna差异", "差异表达", "de分析"],
|
||||
"rnaseq_fgsea": ["fgsea", "gsea", "基因集富集", "排序富集"],
|
||||
@@ -16838,7 +16854,10 @@ async def _run_agent_chat(
|
||||
)
|
||||
router_context_terms = dict(terms)
|
||||
|
||||
from gfa_core.mas.utils.intent_parser import parse_sequence_extract_request
|
||||
from gfa_core.mas.utils.intent_parser import (
|
||||
deterministic_agent_chat_intent,
|
||||
parse_sequence_extract_request,
|
||||
)
|
||||
|
||||
utility_params = parse_sequence_extract_request(request.message)
|
||||
if utility_params:
|
||||
@@ -16884,6 +16903,58 @@ async def _run_agent_chat(
|
||||
"session_id": session_id,
|
||||
}
|
||||
|
||||
deterministic_intent = deterministic_agent_chat_intent(
|
||||
request.message,
|
||||
rnaseq_context=any(
|
||||
str(stage).startswith("rnaseq_") for stage in completed_stages
|
||||
),
|
||||
)
|
||||
if deterministic_intent is not None:
|
||||
report_action = str(
|
||||
(deterministic_intent.get("params") or {}).get("rnaseqReportAction")
|
||||
or "generate"
|
||||
)
|
||||
report_message = (
|
||||
"已识别为打开 RNA-seq 结果报告。"
|
||||
if report_action == "open"
|
||||
else "已识别为生成 RNA-seq 结果报告。"
|
||||
)
|
||||
report_intent = {
|
||||
**deterministic_intent,
|
||||
"stage": "rnaseq_report",
|
||||
"primary_stage": "rnaseq_report",
|
||||
"taskType": "analysis",
|
||||
"task_kind": "analysis",
|
||||
"job_id": request.job_id,
|
||||
"workspace": (
|
||||
str(PROJECT_ROOT / "workspace" / 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=report_intent,
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"status": "ok",
|
||||
"action": "propose_analysis",
|
||||
"intent": report_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:
|
||||
@@ -20012,6 +20083,63 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
request.intent["params"] = dict(request.intent.get("params") or {})
|
||||
_apply_reconciled_species_to_intent(request.intent, plan_role_resolution)
|
||||
query_lower_for_context = (request.message or "").lower()
|
||||
result_report_query = (
|
||||
"结果报告" in query_lower_for_context
|
||||
and any(action in query_lower_for_context for action in ["生成", "打开"])
|
||||
)
|
||||
if result_report_query:
|
||||
from gfa_core.rnaseq_evidence import is_rnaseq_evidence_stage, manifest_identity_value
|
||||
|
||||
current_intent = dict(request.intent or {})
|
||||
current_params = dict(current_intent.get("params") or {})
|
||||
current_stages = [str(stage) for stage in (current_intent.get("stages") or [])]
|
||||
explicit_rnaseq_report = any(
|
||||
marker in query_lower_for_context
|
||||
for marker in ["rnaseq", "rna-seq", "rna seq", "转录组"]
|
||||
)
|
||||
rnaseq_context = any(is_rnaseq_evidence_stage(stage) for stage in current_stages)
|
||||
latest_rnaseq_manifest = None
|
||||
if request.job_id:
|
||||
report_final_dir = WORKSPACE_ROOT / request.job_id / "final"
|
||||
report_prefix = _nonempty_prefix(request.prefix or current_intent.get("prefix") or current_params.get("prefix"))
|
||||
report_pattern = f"{report_prefix}.*.manifest.json" if report_prefix else "*.manifest.json"
|
||||
candidates = []
|
||||
for candidate in report_final_dir.glob(report_pattern) if report_final_dir.exists() else []:
|
||||
try:
|
||||
payload = json.loads(candidate.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
continue
|
||||
if (
|
||||
is_rnaseq_evidence_stage(str(payload.get("stage") or ""))
|
||||
and str(payload.get("status") or "") in {"success", "partial_success"}
|
||||
):
|
||||
candidates.append((candidate.stat().st_mtime, candidate, payload))
|
||||
if candidates:
|
||||
_mtime, _path, latest_rnaseq_manifest = max(candidates, key=lambda item: item[0])
|
||||
rnaseq_context = True
|
||||
if explicit_rnaseq_report or rnaseq_context:
|
||||
current_params["rnaseqReportAction"] = "open" if "打开" in query_lower_for_context else "generate"
|
||||
if latest_rnaseq_manifest:
|
||||
current_params.setdefault(
|
||||
"rnaseqScopeId",
|
||||
manifest_identity_value(latest_rnaseq_manifest, "rnaseq_scope_id"),
|
||||
)
|
||||
report_prefix = _nonempty_prefix(
|
||||
request.prefix
|
||||
or current_intent.get("prefix")
|
||||
or current_params.get("prefix")
|
||||
or (latest_rnaseq_manifest or {}).get("prefix")
|
||||
)
|
||||
request.intent = {
|
||||
**current_intent,
|
||||
"stages": ["rnaseq_report"],
|
||||
"primary_stage": "rnaseq_report",
|
||||
"task_type": "report",
|
||||
"task_kind": "pipeline",
|
||||
"prefix": report_prefix,
|
||||
"params": {**current_params, "prefix": report_prefix},
|
||||
"confidence": 0.99,
|
||||
}
|
||||
subfamily_confirm_query_for_context = any(
|
||||
kw in query_lower_for_context
|
||||
for kw in ["确认亚家族", "亚家族确认", "确认亚家族标签", "人工确认亚家族", "subfamily confirm", "confirmed subfamily"]
|
||||
@@ -20907,7 +21035,10 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
"manifest_path": str(status.manifest_path) if status.manifest_path else "",
|
||||
"missing_count": len(status.missing_artifacts or []) + len(status.errors or []),
|
||||
}
|
||||
if status.completed:
|
||||
# Report requests are actions, not a request to skip an
|
||||
# already completed computation. The CLI decides whether
|
||||
# an "open" request can reuse the fresh snapshot.
|
||||
if status.completed and stage_id != "rnaseq_report":
|
||||
completed_rnaseq_stages.append(stage_id)
|
||||
else:
|
||||
remaining_rnaseq_stages.append(stage_id)
|
||||
@@ -20923,6 +21054,7 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
"rnaseq_fgsea": "RNA-seq fgsea",
|
||||
"rnaseq_wgcna": "RNA-seq WGCNA",
|
||||
"rnaseq_candidate_family": "RNA-seq Candidate Family",
|
||||
"rnaseq_report": "RNA-seq Result Report",
|
||||
}
|
||||
plan_prefix = effective_plan_prefix or request.prefix or incoming_intent.get("prefix", "")
|
||||
final_dir_for_rnaseq = PROJECT_ROOT / "workspace" / request.job_id / "final"
|
||||
@@ -20942,6 +21074,7 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
"rnaseq_fgsea": ["rnaseq_de"],
|
||||
"rnaseq_wgcna": ["rnaseq_sample_qc"],
|
||||
"rnaseq_candidate_family": ["rnaseq_de", "rnaseq_go_enrich", "rnaseq_kegg_enrich", "rnaseq_fgsea"],
|
||||
"rnaseq_report": [],
|
||||
}
|
||||
if (
|
||||
"rnaseq_fgsea" in remaining_rnaseq_stages
|
||||
@@ -20965,6 +21098,7 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
"rnaseq_fgsea": 10,
|
||||
"rnaseq_wgcna": 20,
|
||||
"rnaseq_candidate_family": 10,
|
||||
"rnaseq_report": 2,
|
||||
}
|
||||
stages_detail = []
|
||||
for order, stage_id in enumerate(remaining_rnaseq_stages, start=1):
|
||||
|
||||
Reference in New Issue
Block a user