fix: preserve gene-family stage intent and completed results
This commit is contained in:
@@ -23500,16 +23500,20 @@ compare: [[flesh, peel]]`;
|
||||
let stagesDetail = (rawExecutableDetails || []).filter(stage => !completedSet.has(stage.id || stage.name));
|
||||
|
||||
if (stages.length === 0) {
|
||||
const plannedStageIds = (plan.stages || [])
|
||||
const plannedStageIds = (
|
||||
(plan.all_stages || []).length > 0
|
||||
? plan.all_stages
|
||||
: (plan.stages || [])
|
||||
)
|
||||
.map(stage => typeof stage === 'object' ? stage.id : stage)
|
||||
.filter(Boolean);
|
||||
const completedOnlyPlan = plannedStageIds.length === 0
|
||||
&& completedSet.size > 0
|
||||
&& ['所有阶段已完成', 'All stages completed'].includes(String(plan.description || ''));
|
||||
const requestedCompletedStage = (plannedStageIds.length > 0
|
||||
? plannedStageIds.filter(stageId => completedSet.has(stageId))
|
||||
: Array.from(completedSet)
|
||||
).pop() || '';
|
||||
const requestedCompletedStage = plannedStageIds.length === 1
|
||||
&& completedSet.has(plannedStageIds[0])
|
||||
? plannedStageIds[0]
|
||||
: '';
|
||||
const allPlannedCompleted = (plannedStageIds.length > 0 && plannedStageIds.every(stageId => completedSet.has(stageId)))
|
||||
|| completedOnlyPlan;
|
||||
if (allPlannedCompleted && requestedCompletedStage) {
|
||||
@@ -23520,6 +23524,11 @@ compare: [[flesh, peel]]`;
|
||||
taskType: 'analysis_completed',
|
||||
plan: plan
|
||||
});
|
||||
} else if (allPlannedCompleted) {
|
||||
addAgentMessage('assistant', t('当前请求中的分析阶段均已有同一分析范围的有效结果,无需重复执行。'), {
|
||||
taskType: 'analysis_completed',
|
||||
plan: plan
|
||||
});
|
||||
} else {
|
||||
addAgentMessage('assistant', t('未生成可执行阶段。未检测到当前 scope 的完成结果时,不再把空计划解释为已完成;请检查阶段名称或前置依赖。'), {
|
||||
taskType: 'analysis_plan_empty',
|
||||
|
||||
+171
-9
@@ -2682,12 +2682,26 @@ def _normalize_full_gene_family_workflow_plan(
|
||||
stages_detail: list[Dict[str, Any]],
|
||||
plan_params: Dict[str, Any],
|
||||
plan_prefix: str,
|
||||
authorized_stage_ids: Optional[list[str]] = None,
|
||||
) -> tuple[list[str], list[Dict[str, Any]]]:
|
||||
if not _looks_like_gene_family_workflow_query(message or ""):
|
||||
return stage_ids, stages_detail
|
||||
|
||||
desired = list(FULL_GENE_FAMILY_WORKFLOW_STAGES)
|
||||
if "expression" in stage_ids or any(token in (message or "").lower() for token in ["表达", "expression", "rna"]):
|
||||
authorized = None if not authorized_stage_ids else {
|
||||
str(stage_id).strip()
|
||||
for stage_id in authorized_stage_ids
|
||||
if str(stage_id).strip()
|
||||
}
|
||||
desired = [
|
||||
stage_id
|
||||
for stage_id in FULL_GENE_FAMILY_WORKFLOW_STAGES
|
||||
if authorized is None or stage_id in authorized
|
||||
]
|
||||
include_expression = (
|
||||
"expression" in stage_ids
|
||||
or any(token in (message or "").lower() for token in ["表达", "expression", "rna"])
|
||||
)
|
||||
if include_expression and (authorized is None or "expression" in authorized):
|
||||
desired.append("expression")
|
||||
|
||||
shared_refs = _param_list(plan_params.get("referenceSpecies") or plan_params.get("reference_species") or plan_params.get("refSpeciesList"))
|
||||
@@ -2699,7 +2713,7 @@ def _normalize_full_gene_family_workflow_plan(
|
||||
plan_params["scopeMode"] = "target_plus_comparative"
|
||||
|
||||
ordered: list[str] = []
|
||||
if "identify" in stage_ids:
|
||||
if "identify" in stage_ids and (authorized is None or "identify" in authorized):
|
||||
ordered.append("identify")
|
||||
for sid in desired:
|
||||
if sid not in ordered:
|
||||
@@ -2707,6 +2721,8 @@ def _normalize_full_gene_family_workflow_plan(
|
||||
for sid in stage_ids:
|
||||
if sid == "ortholog":
|
||||
continue
|
||||
if authorized is not None and sid not in authorized:
|
||||
continue
|
||||
if sid and sid not in ordered:
|
||||
ordered.append(sid)
|
||||
|
||||
@@ -3681,13 +3697,38 @@ def _identity_completed_stages_for_plan(
|
||||
workspace_root=WORKSPACE_ROOT,
|
||||
)
|
||||
status = _stage_status_for_plan_identity(job_id, stage_id, identity, strict_identity=True)
|
||||
workflow_phase = str(
|
||||
merged_status_params.get("workflowPhase")
|
||||
or merged_status_params.get("workflow_phase")
|
||||
or ""
|
||||
).strip()
|
||||
requires_confirmed = bool(
|
||||
merged_status_params.get("requiresConfirmedSubfamily")
|
||||
or merged_status_params.get("requires_confirmed_subfamily")
|
||||
)
|
||||
contextual_kaks_shared_reference = (
|
||||
stage_id == "kaks"
|
||||
and workflow_phase == "post_subfamily_confirm"
|
||||
and requires_confirmed
|
||||
)
|
||||
technical_reference_keys = (
|
||||
"referenceSpecies",
|
||||
"refSpeciesList",
|
||||
"ppiReferenceSpecies",
|
||||
"referenceBundleSpecies",
|
||||
"ppiKaksReferenceSpecies",
|
||||
"reference_species",
|
||||
(
|
||||
"ppiReferenceSpecies",
|
||||
"referenceBundleSpecies",
|
||||
"ppiKaksReferenceSpecies",
|
||||
"ppi_reference_species",
|
||||
"reference_bundle_species",
|
||||
"ppi_kaks_reference_species",
|
||||
)
|
||||
if contextual_kaks_shared_reference
|
||||
else (
|
||||
"referenceSpecies",
|
||||
"refSpeciesList",
|
||||
"ppiReferenceSpecies",
|
||||
"referenceBundleSpecies",
|
||||
"ppiKaksReferenceSpecies",
|
||||
"reference_species",
|
||||
)
|
||||
)
|
||||
explicit_technical_identity = any(
|
||||
_param_list(merged_status_params.get(key)) for key in technical_reference_keys
|
||||
@@ -3774,6 +3815,114 @@ def _identity_completed_stages_for_plan(
|
||||
strict_identity=True,
|
||||
)
|
||||
break
|
||||
if (
|
||||
status.completed is not True
|
||||
and stage_id in set(FULL_GENE_FAMILY_WORKFLOW_STAGES) - {"ppi", "kaks"}
|
||||
and workflow_phase == "post_subfamily_confirm"
|
||||
and requires_confirmed
|
||||
):
|
||||
requested_family = str(
|
||||
merged_status_params.get("geneFamily")
|
||||
or merged_status_params.get("gene_family")
|
||||
or (str(detail_prefix).split(".")[-1] if "." in str(detail_prefix) else "")
|
||||
).strip()
|
||||
requested_targets = _param_list(
|
||||
merged_status_params.get("targetSpecies")
|
||||
or merged_status_params.get("target_species")
|
||||
)
|
||||
expected_route = str(
|
||||
merged_status_params.get("analysisRoute")
|
||||
or merged_status_params.get("analysis_route")
|
||||
or ""
|
||||
).strip()
|
||||
plan_references = _param_list(
|
||||
(plan_params or {}).get("referenceSpecies")
|
||||
or (plan_params or {}).get("reference_species")
|
||||
)
|
||||
final_dir = WORKSPACE_ROOT / job_id / "final"
|
||||
latest_confirmation_mtime = 0.0
|
||||
for confirmation_path in final_dir.glob("*.subfamily_confirm.manifest.json") if final_dir.is_dir() else []:
|
||||
try:
|
||||
confirmation = json.loads(confirmation_path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
continue
|
||||
if (
|
||||
confirmation.get("status") == "success"
|
||||
and str(confirmation.get("prefix") or "").strip() == str(detail_prefix)
|
||||
):
|
||||
latest_confirmation_mtime = max(latest_confirmation_mtime, confirmation_path.stat().st_mtime)
|
||||
candidates = sorted(
|
||||
final_dir.glob(f"*.{stage_id}.manifest.json") if final_dir.is_dir() else [],
|
||||
key=lambda path: path.stat().st_mtime,
|
||||
reverse=True,
|
||||
)
|
||||
for manifest_path in candidates:
|
||||
try:
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
continue
|
||||
stage_context = manifest.get("stage_context") if isinstance(manifest.get("stage_context"), dict) else {}
|
||||
parameters = manifest.get("parameters") if isinstance(manifest.get("parameters"), dict) else {}
|
||||
|
||||
def regular_manifest_value(*keys: str):
|
||||
for source in (manifest, stage_context, parameters):
|
||||
for key in keys:
|
||||
if source.get(key) not in (None, "", []):
|
||||
return source.get(key)
|
||||
return ""
|
||||
|
||||
manifest_prefix = str(manifest.get("prefix") or manifest.get("output_prefix") or "").strip()
|
||||
manifest_family = str(
|
||||
regular_manifest_value("family", "gene_family")
|
||||
or (manifest_prefix.split(".")[-1] if "." in manifest_prefix else "")
|
||||
).strip()
|
||||
manifest_targets = _param_list(regular_manifest_value("target_species", "targetSpecies"))
|
||||
manifest_references = _param_list(regular_manifest_value("reference_species", "referenceSpecies"))
|
||||
manifest_route = str(regular_manifest_value("analysis_route", "analysisRoute")).strip()
|
||||
if (
|
||||
manifest.get("stage") != stage_id
|
||||
or manifest.get("status") != "success"
|
||||
or manifest_prefix != str(detail_prefix)
|
||||
or manifest_family != requested_family
|
||||
or set(manifest_targets) != set(requested_targets)
|
||||
or manifest_route != expected_route
|
||||
or (
|
||||
manifest_references
|
||||
and set(manifest_references) != set(plan_references)
|
||||
)
|
||||
or (
|
||||
latest_confirmation_mtime
|
||||
and manifest_path.stat().st_mtime < latest_confirmation_mtime
|
||||
)
|
||||
):
|
||||
continue
|
||||
inherited_params = {
|
||||
**merged_status_params,
|
||||
"prefix": detail_prefix,
|
||||
"targetSpecies": manifest_targets,
|
||||
"referenceSpecies": manifest_references,
|
||||
"scopeMode": regular_manifest_value("scope_mode", "scopeMode"),
|
||||
"analysisRoute": manifest_route,
|
||||
"memberSetId": regular_manifest_value("member_set_id", "memberSetId"),
|
||||
}
|
||||
inherited_params.pop("scopeId", None)
|
||||
inherited_params.pop("scope_id", None)
|
||||
inherited_identity = resolve_stage_identity(
|
||||
stage=stage_id,
|
||||
prefix=detail_prefix,
|
||||
params=inherited_params,
|
||||
job_id=job_id,
|
||||
workspace_root=WORKSPACE_ROOT,
|
||||
)
|
||||
inherited_status = _stage_status_for_plan_identity(
|
||||
job_id,
|
||||
stage_id,
|
||||
inherited_identity,
|
||||
strict_identity=True,
|
||||
)
|
||||
if inherited_status.completed is True:
|
||||
status = inherited_status
|
||||
break
|
||||
if status.completed is True:
|
||||
if stage_id == "combo":
|
||||
required_panels = _combo_panels_from_params(merged_status_params)
|
||||
@@ -19096,6 +19245,7 @@ def _build_contextual_gene_family_plan_response(request: Any, session_id: str, c
|
||||
stages_detail=stages_detail,
|
||||
plan_params=plan_params,
|
||||
plan_prefix=plan_prefix,
|
||||
authorized_stage_ids=list(request_intent.get("stages") or requested_stage_ids),
|
||||
)
|
||||
|
||||
completed_stages: list[str] = []
|
||||
@@ -21292,6 +21442,11 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
"description": "执行完整 GWAS 分析链",
|
||||
"confidence": 0.98,
|
||||
})
|
||||
authoritative_intent_stage_ids = [
|
||||
str(stage_id).strip()
|
||||
for stage_id in (incoming_intent.get("stages") or [])
|
||||
if str(stage_id).strip()
|
||||
]
|
||||
incoming_params = dict(incoming_intent.get("params") or {})
|
||||
def _request_list(*keys):
|
||||
for key in keys:
|
||||
@@ -22202,6 +22357,11 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
if result.get("status") == "completed":
|
||||
plan = result.get("final_output", {}).get("analysis_plan", {})
|
||||
intent = result.get("final_output", {}).get("intent", {})
|
||||
plan_authorized_stage_ids = authoritative_intent_stage_ids or [
|
||||
str(stage_id).strip()
|
||||
for stage_id in (intent.get("stages") or [])
|
||||
if str(stage_id).strip()
|
||||
]
|
||||
if authoritative_full_gwas_stages:
|
||||
gwas_prefix = (
|
||||
intent.get("prefix")
|
||||
@@ -22907,6 +23067,7 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
stages_detail=stages_detail,
|
||||
plan_params=plan_params,
|
||||
plan_prefix=plan_prefix or effective_plan_prefix or request.prefix or "",
|
||||
authorized_stage_ids=plan_authorized_stage_ids,
|
||||
)
|
||||
raw_stages = stage_ids
|
||||
plan["stages"] = stage_ids
|
||||
@@ -22939,6 +23100,7 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
stages_detail=stages_detail,
|
||||
plan_params=plan_params,
|
||||
plan_prefix=plan_prefix or effective_plan_prefix or request.prefix or "",
|
||||
authorized_stage_ids=plan_authorized_stage_ids,
|
||||
)
|
||||
raw_stages = stage_ids
|
||||
plan["stages"] = stage_ids
|
||||
|
||||
@@ -593,6 +593,7 @@ window.__I18N_EN__ = {
|
||||
" 亚家族分类已完成预测,但确认表尚未保存;后续会继续显示为待确认。": " subfamily classification predicted; confirmation table not yet saved; will show as pending.",
|
||||
" 到后端;系统会先生成 ": " to backend; system will generate ",
|
||||
" 前端连接短暂中断,正在按后端产物状态恢复,不会立即判定阶段失败": " frontend connection interrupted; recovering from backend artifact state. Stage will not be marked as failed.",
|
||||
"当前请求中的分析阶段均已有同一分析范围的有效结果,无需重复执行。": "All analysis stages in the current request already have valid results for the same analysis scope. No need to re-execute.",
|
||||
" 已有同一分析范围的有效结果,无需重复执行。": " already has valid results for the same analysis scope. No need to re-execute.",
|
||||
" 的依赖尚未完成": " dependency not yet completed",
|
||||
" 重新提交(自动注释背景)...": " re-submitting (auto-annotation background)...",
|
||||
|
||||
Reference in New Issue
Block a user