feat:sync-report-workflows-and-migration-runtime
This commit is contained in:
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate or open a formal RNA-seq evidence report."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).absolute().parents[2]
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from gfa_core.rnaseq_report import NoSuccessfulRNASeqEvidence, generate_rnaseq_report
|
||||
|
||||
|
||||
def _csv(value: str) -> list[str] | None:
|
||||
values = [item.strip() for item in str(value or "").split(",") if item.strip()]
|
||||
return values or None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate a deterministic formal RNA-seq result report from successful manifests."
|
||||
)
|
||||
parser.add_argument("--workspace", required=True, help="RNA-seq job workspace containing final/")
|
||||
parser.add_argument("--prefix", required=True, help="RNA-seq output prefix")
|
||||
parser.add_argument("--job-id", default="")
|
||||
parser.add_argument("--rnaseq-scope-id", default="")
|
||||
parser.add_argument(
|
||||
"--expected-stages",
|
||||
default="",
|
||||
help="Comma-separated stages selected for the current RNA-seq batch",
|
||||
)
|
||||
parser.add_argument("--action", choices=("generate", "open"), default="generate")
|
||||
parser.add_argument("--no-model", action="store_true", help="Disable optional LLM language enhancement")
|
||||
parser.add_argument("--log-file", default="", help="Accepted for pipeline runner compatibility")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
result = generate_rnaseq_report(
|
||||
Path(args.workspace),
|
||||
args.prefix,
|
||||
rnaseq_scope_id=args.rnaseq_scope_id,
|
||||
expected_stages=_csv(args.expected_stages),
|
||||
use_model=not args.no_model,
|
||||
action=args.action,
|
||||
job_id=args.job_id,
|
||||
)
|
||||
except NoSuccessfulRNASeqEvidence as exc:
|
||||
print(str(exc), file=sys.stderr)
|
||||
return 2
|
||||
except Exception as exc:
|
||||
print(f"RNA-seq 结果报告生成失败:{type(exc).__name__}: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
reuse = "(复用最新报告)" if result.reused else ""
|
||||
print(
|
||||
f"RNA-seq 结果报告已就绪{reuse}: {result.report_path} "
|
||||
f"[coverage={result.coverage_status}, stages={','.join(result.included_stages)}]"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -3355,6 +3355,7 @@ RNA_SEQ_STAGES = {
|
||||
"rnaseq_wgcna",
|
||||
"rnaseq_candidate_family",
|
||||
"rnaseq_family_score",
|
||||
"rnaseq_report",
|
||||
}
|
||||
|
||||
|
||||
@@ -8383,7 +8384,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:
|
||||
@@ -8392,6 +8418,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 = [
|
||||
@@ -11407,6 +11435,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"],
|
||||
|
||||
+133
-11
@@ -1243,9 +1243,12 @@
|
||||
<el-config-provider :locale="elLocale">
|
||||
<!-- Header -->
|
||||
<div class="header" style="display:flex; align-items:center; justify-content:space-between;">
|
||||
<div style="display: flex; align-items: baseline; gap: 12px; margin-left: 24px;">
|
||||
<h1 style="margin: 0; font-size: 28px; font-weight: 700;">CropAI</h1>
|
||||
<span style="font-size: 15px; opacity: 0.85; font-weight: 400;">Evidence-grounded multi-agent artificial intelligence for crop gene-family discovery and transcriptomic decision support</span>
|
||||
<div style="display: flex; align-items: center; gap: 12px; margin-left: 24px;">
|
||||
<img src="/static/logo.png?v=20260809" alt="OMaaX" style="height: 60px; margin-right: 8px; margin-top: -10px;">
|
||||
<div style="margin-top: -5px;">
|
||||
<h1 style="margin: 0; font-size: 28px; font-weight: 700; display: inline;">CropAI</h1>
|
||||
<span style="font-size: 15px; opacity: 0.85; font-weight: 400; margin-left: 25px;">Multi-Agent Platform for crop Bioinformatics Analysis</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-select v-model="currentLang" @change="switchLang" size="small" style="width:110px;margin-right:20px;">
|
||||
<el-option :label="t('中文')" value="zh"></el-option>
|
||||
@@ -1282,6 +1285,7 @@
|
||||
<el-option label="KaKs - KaKs Analysis" value="kaks"></el-option>
|
||||
<el-option label="Expression (使用FPKM) - Differential Expression" value="expression"></el-option>
|
||||
<el-option label="Combo - Combined Plot(Tree+Motif+Structure+Domain)" value="combo"></el-option>
|
||||
<el-option label="Gene Family Result Report - 基因家族结果报告" value="gene_family_report"></el-option>
|
||||
<el-option label="Run All - Complete Pipeline" value="all"></el-option>
|
||||
<el-option label="GWAS Format - TASSEL VCF → HapMap" value="gwas_fmt"></el-option>
|
||||
<!-- RNA-seq拆分选项 -->
|
||||
@@ -1297,6 +1301,7 @@
|
||||
<el-option label="RNA-Seq fgsea - 排序富集分析" value="rnaseq_fgsea"></el-option>
|
||||
<el-option label="RNA-Seq WGCNA - 加权共表达分析" value="rnaseq_wgcna"></el-option>
|
||||
<el-option label="RNA-Seq Candidate Family - 候选家族筛选" value="rnaseq_candidate_family"></el-option>
|
||||
<el-option label="RNA-Seq Result Report - 结果报告" value="rnaseq_report"></el-option>
|
||||
</el-select>
|
||||
|
||||
<el-button
|
||||
@@ -1787,7 +1792,7 @@
|
||||
</div>
|
||||
<!-- lightWeightExtensionTest:start -->
|
||||
<div id="lightweight-extension-test-drop-zone"
|
||||
style="display:none;margin-top:8px;border:1px dashed #409EFF;padding:10px;font-size:12px;color:#606266;background:#f8fbff;"
|
||||
style="display:block;margin-top:8px;border:1px dashed #409EFF;padding:10px;font-size:12px;color:#606266;background:#f8fbff;"
|
||||
@dragover.prevent
|
||||
@drop.prevent="handleLightweightExtensionFolderDrop($event)">
|
||||
<div style="font-weight:600;color:#303133;">拖入固定结构拓展包</div>
|
||||
@@ -8778,6 +8783,7 @@ RNA-seq KEGG/fgsea 会优先使用物种级 KEGG gene set;没有物种级缓
|
||||
{ stage: 'tfbs', label: 'tfbs', group: 'gene_family', default_cpu: 4, max_cpu: 4 },
|
||||
{ stage: 'ortholog', label: 'ortholog', group: 'gene_family', default_cpu: 4, max_cpu: 4 },
|
||||
{ stage: 'kaks', label: 'kaks', group: 'gene_family', default_cpu: 4, max_cpu: 4 },
|
||||
{ stage: 'gene_family_report', label: 'report', group: 'gene_family', default_cpu: 4, max_cpu: 4 },
|
||||
{ stage: 'rnaseq_qc', label: 'qc', group: 'rnaseq', default_cpu: 8, max_cpu: 20 },
|
||||
{ stage: 'rnaseq_align', label: 'align', group: 'rnaseq', default_cpu: 8, max_cpu: 20 },
|
||||
{ stage: 'rnaseq_quant', label: 'quant', group: 'rnaseq', default_cpu: 6, max_cpu: 8 },
|
||||
@@ -8785,7 +8791,8 @@ RNA-seq KEGG/fgsea 会优先使用物种级 KEGG gene set;没有物种级缓
|
||||
{ stage: 'rnaseq_enrich', label: 'enrich', group: 'rnaseq', default_cpu: 4, max_cpu: 4 },
|
||||
{ stage: 'rnaseq_go_enrich', label: 'go enrich', group: 'rnaseq', default_cpu: 4, max_cpu: 4 },
|
||||
{ stage: 'rnaseq_kegg_enrich', label: 'kegg enrich', group: 'rnaseq', default_cpu: 4, max_cpu: 4 },
|
||||
{ stage: 'rnaseq_candidate_family', label: 'candidate', group: 'rnaseq', default_cpu: 4, max_cpu: 4 }
|
||||
{ stage: 'rnaseq_candidate_family', label: 'candidate', group: 'rnaseq', default_cpu: 4, max_cpu: 4 },
|
||||
{ stage: 'rnaseq_report', label: 'report', group: 'rnaseq', default_cpu: 4, max_cpu: 4 }
|
||||
]);
|
||||
let runtimeMonitorTimer = null;
|
||||
let resourceMonitorTimer = null;
|
||||
@@ -9076,6 +9083,22 @@ RNA-seq KEGG/fgsea 会优先使用物种级 KEGG gene set;没有物种级缓
|
||||
requestBody.candidateMaxDegProteins = numberParam(firstDefinedParam(src.candidateMaxDegProteins, src.max_deg_proteins, src.maxDegProteins, confirmParams.candidateMaxDegProteins), 500);
|
||||
requestBody.candidateRunInterproscan = firstDefinedParam(src.candidateRunInterproscan, src.run_interproscan, src.runInterproscan, confirmParams.candidateRunInterproscan) || 'auto';
|
||||
}
|
||||
if (stageName === 'gene_family_report') {
|
||||
requestBody.expectedStages = src.expectedStages || [];
|
||||
requestBody.stageStatuses = src.stageStatuses || {};
|
||||
requestBody.reportAction = src.reportAction || 'generate';
|
||||
requestBody.reportUseModel = src.reportUseModel !== false;
|
||||
requestBody.scopeId = src.scopeId || '';
|
||||
requestBody.analysisRoute = src.analysisRoute || '';
|
||||
requestBody.memberSetId = src.memberSetId || '';
|
||||
requestBody.rnaseqScopeId = src.rnaseqScopeId || '';
|
||||
}
|
||||
if (stageName === 'rnaseq_report') {
|
||||
requestBody.rnaseqScopeId = firstDefinedParam(src.rnaseqScopeId, src.rnaseq_scope_id, activeRnaseqScopeId.value) || '';
|
||||
requestBody.rnaseqExpectedStages = rnaseqListParam(firstDefinedParam(src.rnaseqExpectedStages, src.expected_stages), []);
|
||||
requestBody.rnaseqReportAction = firstDefinedParam(src.rnaseqReportAction, src.report_action) || 'generate';
|
||||
requestBody.rnaseqReportUseModel = booleanParam(firstDefinedParam(src.rnaseqReportUseModel, src.report_use_model), true);
|
||||
}
|
||||
return requestBody;
|
||||
};
|
||||
const isRnaseqQueue = (name) => {
|
||||
@@ -9832,7 +9855,9 @@ compare: [[flesh, peel]]`;
|
||||
// batch it may be the stage that generates gene2ko, so wait
|
||||
// for it to avoid submitting fgsea before the background exists.
|
||||
'rnaseq_fgsea': ['rnaseq_de', 'rnaseq_kegg_enrich'],
|
||||
'rnaseq_candidate_family': ['rnaseq_de', 'rnaseq_go_enrich', 'rnaseq_kegg_enrich', 'rnaseq_fgsea']
|
||||
'rnaseq_candidate_family': ['rnaseq_de', 'rnaseq_go_enrich', 'rnaseq_kegg_enrich', 'rnaseq_fgsea'],
|
||||
'gene_family_report': [],
|
||||
'rnaseq_report': []
|
||||
};
|
||||
|
||||
// 阶段执行状态跟踪
|
||||
@@ -10185,6 +10210,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: '📄 Gene Family Result Report', 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 },
|
||||
@@ -10196,7 +10222,8 @@ compare: [[flesh, peel]]`;
|
||||
{ name: 'rnaseq_kegg_enrich', label: '2️⃣2️⃣ RNA-seq KEGG Enrich', status: 'pending', images: [], files: [], elapsed_time: 0 },
|
||||
{ name: 'rnaseq_fgsea', label: '2️⃣3️⃣ RNA-seq fgsea', status: 'pending', images: [], files: [], elapsed_time: 0 },
|
||||
{ name: 'rnaseq_wgcna', label: '2️⃣4️⃣ RNA-seq WGCNA', status: 'pending', images: [], files: [], elapsed_time: 0 },
|
||||
{ name: 'rnaseq_candidate_family', label: '2️⃣5️⃣ RNA-seq Candidate Family', status: 'pending', images: [], files: [], elapsed_time: 0 }
|
||||
{ name: 'rnaseq_candidate_family', label: '2️⃣5️⃣ RNA-seq Candidate Family', status: 'pending', images: [], files: [], elapsed_time: 0 },
|
||||
{ name: 'rnaseq_report', label: '2️⃣6️⃣ RNA-seq Result Report', status: 'pending', images: [], files: [], elapsed_time: 0 }
|
||||
]);
|
||||
// 新版结果区域 - 展开的阶段
|
||||
const activeStages = ref('phylogeny');
|
||||
@@ -10205,7 +10232,7 @@ compare: [[flesh, peel]]`;
|
||||
const rnaseqResultStageNames = new Set([
|
||||
'rnaseq_qc', 'rnaseq_align', 'rnaseq_quant', 'rnaseq_sample_qc',
|
||||
'rnaseq_de', 'annotation', 'rnaseq_go_enrich', 'rnaseq_kegg_enrich',
|
||||
'rnaseq_fgsea', 'rnaseq_wgcna', 'rnaseq_candidate_family'
|
||||
'rnaseq_fgsea', 'rnaseq_wgcna', 'rnaseq_candidate_family', 'rnaseq_report'
|
||||
]);
|
||||
const wgsPopulationGwasStageNames = new Set([
|
||||
'wgs_qc', 'wgs_index', 'wgs_align', 'wgs_markdup', 'wgs_bam_qc',
|
||||
@@ -10653,12 +10680,15 @@ compare: [[flesh, peel]]`;
|
||||
const isJumpFile = (filename, stageName = '') => {
|
||||
const lower = String(filename || '').toLowerCase();
|
||||
if (lower.endsWith('.ppi_url.txt')) return true;
|
||||
if (stageName === 'rnaseq_report' && lower.endsWith('.rnaseq_report.html')) return true;
|
||||
if (stageName === 'gene_family_report' && lower.endsWith('.gene_family_report.html')) return true;
|
||||
return stageName === 'rnaseq_qc' && lower.endsWith('_multiqc_report.html');
|
||||
};
|
||||
|
||||
const isReportOrManifestFile = (filename) => {
|
||||
const lower = String(filename || '').toLowerCase();
|
||||
return lower.endsWith('.manifest.json') || lower.endsWith('_report.md') || lower.endsWith('.report.md') || lower.endsWith('report.md');
|
||||
return lower.endsWith('.manifest.json') || lower.endsWith('_report.md') || lower.endsWith('.report.md') || lower.endsWith('report.md')
|
||||
|| (lower.endsWith('.html') && (lower.includes('rnaseq_report') || lower.includes('gene_family_report')));
|
||||
};
|
||||
|
||||
const resultDisplayFiles = (stage) => {
|
||||
@@ -10904,6 +10934,12 @@ compare: [[flesh, peel]]`;
|
||||
selectedResultStage.value = stageName;
|
||||
selectedFile.value = file.name;
|
||||
|
||||
const selectedLowerName = String(file.name || '').toLowerCase();
|
||||
if (['rnaseq_report', 'gene_family_report'].includes(stageName) && selectedLowerName.endsWith('.html')) {
|
||||
window.open(file.path, '_blank');
|
||||
return;
|
||||
}
|
||||
|
||||
if (stageName === 'gwas_fmt' && ['sorted_vcf', 'tassel_hapmap'].includes(String(file.role || ''))) {
|
||||
window.open(file.path, '_blank');
|
||||
return;
|
||||
@@ -15027,6 +15063,87 @@ compare: [[flesh, peel]]`;
|
||||
await startBatchExecute();
|
||||
};
|
||||
|
||||
|
||||
const autoGenerateGeneFamilyReportAfterBatch = async (batchStages = []) => {
|
||||
const stageIds = batchStages.map(stage => getStageId(stage)).filter(Boolean);
|
||||
if (stageIds.includes('gene_family_report')) return;
|
||||
const geneFamilyStages = new Set([
|
||||
'identify', 'phylogeny', 'reference_classification', 'motif', 'domain', 'structure',
|
||||
'promoter', 'tfbs', 'synteny', 'karyotype', 'interspecies_synteny', 'ppi', 'kaks',
|
||||
'pep', 'expression', 'combo'
|
||||
]);
|
||||
const expectedStages = [...new Set(stageIds.filter(stageId => geneFamilyStages.has(stageId)))];
|
||||
if (expectedStages.length === 0) return;
|
||||
const hasSuccessfulEvidence = expectedStages.some(
|
||||
stageId => stageExecutionStatus.value[stageId] === 'completed'
|
||||
);
|
||||
if (!hasSuccessfulEvidence) {
|
||||
addAgentMessage('assistant', t('暂无可生成的基因家族结果:本批次没有成功阶段。'));
|
||||
return;
|
||||
}
|
||||
const stageStatuses = Object.fromEntries(expectedStages.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 || {};
|
||||
try {
|
||||
stageExecutionStatus.value.gene_family_report = 'running';
|
||||
await executeSingleStageWithParams('gene_family_report', {
|
||||
expectedStages,
|
||||
stageStatuses,
|
||||
reportAction: 'generate',
|
||||
reportUseModel: true,
|
||||
scopeId: batchParams.scopeId || planParams.scopeId || '',
|
||||
analysisRoute: batchParams.analysisRoute || planParams.analysisRoute || '',
|
||||
memberSetId: batchParams.memberSetId || planParams.memberSetId || '',
|
||||
rnaseqScopeId: batchParams.rnaseqScopeId || planParams.rnaseqScopeId || ''
|
||||
});
|
||||
stageExecutionStatus.value.gene_family_report = 'completed';
|
||||
setResultStageFilter('gene_family_report');
|
||||
await refreshResults(jobInfo.job_id, 'gene_family_report');
|
||||
addAgentMessage('assistant', t('已根据本批次成功的基因家族阶段自动生成结果报告。'));
|
||||
} catch (error) {
|
||||
stageExecutionStatus.value.gene_family_report = 'failed';
|
||||
addAgentMessage('assistant', `${t('基因家族批次已结束,但结果报告生成失败:')}${error?.message || error}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const autoGenerateRnaseqReportAfterBatch = async (batchStages = []) => {
|
||||
const stageIds = batchStages.map(stage => getStageId(stage)).filter(Boolean);
|
||||
if (stageIds.includes('rnaseq_report')) return;
|
||||
const expectedStages = [...new Set(stageIds.filter(stageId =>
|
||||
(String(stageId).startsWith('rnaseq_') || ['annotation', 'annotation_go', 'annotation_kegg'].includes(stageId))
|
||||
&& stageId !== 'rnaseq_report'
|
||||
))];
|
||||
if (expectedStages.length === 0) return;
|
||||
const hasSuccessfulEvidence = expectedStages.some(
|
||||
stageId => stageExecutionStatus.value[stageId] === 'completed'
|
||||
);
|
||||
if (!hasSuccessfulEvidence) {
|
||||
addAgentMessage('assistant', t('暂无可生成的 RNA-seq 结果:本批次没有成功阶段。'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
stageExecutionStatus.value.rnaseq_report = 'running';
|
||||
await executeSingleStageWithParams('rnaseq_report', {
|
||||
prefix: executionPrefix(expectedStages[0]),
|
||||
rnaseqScopeId: activeRnaseqScopeId.value || '',
|
||||
rnaseqExpectedStages: expectedStages,
|
||||
rnaseqReportAction: 'generate',
|
||||
rnaseqReportUseModel: true
|
||||
});
|
||||
stageExecutionStatus.value.rnaseq_report = 'completed';
|
||||
setResultStageFilter('rnaseq_report');
|
||||
await refreshResults(jobInfo.job_id, 'rnaseq_report');
|
||||
addAgentMessage('assistant', t('已根据本批次成功的 RNA-seq 阶段自动生成结果报告。'));
|
||||
} catch (error) {
|
||||
stageExecutionStatus.value.rnaseq_report = 'failed';
|
||||
addAgentMessage('assistant', `${t('RNA-seq 批次已结束,但结果报告生成失败:')}${error?.message || error}`);
|
||||
}
|
||||
};
|
||||
|
||||
const startBatchExecute = async () => {
|
||||
if (isBatchExecuting.value) return;
|
||||
if (!batchExecutionUserConfirmed.value) return;
|
||||
@@ -15076,10 +15193,10 @@ compare: [[flesh, peel]]`;
|
||||
const allStages = ['genome_prepare',
|
||||
'identify', 'identify_refine', 'subfamily_confirm', 'phylogeny', 'reference_classification', 'motif', 'domain', 'structure', 'promoter', 'tfbs',
|
||||
'synteny', 'karyotype', 'interspecies_synteny', 'ppi', 'kaks', 'pep',
|
||||
'expression', 'rnaseq_qc', 'rnaseq_align', 'rnaseq_quant', 'rnaseq_sample_qc',
|
||||
'expression', 'gene_family_report', 'rnaseq_qc', 'rnaseq_align', 'rnaseq_quant', 'rnaseq_sample_qc',
|
||||
'rnaseq_de', 'annotation',
|
||||
'rnaseq_go_enrich', 'rnaseq_kegg_enrich', 'rnaseq_fgsea', 'rnaseq_wgcna',
|
||||
'rnaseq_candidate_family'];
|
||||
'rnaseq_candidate_family', 'rnaseq_report'];
|
||||
const jobId = jobInfo.job_id;
|
||||
|
||||
for (const stage of allStages) {
|
||||
@@ -15136,6 +15253,11 @@ compare: [[flesh, peel]]`;
|
||||
}).length;
|
||||
const totalCount = stages.length;
|
||||
|
||||
if (pausedCount === 0) {
|
||||
await autoGenerateGeneFamilyReportAfterBatch(stages);
|
||||
await autoGenerateRnaseqReportAfterBatch(stages);
|
||||
}
|
||||
|
||||
if (failedCount > 0) {
|
||||
addAgentMessage('assistant', t('⚠️ 批量执行完成:成功 ') + completedCount + '/' + totalCount + t(',失败 ') + failedCount + '/' + totalCount);
|
||||
} else if (pausedCount > 0) {
|
||||
|
||||
+80
-5
@@ -4676,7 +4676,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]:
|
||||
@@ -4686,7 +4686,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}")
|
||||
@@ -8568,7 +8572,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",
|
||||
@@ -17132,7 +17136,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(
|
||||
@@ -25246,7 +25321,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),
|
||||
|
||||
@@ -714,4 +714,3 @@ window.__I18N_EN__ = {
|
||||
" 个分析阶段": " analysis stages",
|
||||
" 个阶段": " stages"
|
||||
};
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ env BBAV5_QUEUE_PREFIX=plantai_dev_7908 \
|
||||
BBAV5_REDIS_URL=redis://localhost:6379/9 \
|
||||
BBAV5_CELERY_LOG_DIR=logs/celery_plantai_dev_7908 \
|
||||
BBAV5_WORKER_NAME_PREFIX=plantai_dev_7908 \
|
||||
./start_celery_7880.sh
|
||||
./start_celery_9000.sh
|
||||
|
||||
env BBAV5_QUEUE_PREFIX=plantai_dev_7908 \
|
||||
BBAV5_REDIS_URL=redis://localhost:6379/9 \
|
||||
|
||||
@@ -700,11 +700,14 @@ def build_report_data(
|
||||
),
|
||||
]
|
||||
sections.extend(stage_sections)
|
||||
# The integrated conclusion has a fixed evidence contract. Do not take the
|
||||
# first arbitrary observations because plan ordering can displace one of
|
||||
# the three core facts requested for delivery.
|
||||
integrated_observations = [
|
||||
section.observations[0]
|
||||
for section in stage_sections
|
||||
if section.observations
|
||||
][:3]
|
||||
stage_sections_by_name[stage].observations[0]
|
||||
for stage in ("identify", "phylogeny", "reference_classification")
|
||||
if stage in stage_sections_by_name and stage_sections_by_name[stage].observations
|
||||
]
|
||||
sections.extend(
|
||||
[
|
||||
ReportSection(
|
||||
|
||||
@@ -52,6 +52,7 @@ STAGE_SCRIPT_MAP = {
|
||||
"annotation_kegg": "run_annotation.py",
|
||||
"rnaseq_wgcna": "run_rnaseq_wgcna.py",
|
||||
"rnaseq_candidate_family": "run_rnaseq_candidate_family.py",
|
||||
"rnaseq_report": "run_rnaseq_report.py",
|
||||
"rnaseq_sample_qc": "run_rnaseq_sample_qc.py",
|
||||
"genome_prepare": "run_genome_prepare.py",
|
||||
"genome_prepare_v2": "run_genome_prepare.py",
|
||||
@@ -341,6 +342,11 @@ DIAGNOSIS_STAGE_COVERAGE = {
|
||||
"postflight": ["candidate_family_ranking", "evidence_matrix", "recommendation", "rag_queries", "rag_references"],
|
||||
"failure": ["annotation_missing", "id_mapping"],
|
||||
},
|
||||
"rnaseq_report": {
|
||||
"preflight": ["successful_rnaseq_manifest", "prefix_scope_identity"],
|
||||
"postflight": ["report_markdown", "evidence_snapshot", "coverage_status"],
|
||||
"failure": ["no_successful_rnaseq_evidence", "scope_mismatch", "artifact_missing"],
|
||||
},
|
||||
"annotation": {
|
||||
"preflight": ["protein_background", "tool_availability"],
|
||||
"postflight": ["gene2go", "gene2ko", "annotation_coverage"],
|
||||
|
||||
@@ -74,6 +74,7 @@ class IntentAgentV2(BaseAgent):
|
||||
"rnaseq_de": "RNA-seq差异表达分析 - 从原始FASTQ数据开始,进行质控→比对→定量→差异表达分析(DESeq2)",
|
||||
"rnaseq_enrich": "RNA-seq富集分析 - GO/KEGG通路富集分析(RNA-seq流程最后一步)",
|
||||
"rnaseq_candidate_family": "RNA-seq候选家族筛选 - 整合DEG、InterPro/Pfam和GO/KEGG富集,输出候选基因家族排名",
|
||||
"rnaseq_report": "RNA-seq结果报告 - 从当前成功阶段生成可追溯的正式Markdown报告",
|
||||
"enrichment": "富集分析 - 基于基因家族鉴定结果进行GO/KEGG通路富集分析",
|
||||
"ncbi_fetch_sequences": "NCBI序列下载 - 根据GeneID、基因名或基因list下载protein/CDS序列"
|
||||
}
|
||||
@@ -440,6 +441,22 @@ class IntentAgentV2(BaseAgent):
|
||||
has_rnaseq_marker = any(kw in query_lower for kw in ["rnaseq", "rna-seq", "rna seq", "转录组"])
|
||||
explicit_gene_family_context = any(kw in query_lower for kw in ["基因家族", "家族表达", "表达量", "热图", "heatmap"])
|
||||
|
||||
if has_rnaseq_marker and any(
|
||||
keyword in query_lower
|
||||
for keyword in ["生成结果报告", "打开结果报告", "结果报告", "生成报告", "打开报告"]
|
||||
):
|
||||
action = "open" if "打开" in query_lower else "generate"
|
||||
return {
|
||||
"gene_family": "",
|
||||
"species": "",
|
||||
"stages": ["rnaseq_report"],
|
||||
"primary_stage": "rnaseq_report",
|
||||
"description": "打开或生成当前RNA-seq正式结果报告",
|
||||
"requirements": ["至少一个成功的RNA-seq阶段manifest"],
|
||||
"params": {"rnaseqReportAction": action},
|
||||
"confidence": 0.99,
|
||||
}
|
||||
|
||||
stage_force_rules = [
|
||||
("rnaseq_candidate_family", ["候选家族", "候选基因家族", "家族排名", "tf注释", "tf family", "tffamily", "pfam注释", "deg注释", "差异基因注释"], "执行RNA-seq候选家族筛选"),
|
||||
("rnaseq_fgsea", ["fgsea", "gsea", "排序富集", "基因集富集"], "执行RNA-seq fgsea排序富集分析"),
|
||||
|
||||
@@ -377,6 +377,7 @@ class PlanningAgentV2(BaseAgent):
|
||||
"rnaseq_fgsea": [],
|
||||
"rnaseq_wgcna": [],
|
||||
"rnaseq_candidate_family": [],
|
||||
"rnaseq_report": [],
|
||||
}
|
||||
|
||||
# 各阶段依赖 (来自PreFlight)
|
||||
|
||||
@@ -20,6 +20,7 @@ ANALYSIS_KEYWORDS = {
|
||||
"rnaseq_kegg_enrich": ["kegg富集", "kegg分析", "kegg", "pathway"],
|
||||
"rnaseq_fgsea": ["fgsea", "gsea", "排序富集", "基因集富集"],
|
||||
"rnaseq_candidate_family": ["候选家族", "候选基因家族", "家族排名", "tf注释", "tf family", "tffamily", "pfam注释", "deg注释", "差异基因注释"],
|
||||
"rnaseq_report": ["rnaseq结果报告", "rna-seq结果报告", "转录组结果报告", "rnaseq报告", "rna-seq报告"],
|
||||
"rnaseq_enrich": ["富集分析", "功能富集"],
|
||||
"identify": ["鉴定", "identify", "识别", "找出", "鉴定基因家族"],
|
||||
"identify_refine": ["修订成员", "修订鉴定成员", "修改成员", "删除基因", "删除", "去除基因", "去掉基因", "移除基因", "补充基因", "遗漏基因", "重建蛋白", "人工修订id", "curated members", "refine identify"],
|
||||
@@ -61,6 +62,7 @@ RNASEQ_STAGES = {
|
||||
"rnaseq_kegg_enrich",
|
||||
"rnaseq_fgsea",
|
||||
"rnaseq_candidate_family",
|
||||
"rnaseq_report",
|
||||
"rnaseq_enrich",
|
||||
}
|
||||
|
||||
@@ -179,6 +181,21 @@ def fast_intent_recognition(query: str) -> Optional[Dict[str, Any]]:
|
||||
except Exception:
|
||||
pass
|
||||
has_rnaseq_marker = any(marker in query_lower for marker in RNASEQ_MARKERS)
|
||||
if has_rnaseq_marker and any(
|
||||
keyword in query_lower
|
||||
for keyword in ["生成结果报告", "打开结果报告", "结果报告", "生成报告", "打开报告"]
|
||||
):
|
||||
action = "open" if "打开" in query_lower else "generate"
|
||||
return {
|
||||
"stage": "rnaseq_report",
|
||||
"stages": ["rnaseq_report"],
|
||||
"gene_family": "",
|
||||
"species": "",
|
||||
"prefix": "",
|
||||
"params": {"rnaseqReportAction": action},
|
||||
"confidence": 0.99,
|
||||
"method": "rnaseq_report_deterministic",
|
||||
}
|
||||
header_clean_context = any(kw in query_lower for kw in [
|
||||
"id处理", "处理id", "修订id", "简化id", "清理id", "id简化",
|
||||
"去掉注释", "去除注释", "注释信息", "长注释",
|
||||
|
||||
@@ -793,6 +793,26 @@ CAPABILITIES: Dict[str, StageCapability] = {
|
||||
repair_candidates=["gff_id_overlap_report", "motif_database_fallback_to_jaspar", "artifact_reresolve"],
|
||||
rerun_policy="manual",
|
||||
),
|
||||
"rnaseq_report": StageCapability(
|
||||
stage="rnaseq_report",
|
||||
cli="apps/cli/run_rnaseq_report.py",
|
||||
env="test4py311",
|
||||
category="rnaseq",
|
||||
consumes=[
|
||||
ArtifactNeed("successful_rnaseq_manifests", "manifest", alternatives=["final_manifest_scan"]),
|
||||
],
|
||||
provides=[
|
||||
ArtifactProduct("rnaseq_report", "markdown", "{prefix}.rnaseq_report.md"),
|
||||
ArtifactProduct("rnaseq_report_evidence", "json", "{prefix}.rnaseq_report.evidence.json"),
|
||||
ArtifactProduct("rnaseq_report_model", "json", "{prefix}.rnaseq_report.model.json"),
|
||||
ArtifactProduct("rnaseq_report_manifest", "manifest", "{prefix}.rnaseq_report.manifest.json"),
|
||||
],
|
||||
scope_modes=["rnaseq_scope_id"],
|
||||
preflight_checks=["at_least_one_successful_rnaseq_manifest", "prefix_scope_identity_consistent"],
|
||||
postflight_checks=["deterministic_markdown_exists", "evidence_snapshot_exists", "coverage_recorded"],
|
||||
repair_candidates=["manifest_recovery", "regenerate_from_current_snapshot"],
|
||||
rerun_policy="manual_or_after_selected_batch_terminal",
|
||||
),
|
||||
"summary": StageCapability(
|
||||
stage="summary",
|
||||
cli="apps/cli/run_summary.py",
|
||||
|
||||
@@ -90,6 +90,7 @@ PIPELINE_STAGES = {
|
||||
"rnaseq_go_enrich",
|
||||
"rnaseq_kegg_enrich",
|
||||
"rnaseq_candidate_family",
|
||||
"rnaseq_report",
|
||||
}
|
||||
|
||||
STAGE_DEPENDENCIES = {
|
||||
@@ -173,6 +174,9 @@ STAGE_DEPENDENCIES = {
|
||||
"rnaseq_go_enrich": ["rnaseq_de"],
|
||||
"rnaseq_kegg_enrich": ["rnaseq_de"],
|
||||
"rnaseq_candidate_family": ["rnaseq_de", "rnaseq_go_enrich", "rnaseq_kegg_enrich", "rnaseq_fgsea"],
|
||||
# A report is a snapshot of whatever RNA-seq evidence is already successful.
|
||||
# Static dependencies would incorrectly prevent intentional mid-pipeline reports.
|
||||
"rnaseq_report": [],
|
||||
}
|
||||
|
||||
PARALLEL_GROUPS = [
|
||||
@@ -271,6 +275,7 @@ STAGE_RESOURCE_PROFILES = {
|
||||
"rnaseq_kegg_enrich": {"queue": "rnaseq", "resource_class": "rnaseq", "default_cpu": 4, "max_cpu": 4},
|
||||
"rnaseq_fgsea": {"queue": "rnaseq", "resource_class": "rnaseq", "default_cpu": 4, "max_cpu": 4},
|
||||
"rnaseq_candidate_family": {"queue": "rnaseq", "resource_class": "rnaseq", "default_cpu": 4, "max_cpu": 4},
|
||||
"rnaseq_report": {"queue": "light", "resource_class": "light", "default_cpu": 4, "max_cpu": 4},
|
||||
}
|
||||
|
||||
DEFAULT_STAGE_RESOURCE_PROFILE = {
|
||||
@@ -391,6 +396,7 @@ STAGE_ORDER = [
|
||||
"rnaseq_kegg_enrich",
|
||||
"rnaseq_fgsea",
|
||||
"rnaseq_candidate_family",
|
||||
"rnaseq_report",
|
||||
"rnaseq_enrich",
|
||||
"gene_family_report",
|
||||
]
|
||||
@@ -469,6 +475,7 @@ STAGE_INFO = {
|
||||
"rnaseq_enrich": {"name": "RNA-seq富集分析", "estimated_time": 15, "cpu": 4},
|
||||
"rnaseq_fgsea": {"name": "RNA-seq fgsea分析", "estimated_time": 15, "cpu": 4},
|
||||
"rnaseq_candidate_family": {"name": "RNA-seq候选家族筛选", "estimated_time": 20, "cpu": 4},
|
||||
"rnaseq_report": {"name": "RNA-seq结果报告", "estimated_time": 2, "cpu": 4},
|
||||
}
|
||||
|
||||
EXECUTABLE_STAGES = {
|
||||
@@ -544,6 +551,7 @@ EXECUTABLE_STAGES = {
|
||||
"rnaseq_enrich",
|
||||
"rnaseq_fgsea",
|
||||
"rnaseq_candidate_family",
|
||||
"rnaseq_report",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ RNASEQ_STATUS_STAGES = {
|
||||
"rnaseq_fgsea",
|
||||
"rnaseq_wgcna",
|
||||
"rnaseq_candidate_family",
|
||||
"rnaseq_report",
|
||||
"annotation",
|
||||
"annotation_go",
|
||||
"annotation_kegg",
|
||||
|
||||
@@ -1516,6 +1516,19 @@ class StageToolRegistry:
|
||||
"timeout": 7200,
|
||||
"conda_env": "rnaseq"
|
||||
},
|
||||
# 24. Formal RNA-seq Result Report
|
||||
{
|
||||
"id": "rnaseq_report",
|
||||
"name": "RNA-seq Result Report",
|
||||
"script": "run_rnaseq_report.py",
|
||||
"category": "rnaseq",
|
||||
"description": "Generate a scoped formal Markdown report from successful RNA-seq manifests",
|
||||
"required_inputs": [],
|
||||
"outputs": ["rnaseq_report", "rnaseq_report_evidence", "rnaseq_report_model"],
|
||||
"parameters": {"cpu": 4},
|
||||
"timeout": 1800,
|
||||
"conda_env": "test4py311"
|
||||
},
|
||||
# Combo Plot - 组合图
|
||||
{
|
||||
"id": "combo",
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
"""Lightweight, provider-free RNA-seq manifest evidence snapshot.
|
||||
|
||||
The live interpretation view and the formal report share this module so scope
|
||||
selection and manifest provenance have one implementation. It intentionally
|
||||
does not import the MAS agent package.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable
|
||||
|
||||
|
||||
REPORT_STAGE = "rnaseq_report"
|
||||
SUCCESS_STATUSES = {"success", "partial_success"}
|
||||
GLOBAL_LEGACY_STAGES = {
|
||||
"rnaseq_qc",
|
||||
"rnaseq_align",
|
||||
"rnaseq_quant",
|
||||
"annotation",
|
||||
"annotation_go",
|
||||
"annotation_kegg",
|
||||
}
|
||||
RNASEQ_STAGE_NAMES = {
|
||||
"rnaseq_qc": "RNA-seq质控",
|
||||
"rnaseq_align": "RNA-seq比对",
|
||||
"rnaseq_quant": "RNA-seq定量",
|
||||
"rnaseq_sample_qc": "RNA-seq样本质量评估",
|
||||
"rnaseq_de": "RNA-seq差异表达",
|
||||
"annotation": "背景功能注释",
|
||||
"annotation_go": "GO背景自动注释",
|
||||
"annotation_kegg": "KEGG/KO背景自动注释",
|
||||
"rnaseq_go_enrich": "RNA-seq GO富集分析",
|
||||
"rnaseq_kegg_enrich": "RNA-seq KEGG富集分析",
|
||||
"rnaseq_enrich": "RNA-seq富集分析(兼容入口)",
|
||||
"rnaseq_fgsea": "RNA-seq fgsea排序富集",
|
||||
"rnaseq_wgcna": "RNA-seq WGCNA共表达分析",
|
||||
"rnaseq_candidate_family": "RNA-seq候选家族筛选",
|
||||
"rnaseq_family_score": "RNA-seq候选家族评分",
|
||||
}
|
||||
RNASEQ_STAGE_ORDER = list(RNASEQ_STAGE_NAMES)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RNASeqManifestSelection:
|
||||
rnaseq_scope_id: str
|
||||
discovered_scope_ids: tuple[str, ...]
|
||||
manifests: dict[str, dict[str, Any]]
|
||||
|
||||
|
||||
def read_json(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
return data if isinstance(data, dict) else {}
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return {}
|
||||
|
||||
|
||||
def manifest_identity_value(manifest: dict[str, Any], key: str) -> str:
|
||||
context = manifest.get("stage_context") or manifest.get("stageContext") or {}
|
||||
params = manifest.get("parameters") or {}
|
||||
return str(manifest.get(key) or params.get(key) or context.get(key) or "").strip()
|
||||
|
||||
|
||||
def is_rnaseq_evidence_stage(stage: str) -> bool:
|
||||
stage = str(stage or "").strip()
|
||||
return stage != REPORT_STAGE and (
|
||||
stage.startswith("rnaseq_") or stage in {"annotation", "annotation_go", "annotation_kegg"}
|
||||
)
|
||||
|
||||
|
||||
def stage_sort_key(stage: str) -> tuple[int, str]:
|
||||
try:
|
||||
return RNASEQ_STAGE_ORDER.index(stage), stage
|
||||
except ValueError:
|
||||
return len(RNASEQ_STAGE_ORDER), stage
|
||||
|
||||
|
||||
def _manifest_score(path: Path, manifest: dict[str, Any]) -> tuple[int, int, int, float]:
|
||||
status = str(manifest.get("status") or "")
|
||||
status_rank = 2 if status == "success" else 1 if status == "partial_success" else 0
|
||||
metrics = manifest.get("metrics") if isinstance(manifest.get("metrics"), dict) else {}
|
||||
artifacts = manifest.get("artifacts") if isinstance(manifest.get("artifacts"), dict) else {}
|
||||
try:
|
||||
mtime = path.stat().st_mtime
|
||||
except OSError:
|
||||
mtime = 0.0
|
||||
return status_rank, len(metrics), len(artifacts), mtime
|
||||
|
||||
|
||||
def select_rnaseq_manifests(
|
||||
final_dir: Path,
|
||||
prefix: str,
|
||||
*,
|
||||
rnaseq_scope_id: str = "",
|
||||
) -> RNASeqManifestSelection:
|
||||
"""Select one non-mixing manifest per RNA-seq stage for a prefix/scope."""
|
||||
final_dir = Path(final_dir)
|
||||
pattern = f"{prefix}.*.manifest.json" if prefix else "*.manifest.json"
|
||||
candidates: list[tuple[Path, dict[str, Any]]] = []
|
||||
latest_by_scope: dict[str, float] = {}
|
||||
for path in sorted(final_dir.glob(pattern)) if final_dir.exists() else []:
|
||||
manifest = read_json(path)
|
||||
stage = str(manifest.get("stage") or "")
|
||||
if not is_rnaseq_evidence_stage(stage):
|
||||
continue
|
||||
manifest_prefix = str(manifest.get("prefix") or "").strip()
|
||||
if prefix and manifest_prefix and manifest_prefix != prefix:
|
||||
continue
|
||||
candidates.append((path, manifest))
|
||||
scope = manifest_identity_value(manifest, "rnaseq_scope_id")
|
||||
if scope:
|
||||
try:
|
||||
latest_by_scope[scope] = max(latest_by_scope.get(scope, 0.0), path.stat().st_mtime)
|
||||
except OSError:
|
||||
latest_by_scope.setdefault(scope, 0.0)
|
||||
|
||||
discovered = sorted(latest_by_scope, key=lambda item: (-latest_by_scope[item], item))
|
||||
resolved_scope = str(rnaseq_scope_id or "").strip() or (discovered[0] if discovered else "")
|
||||
selected_paths: dict[str, Path] = {}
|
||||
selected: dict[str, dict[str, Any]] = {}
|
||||
for path, manifest in candidates:
|
||||
stage = str(manifest.get("stage") or "")
|
||||
scope = manifest_identity_value(manifest, "rnaseq_scope_id")
|
||||
if resolved_scope and scope and scope != resolved_scope:
|
||||
continue
|
||||
if resolved_scope and not scope and stage not in GLOBAL_LEGACY_STAGES:
|
||||
continue
|
||||
existing = selected.get(stage)
|
||||
if existing is not None and _manifest_score(selected_paths[stage], existing) >= _manifest_score(path, manifest):
|
||||
continue
|
||||
manifest = dict(manifest)
|
||||
manifest["_manifest_path"] = str(path)
|
||||
selected[stage] = manifest
|
||||
selected_paths[stage] = path
|
||||
return RNASeqManifestSelection(resolved_scope, tuple(discovered), selected)
|
||||
|
||||
|
||||
def _compact_value(value: Any, limit: int = 240) -> str:
|
||||
if isinstance(value, (dict, list)):
|
||||
text = json.dumps(value, ensure_ascii=False, sort_keys=True)
|
||||
else:
|
||||
text = str(value)
|
||||
return text if len(text) <= limit else text[: limit - 1] + "…"
|
||||
|
||||
|
||||
def resolve_artifact_path(
|
||||
final_dir: Path,
|
||||
item: dict[str, Any],
|
||||
*,
|
||||
require_exists: bool = False,
|
||||
) -> Path | None:
|
||||
"""Resolve artifacts after a workspace has been moved or mounted elsewhere."""
|
||||
final_dir = Path(final_dir)
|
||||
workspace = final_dir.parent
|
||||
candidates: list[Path] = []
|
||||
for raw in (item.get("full_path"), item.get("path")):
|
||||
if not raw:
|
||||
continue
|
||||
path = Path(str(raw))
|
||||
candidates.append(path if path.is_absolute() else workspace / path)
|
||||
if path.is_absolute():
|
||||
candidates.append(final_dir / path.name)
|
||||
|
||||
unique: list[Path] = []
|
||||
seen: set[str] = set()
|
||||
for path in candidates:
|
||||
identity = str(path)
|
||||
if identity not in seen:
|
||||
seen.add(identity)
|
||||
unique.append(path)
|
||||
for path in unique:
|
||||
if path.exists():
|
||||
return path
|
||||
if require_exists:
|
||||
return None
|
||||
return unique[0] if unique else None
|
||||
|
||||
|
||||
def build_rnaseq_evidence_snapshot(
|
||||
final_dir: Path,
|
||||
prefix: str,
|
||||
*,
|
||||
rnaseq_scope_id: str = "",
|
||||
include_stages: Iterable[str] | None = None,
|
||||
selection: RNASeqManifestSelection | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build a compact evidence object without invoking an LLM or agent cache."""
|
||||
selection = selection or select_rnaseq_manifests(
|
||||
final_dir, prefix, rnaseq_scope_id=rnaseq_scope_id
|
||||
)
|
||||
allowed = {str(stage) for stage in include_stages or [] if str(stage).strip()}
|
||||
manifests = {
|
||||
stage: manifest
|
||||
for stage, manifest in selection.manifests.items()
|
||||
if not allowed or stage in allowed
|
||||
}
|
||||
stages = sorted(manifests, key=stage_sort_key)
|
||||
stage_flow: list[dict[str, Any]] = []
|
||||
findings: list[dict[str, Any]] = []
|
||||
reviews: list[dict[str, Any]] = []
|
||||
cards: list[dict[str, Any]] = []
|
||||
risks: list[dict[str, Any]] = []
|
||||
card_index = 0
|
||||
for stage in stages:
|
||||
manifest = manifests[stage]
|
||||
status = str(manifest.get("status") or "unknown")
|
||||
metrics = manifest.get("metrics") if isinstance(manifest.get("metrics"), dict) else {}
|
||||
parameters = manifest.get("parameters") if isinstance(manifest.get("parameters"), dict) else {}
|
||||
artifacts = manifest.get("artifacts") if isinstance(manifest.get("artifacts"), dict) else {}
|
||||
metric_items = list(sorted(metrics.items()))[:12]
|
||||
metric_summary = ", ".join(f"{key}={_compact_value(value, 80)}" for key, value in metric_items[:5])
|
||||
key_result = metric_summary or f"登记产物 {len(artifacts)} 个"
|
||||
stage_flow.append(
|
||||
{
|
||||
"id": stage,
|
||||
"name": RNASEQ_STAGE_NAMES.get(stage, stage),
|
||||
"status": status,
|
||||
"key_result": key_result,
|
||||
}
|
||||
)
|
||||
findings.append(
|
||||
{
|
||||
"stage": stage,
|
||||
"title": RNASEQ_STAGE_NAMES.get(stage, stage),
|
||||
"description": f"manifest 状态为 {status};{key_result}。",
|
||||
}
|
||||
)
|
||||
limitations: list[str] = []
|
||||
if status == "partial_success":
|
||||
limitations.append("该阶段为 partial_success,结果可用但不完整。")
|
||||
elif status != "success":
|
||||
limitations.append("该阶段未成功,不纳入正式结论。")
|
||||
missing_artifacts: list[str] = []
|
||||
for name, item in sorted(artifacts.items()):
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
path = resolve_artifact_path(final_dir, item)
|
||||
exists = bool(path and path.exists())
|
||||
card_index += 1
|
||||
cards.append(
|
||||
{
|
||||
"id": f"RNA-E{card_index:04d}",
|
||||
"stage": stage,
|
||||
"claim": f"产物 {name}",
|
||||
"evidence": f"path={path or ''}, exists={exists}",
|
||||
"source_type": "raw_file" if exists else "manifest",
|
||||
}
|
||||
)
|
||||
if not exists:
|
||||
missing_artifacts.append(str(name))
|
||||
for key, value in metric_items:
|
||||
card_index += 1
|
||||
cards.append(
|
||||
{
|
||||
"id": f"RNA-E{card_index:04d}",
|
||||
"stage": stage,
|
||||
"claim": f"指标 {key}",
|
||||
"evidence": _compact_value(value),
|
||||
"source_type": "manifest",
|
||||
}
|
||||
)
|
||||
safe_parameter_items = [
|
||||
(key, value)
|
||||
for key, value in sorted(parameters.items())
|
||||
if not any(secret in str(key).lower() for secret in ("api_key", "token", "secret", "password"))
|
||||
][:10]
|
||||
for key, value in safe_parameter_items:
|
||||
card_index += 1
|
||||
cards.append(
|
||||
{
|
||||
"id": f"RNA-E{card_index:04d}",
|
||||
"stage": stage,
|
||||
"claim": f"参数 {key}",
|
||||
"evidence": _compact_value(value),
|
||||
"source_type": "manifest",
|
||||
}
|
||||
)
|
||||
if missing_artifacts:
|
||||
shown = missing_artifacts[:12]
|
||||
remainder = len(missing_artifacts) - len(shown)
|
||||
suffix = f"(另有 {remainder} 个,详见 evidence JSON)" if remainder else ""
|
||||
message = f"manifest 登记但文件不存在的产物:{', '.join(shown)}{suffix}"
|
||||
limitations.append(message)
|
||||
risks.append({"stage": stage, "severity": "high", "message": message})
|
||||
if status not in SUCCESS_STATUSES:
|
||||
risks.append({"stage": stage, "severity": "high", "message": f"阶段状态为 {status},不得作为成功证据。"})
|
||||
reviews.append({"stage": stage, "stage_name": RNASEQ_STAGE_NAMES.get(stage, stage), "limitations": limitations})
|
||||
|
||||
successful = [stage for stage in stages if str(manifests[stage].get("status") or "") in SUCCESS_STATUSES]
|
||||
return {
|
||||
"schema": "rnaseq_manifest_evidence_v1",
|
||||
"metadata": {
|
||||
"prefix": prefix,
|
||||
"rnaseq_scope_id": selection.rnaseq_scope_id,
|
||||
"discovered_scope_ids": list(selection.discovered_scope_ids),
|
||||
"included_stages": stages,
|
||||
"successful_stages": successful,
|
||||
"interpretation_boundary": "仅陈述当前 prefix 与 RNA-seq scope 下 manifest/产物支持的事实;富集、相关性和候选排序不得写成调控因果。",
|
||||
},
|
||||
"stage_flow": {"visualization": " -> ".join(stages), "stages": stage_flow},
|
||||
"key_findings": findings,
|
||||
"stage_reviews": reviews,
|
||||
"evidence_cards": cards,
|
||||
"risk_flags": risks,
|
||||
"major_claims": [],
|
||||
"biological_story": (
|
||||
f"当前报告汇总了 {len(successful)} 个成功或部分成功的 RNA-seq 阶段。"
|
||||
"跨阶段生物学解释必须以各阶段数值、图表和风险标记为边界。"
|
||||
),
|
||||
"next_steps": ["补齐缺失或失败阶段后重新生成报告,以刷新证据快照。"],
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import mimetypes
|
||||
import os
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable
|
||||
from urllib.parse import unquote
|
||||
@@ -79,8 +79,21 @@ class RNASeqReportResult:
|
||||
reused: bool = False
|
||||
|
||||
|
||||
BEIJING_TIMEZONE = timezone(timedelta(hours=8))
|
||||
|
||||
|
||||
def _now() -> str:
|
||||
return datetime.now().astimezone().isoformat(timespec="seconds")
|
||||
return datetime.now(BEIJING_TIMEZONE).isoformat(timespec="seconds")
|
||||
|
||||
|
||||
def _display_generated_at(value: str) -> str:
|
||||
try:
|
||||
parsed = datetime.fromisoformat(value)
|
||||
if parsed.tzinfo is None:
|
||||
parsed = parsed.replace(tzinfo=BEIJING_TIMEZONE)
|
||||
return parsed.astimezone(BEIJING_TIMEZONE).strftime("%Y-%m-%d %H:%M:%S")
|
||||
except (TypeError, ValueError):
|
||||
return str(value or "").replace("T", " ")
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
@@ -1224,7 +1237,7 @@ def _render_deterministic_markdown(
|
||||
lines = [
|
||||
f"# {prefix} RNA-seq 结果解读报告",
|
||||
"",
|
||||
f"生成时间:{generated_at} ",
|
||||
f"生成时间(北京时间):{_display_generated_at(generated_at)} ",
|
||||
f"任务编号:`{workspace.name}`",
|
||||
"",
|
||||
"**任务结果路径:** ",
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# RNA-seq Result Report Contract v1
|
||||
|
||||
## Stage
|
||||
|
||||
- Stage id: `rnaseq_report`
|
||||
- Script: `apps/cli/run_rnaseq_report.py`
|
||||
- Scope: one exact `prefix + rnaseq_scope_id` evidence snapshot
|
||||
- Purpose: materialize a formal, reproducible RNA-seq Markdown result report without depending on frontend session state.
|
||||
|
||||
## Inputs
|
||||
|
||||
- At least one RNA-seq/annotation manifest with status `success` or `partial_success` under `final/`.
|
||||
- All selected evidence must match the requested prefix and RNA-seq scope. Legacy unscoped QC/alignment/quantification/annotation manifests may be treated as experiment-global evidence.
|
||||
- `expected_stages` is optional and records the stages selected for the current batch. It controls coverage only; it must not become a static execution dependency.
|
||||
- An existing configured `LLMManager` provider is optional. No provider or model name is required by this stage.
|
||||
|
||||
If no successful RNA-seq evidence exists, the stage must return `暂无可生成的 RNA-seq 结果` and must not create an empty report.
|
||||
|
||||
## Required Outputs
|
||||
|
||||
- `final/{prefix}.rnaseq_report.md`
|
||||
- `final/{prefix}.rnaseq_report.evidence.json`
|
||||
- `final/{prefix}.rnaseq_report.model.json`
|
||||
- `final/{prefix}.rnaseq_report.manifest.json`
|
||||
|
||||
The manifest stage status is `success` whenever these outputs are generated. Content completeness is recorded separately as `metrics.coverage_status = complete|partial`.
|
||||
|
||||
## Generation Semantics
|
||||
|
||||
- `generate`: always rebuild from the current manifest snapshot.
|
||||
- `open`: reuse a fresh report; regenerate first when the source/request fingerprint changed.
|
||||
- The deterministic Markdown body is always complete enough to read without an API.
|
||||
- Optional model output is a labeled language-enhancement section only. Model failure must retain the deterministic report and be recorded in `.model.json`.
|
||||
- `rnaseq_report` manifests are excluded from their own evidence scan.
|
||||
|
||||
## Boundary With Deep Interpretation
|
||||
|
||||
Both features may call the pure `gfa_core.rnaseq_evidence` snapshot builder. The formal report must not call `/api/agent/interpret`, read its session cache, or replace the frontend deep-interpretation view.
|
||||
|
||||
## Validation
|
||||
|
||||
- Every manifest artifact path exists and its digest is recorded.
|
||||
- Evidence contains only the selected prefix/scope.
|
||||
- `coverage_status=complete` only when every expected stage has status `success`.
|
||||
- Missing, failed, or `partial_success` expected stages yield `coverage_status=partial` while the report manifest remains `success`.
|
||||
@@ -33,10 +33,6 @@
|
||||
|
||||
各阶段依次写明分析对象、源结果中的直接观察、谨慎解释、限制,并仅在有助复核时列出少量相对路径。
|
||||
|
||||
### 2.2 语言边界
|
||||
|
||||
预测关系统一表述为线索;没有逐成员表或独立证据时,不指定候选基因,不声称功能改变或调控关系已经证实。
|
||||
|
||||
<!-- GFA_SECTION common.completeness -->
|
||||
## 三、结果总览与完整性检查
|
||||
|
||||
|
||||
+58
-19
@@ -2,15 +2,19 @@
|
||||
# PlantAI-dev 9000 服务管理脚本
|
||||
# 用法: bash manage_plantai.sh {start|stop|restart|status} [--port PORT]
|
||||
# 默认端口: 9000
|
||||
#
|
||||
# 项目: /home/xzq/5090/teamWorkspace/zh/PlantAI-dev
|
||||
# 端口: 9000 (web), Redis DB 14 (queue prefix: plantai_dev_9002)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [ -f "$SCRIPT_DIR/.env" ]; then
|
||||
set -a
|
||||
. "$SCRIPT_DIR/.env"
|
||||
set +a
|
||||
fi
|
||||
|
||||
# 解析命令行参数:第一个参数是命令,后面可选 --port / -p
|
||||
CMD=""
|
||||
WEB_PORT="9000"
|
||||
WEB_PORT="${PLANTAI_WEB_PORT:-9000}"
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
start|stop|restart|status)
|
||||
@@ -33,12 +37,15 @@ done
|
||||
CMD="${CMD:-status}"
|
||||
|
||||
# Conda 环境:启动脚本内部也依赖这个环境
|
||||
CONDA_ENV="${CONDA_ENV:-/home/xzq/miniconda3/envs/test4py311}"
|
||||
CONDA_BASE="${BBAV5_CONDA_BASE:-/home/xia-lab/miniconda3}"
|
||||
DEFAULT_ENV="${BBAV5_DEFAULT_ENV:-test4py311}"
|
||||
CONDA_ENV="${CONDA_ENV:-$CONDA_BASE/envs/$DEFAULT_ENV}"
|
||||
export PATH="$CONDA_ENV/bin:$PATH"
|
||||
|
||||
# 项目配置(与 .env 和启动脚本保持一致)
|
||||
PROJECT_DIR="/home/xzq/5090/teamWorkspace/zh/PlantAI-dev"
|
||||
QUEUE_PREFIX="plantai_dev_9002"
|
||||
PROJECT_DIR="$SCRIPT_DIR"
|
||||
QUEUE_PREFIX="${BBAV5_QUEUE_PREFIX:-plantai_4090_9000}"
|
||||
REDIS_URL="${BBAV5_REDIS_URL:-redis://localhost:6379/10}"
|
||||
WEB_LOG="/tmp/plantai_${WEB_PORT}.log"
|
||||
CELERY_LOG="/tmp/celery_${WEB_PORT}.log"
|
||||
|
||||
@@ -46,28 +53,49 @@ red() { echo -e "\033[31m$*\033[0m"; }
|
||||
green(){ echo -e "\033[32m$*\033[0m"; }
|
||||
cyan() { echo -e "\033[36m$*\033[0m"; }
|
||||
|
||||
web_pid_for_port() {
|
||||
ss -tlnp 2>/dev/null \
|
||||
| grep ":$WEB_PORT " \
|
||||
| grep -oP 'pid=\K\d+' \
|
||||
| head -n 1 \
|
||||
|| true
|
||||
}
|
||||
|
||||
web_process_cwd() {
|
||||
readlink -f "/proc/$1/cwd" 2>/dev/null || true
|
||||
}
|
||||
|
||||
web_pid_belongs_to_project() {
|
||||
[ "$(web_process_cwd "$1")" = "$PROJECT_DIR" ]
|
||||
}
|
||||
|
||||
status() {
|
||||
echo ""
|
||||
cyan "=== PlantAI-dev ${WEB_PORT} 服务状态 ==="
|
||||
echo ""
|
||||
|
||||
# Web 服务
|
||||
WEB_PID=$(ss -tlnp 2>/dev/null | grep ":$WEB_PORT " | grep -oP 'pid=\K\d+' || true)
|
||||
WEB_PID="$(web_pid_for_port)"
|
||||
if [ -n "${WEB_PID:-}" ]; then
|
||||
green "[✓] Web 服务运行中 (端口 $WEB_PORT, PID $WEB_PID)"
|
||||
WEB_CWD="$(web_process_cwd "$WEB_PID")"
|
||||
if web_pid_belongs_to_project "$WEB_PID"; then
|
||||
green "[✓] Web 服务运行中 (端口 $WEB_PORT, PID $WEB_PID)"
|
||||
else
|
||||
red "[!] 端口 $WEB_PORT 被其他项目占用 (PID $WEB_PID, cwd ${WEB_CWD:-未知})"
|
||||
fi
|
||||
else
|
||||
red "[✗] Web 服务未启动 (端口 $WEB_PORT)"
|
||||
fi
|
||||
|
||||
# Redis
|
||||
if redis-cli ping > /dev/null 2>&1; then
|
||||
green "[✓] Redis 运行中 (localhost:6379)"
|
||||
if redis-cli -u "$REDIS_URL" ping > /dev/null 2>&1; then
|
||||
green "[✓] Redis 运行中 ($REDIS_URL)"
|
||||
else
|
||||
red "[✗] Redis 未启动"
|
||||
fi
|
||||
|
||||
# Celery workers
|
||||
CELERY_COUNT=$(pgrep -fc "$QUEUE_PREFIX" || echo 0)
|
||||
CELERY_COUNT="$(pgrep -fc "$QUEUE_PREFIX" || true)"
|
||||
if [ "$CELERY_COUNT" -gt 0 ]; then
|
||||
green "[✓] Celery workers 运行中 (${CELERY_COUNT} 进程, 队列前缀: $QUEUE_PREFIX)"
|
||||
echo " Worker 列表:"
|
||||
@@ -86,13 +114,18 @@ stop() {
|
||||
echo ""
|
||||
|
||||
# 停止 Web 服务
|
||||
WEB_PID=$(ss -tlnp 2>/dev/null | grep ":$WEB_PORT " | grep -oP 'pid=\K\d+' || true)
|
||||
WEB_PID="$(web_pid_for_port)"
|
||||
if [ -n "${WEB_PID:-}" ]; then
|
||||
WEB_CWD="$(web_process_cwd "$WEB_PID")"
|
||||
if ! web_pid_belongs_to_project "$WEB_PID"; then
|
||||
red "[!] 拒绝停止:端口 $WEB_PORT 属于其他项目 (PID $WEB_PID, cwd ${WEB_CWD:-未知})"
|
||||
return 1
|
||||
fi
|
||||
echo "正在停止 Web 服务 (PID $WEB_PID)..."
|
||||
kill "$WEB_PID" 2>/dev/null || true
|
||||
sleep 1
|
||||
# 如果还没死,强制 kill
|
||||
if ss -tlnp 2>/dev/null | grep -q ":$WEB_PORT"; then
|
||||
# 仅强制停止仍属于本项目的原进程,避免 PID 复用造成误杀。
|
||||
if kill -0 "$WEB_PID" 2>/dev/null && web_pid_belongs_to_project "$WEB_PID"; then
|
||||
kill -9 "$WEB_PID" 2>/dev/null || true
|
||||
fi
|
||||
green "[✓] Web 服务已停止"
|
||||
@@ -125,12 +158,18 @@ start() {
|
||||
echo ""
|
||||
|
||||
# 检查是否已在运行
|
||||
if ss -tlnp 2>/dev/null | grep -q ":$WEB_PORT"; then
|
||||
red "[!] 端口 $WEB_PORT 已被占用,请先停止服务"
|
||||
WEB_PID="$(web_pid_for_port)"
|
||||
if [ -n "${WEB_PID:-}" ]; then
|
||||
WEB_CWD="$(web_process_cwd "$WEB_PID")"
|
||||
if web_pid_belongs_to_project "$WEB_PID"; then
|
||||
red "[!] 本项目 Web 已在端口 $WEB_PORT 运行 (PID $WEB_PID)"
|
||||
else
|
||||
red "[!] 端口 $WEB_PORT 被其他项目占用 (PID $WEB_PID, cwd ${WEB_CWD:-未知})"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! redis-cli ping > /dev/null 2>&1; then
|
||||
if ! redis-cli -u "$REDIS_URL" ping > /dev/null 2>&1; then
|
||||
red "[!] Redis 未运行,请先启动 Redis"
|
||||
exit 1
|
||||
fi
|
||||
@@ -141,7 +180,7 @@ start() {
|
||||
echo "正在启动 Celery workers..."
|
||||
nohup bash start_celery_9000.sh > "$CELERY_LOG" 2>&1 &
|
||||
sleep 3
|
||||
CELERY_COUNT=$(pgrep -fc "$QUEUE_PREFIX" || echo 0)
|
||||
CELERY_COUNT="$(pgrep -fc "$QUEUE_PREFIX" || true)"
|
||||
if [ "$CELERY_COUNT" -gt 0 ]; then
|
||||
green " [✓] Celery workers 已启动 (${CELERY_COUNT} 进程)"
|
||||
else
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
#!/bin/bash
|
||||
# 重启bbav20311服务并加载新提示词
|
||||
|
||||
echo "=============================================="
|
||||
echo "重启BBAV20311服务 - 加载新提示词"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
# 1. 查找并停止现有服务
|
||||
echo "1. 停止现有服务..."
|
||||
MAIN_PID=$(ps aux | grep "python3 main.py" | grep -v grep | grep -v restart | awk '{print $2}' | head -1)
|
||||
|
||||
if [ -n "$MAIN_PID" ]; then
|
||||
echo " 找到主服务PID: $MAIN_PID"
|
||||
kill -15 $MAIN_PID 2>/dev/null
|
||||
sleep 2
|
||||
echo " 已发送停止信号"
|
||||
else
|
||||
echo " 未找到运行中的主服务"
|
||||
fi
|
||||
|
||||
# 2. 激活conda环境
|
||||
echo ""
|
||||
echo "2. 激活conda环境..."
|
||||
source ~/anaconda3/etc/profile.d/conda.sh
|
||||
conda activate test4py311
|
||||
echo " 环境: $(conda info --envs | grep '*')"
|
||||
|
||||
# 3. 验证提示词文件
|
||||
echo ""
|
||||
echo "3. 验证提示词文件..."
|
||||
PROMPT_COUNT=$(ls /home/xia-lab/wutx/00test/bbav20311/prompts/*_agent.md 2>/dev/null | wc -l)
|
||||
echo " 提示词文件数量: $PROMPT_COUNT"
|
||||
|
||||
if [ "$PROMPT_COUNT" -eq 8 ]; then
|
||||
echo " ✅ 所有提示词文件存在"
|
||||
else
|
||||
echo " ⚠️ 提示词文件数量不匹配"
|
||||
fi
|
||||
|
||||
# 4. 启动服务
|
||||
echo ""
|
||||
echo "4. 启动服务..."
|
||||
cd /home/xia-lab/wutx/00test/bbav20311
|
||||
nohup python3 main.py > /tmp/bbav20311_restart.log 2>&1 &
|
||||
NEW_PID=$!
|
||||
echo " 新PID: $NEW_PID"
|
||||
|
||||
# 5. 等待服务启动
|
||||
echo ""
|
||||
echo "5. 等待服务启动..."
|
||||
sleep 5
|
||||
|
||||
# 6. 验证服务
|
||||
echo ""
|
||||
echo "6. 验证服务..."
|
||||
if ps -p $NEW_PID > /dev/null; then
|
||||
echo " ✅ 服务进程运行中"
|
||||
|
||||
# 测试API
|
||||
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:7865/api/info 2>/dev/null)
|
||||
if [ "$HTTP_STATUS" = "200" ]; then
|
||||
echo " ✅ API响应正常 (HTTP 200)"
|
||||
else
|
||||
echo " ⚠️ API响应异常 (HTTP $HTTP_STATUS)"
|
||||
fi
|
||||
else
|
||||
echo " ❌ 服务启动失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
echo "重启完成"
|
||||
echo "=============================================="
|
||||
echo "PID: $NEW_PID"
|
||||
echo "日志: /tmp/bbav20311_restart.log"
|
||||
echo "提示词: /home/xia-lab/wutx/00test/bbav20311/prompts/"
|
||||
echo "=============================================="
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/bin/bash
|
||||
# WebUI for bbav5 project
|
||||
# Port: 8788, State: ~/.hermes/webui-bbav5
|
||||
|
||||
export HERMES_WEBUI_PORT=8788
|
||||
export HERMES_WEBUI_STATE_DIR="$HOME/.hermes/webui-bbav5"
|
||||
export HERMES_WEBUI_DEFAULT_WORKSPACE="/home/xia-lab/wutx/00test/bbav5"
|
||||
export HERMES_WEBUI_AGENT_DIR="/home/xia-lab/wutx/01model/hermes-agent"
|
||||
|
||||
# Create state dir if not exists
|
||||
mkdir -p "$HERMES_WEBUI_STATE_DIR"
|
||||
|
||||
cd /home/xia-lab/wutx/01model/hermes-webui
|
||||
|
||||
echo "Starting WebUI for bbav5..."
|
||||
echo " Port: $HERMES_WEBUI_PORT"
|
||||
echo " Workspace: $HERMES_WEBUI_DEFAULT_WORKSPACE"
|
||||
echo " State: $HERMES_WEBUI_STATE_DIR"
|
||||
echo " URL: http://127.0.0.1:8788"
|
||||
|
||||
source /home/xia-lab/wutx/01model/hermes-agent/venv/bin/activate
|
||||
python server.py
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/bash
|
||||
# 启动Celery Worker
|
||||
|
||||
cd /home/xia-lab/wutx/00test/bbav5
|
||||
|
||||
# 使用conda环境的Python和Celery
|
||||
CONDA_ENV="/home/xia-lab/miniconda3/envs/test4py311"
|
||||
export PATH="$CONDA_ENV/bin:$PATH"
|
||||
|
||||
echo "Starting Celery Worker with conda environment: test4py311"
|
||||
"$CONDA_ENV/bin/celery" -A celery_app worker --loglevel=info --concurrency=2 &
|
||||
|
||||
echo "Celery Worker started!"
|
||||
echo "To monitor tasks, run: $CONDA_ENV/bin/celery -A celery_app flower --port=5555"
|
||||
@@ -1,93 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Start resource-specific Celery workers for the PlantAI-dev project.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
EXTERNAL_BBAV5_QUEUE_PREFIX="${BBAV5_QUEUE_PREFIX-}"
|
||||
EXTERNAL_BBAV5_REDIS_URL="${BBAV5_REDIS_URL-}"
|
||||
EXTERNAL_BBAV5_CELERY_LOG_DIR="${BBAV5_CELERY_LOG_DIR-}"
|
||||
EXTERNAL_BBAV5_WORKER_NAME_PREFIX="${BBAV5_WORKER_NAME_PREFIX-}"
|
||||
if [ -f "$SCRIPT_DIR/.env" ]; then
|
||||
set -a
|
||||
. "$SCRIPT_DIR/.env"
|
||||
set +a
|
||||
fi
|
||||
if [ -n "$EXTERNAL_BBAV5_QUEUE_PREFIX" ]; then
|
||||
BBAV5_QUEUE_PREFIX="$EXTERNAL_BBAV5_QUEUE_PREFIX"
|
||||
fi
|
||||
if [ -n "$EXTERNAL_BBAV5_REDIS_URL" ]; then
|
||||
BBAV5_REDIS_URL="$EXTERNAL_BBAV5_REDIS_URL"
|
||||
fi
|
||||
if [ -n "$EXTERNAL_BBAV5_CELERY_LOG_DIR" ]; then
|
||||
BBAV5_CELERY_LOG_DIR="$EXTERNAL_BBAV5_CELERY_LOG_DIR"
|
||||
fi
|
||||
if [ -n "$EXTERNAL_BBAV5_WORKER_NAME_PREFIX" ]; then
|
||||
BBAV5_WORKER_NAME_PREFIX="$EXTERNAL_BBAV5_WORKER_NAME_PREFIX"
|
||||
fi
|
||||
|
||||
PROJECT_ROOT="${BBAV5_PROJECT_ROOT:-$SCRIPT_DIR}"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
CONDA_BASE="${BBAV5_CONDA_BASE:-/home/xia-lab/miniconda3}"
|
||||
DEFAULT_ENV="${BBAV5_DEFAULT_ENV:-test4py311}"
|
||||
CONDA_ENV="$CONDA_BASE/envs/$DEFAULT_ENV"
|
||||
export PATH="$CONDA_ENV/bin:$PATH"
|
||||
export BBAV5_QUEUE_PREFIX="${BBAV5_QUEUE_PREFIX:-plantai_dev_7880}"
|
||||
export BBAV5_REDIS_URL="${BBAV5_REDIS_URL:-redis://localhost:6379/9}"
|
||||
WORKER_NAME_PREFIX="${BBAV5_WORKER_NAME_PREFIX:-${BBAV5_QUEUE_PREFIX//[^A-Za-z0-9_]/_}}"
|
||||
|
||||
LOG_DIR="${BBAV5_CELERY_LOG_DIR:-logs/celery_plantai_dev_7880}"
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
CELERY="$CONDA_ENV/bin/celery"
|
||||
APP="celery_app"
|
||||
LOGLEVEL="${BBAV5_CELERY_LOGLEVEL:-info}"
|
||||
HOSTNAME_VALUE="$(hostname)"
|
||||
|
||||
DEFAULT_CONCURRENCY="${BBAV5_DEFAULT_CONCURRENCY:-1}"
|
||||
HEAVY_CONCURRENCY="${BBAV5_HEAVY_CONCURRENCY:-2}"
|
||||
MEDIUM_CONCURRENCY="${BBAV5_MEDIUM_CONCURRENCY:-2}"
|
||||
LIGHT_CONCURRENCY="${BBAV5_LIGHT_CONCURRENCY:-4}"
|
||||
RNASEQ_CONCURRENCY="${BBAV5_RNASEQ_CONCURRENCY:-2}"
|
||||
|
||||
worker_online() {
|
||||
local name="$1"
|
||||
if [ "${BBAV5_CELERY_FORCE_START:-0}" = "1" ]; then
|
||||
return 1
|
||||
fi
|
||||
pgrep -f -- "--hostname=${name}@%h" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
start_worker() {
|
||||
local name="$1"
|
||||
local queue="$2"
|
||||
local concurrency="$3"
|
||||
if [ "$concurrency" -le 0 ]; then
|
||||
echo "Skipping ${name}: concurrency=${concurrency}"
|
||||
return
|
||||
fi
|
||||
if worker_online "$name"; then
|
||||
echo "Skipping ${name}: worker process already exists (${name}@%h)"
|
||||
return
|
||||
fi
|
||||
echo "Starting ${name}: queue=${queue}, concurrency=${concurrency}"
|
||||
"$CELERY" -A "$APP" worker \
|
||||
--detach \
|
||||
--loglevel="$LOGLEVEL" \
|
||||
--hostname="${name}@%h" \
|
||||
--queues="$queue" \
|
||||
--concurrency="$concurrency" \
|
||||
--prefetch-multiplier=1 \
|
||||
--logfile="$LOG_DIR/${name}.log" \
|
||||
--pidfile="$LOG_DIR/${name}.pid"
|
||||
}
|
||||
|
||||
start_worker "${WORKER_NAME_PREFIX}_default" "${BBAV5_QUEUE_PREFIX}.default" "$DEFAULT_CONCURRENCY"
|
||||
start_worker "${WORKER_NAME_PREFIX}_heavy" "${BBAV5_QUEUE_PREFIX}.heavy" "$HEAVY_CONCURRENCY"
|
||||
start_worker "${WORKER_NAME_PREFIX}_medium" "${BBAV5_QUEUE_PREFIX}.medium" "$MEDIUM_CONCURRENCY"
|
||||
start_worker "${WORKER_NAME_PREFIX}_light" "${BBAV5_QUEUE_PREFIX}.light" "$LIGHT_CONCURRENCY"
|
||||
start_worker "${WORKER_NAME_PREFIX}_rnaseq" "${BBAV5_QUEUE_PREFIX}.rnaseq" "$RNASEQ_CONCURRENCY"
|
||||
|
||||
echo "Celery workers started with queue prefix: ${BBAV5_QUEUE_PREFIX}"
|
||||
echo "Logs: ${LOG_DIR}"
|
||||
@@ -26,13 +26,13 @@ if [ -n "$EXTERNAL_BBAV5_WORKER_NAME_PREFIX" ]; then
|
||||
BBAV5_WORKER_NAME_PREFIX="$EXTERNAL_BBAV5_WORKER_NAME_PREFIX"
|
||||
fi
|
||||
if [ -z "${BBAV5_QUEUE_PREFIX-}" ]; then
|
||||
BBAV5_QUEUE_PREFIX="plantai_dev_9000"
|
||||
BBAV5_QUEUE_PREFIX="plantai_4090_9000"
|
||||
fi
|
||||
if [ -z "${BBAV5_REDIS_URL-}" ]; then
|
||||
BBAV5_REDIS_URL="redis://localhost:6379/10"
|
||||
fi
|
||||
if [ -z "${BBAV5_CELERY_LOG_DIR-}" ]; then
|
||||
BBAV5_CELERY_LOG_DIR="logs/celery_plantai_dev_9000"
|
||||
BBAV5_CELERY_LOG_DIR="logs/celery_plantai_4090_9000"
|
||||
fi
|
||||
if [ -z "$EXTERNAL_BBAV5_WORKER_NAME_PREFIX" ]; then
|
||||
unset BBAV5_WORKER_NAME_PREFIX
|
||||
@@ -51,11 +51,11 @@ export BBAV5_PROJECT_ENV_FILE="${BBAV5_PROJECT_ENV_FILE:-$PROJECT_ROOT/.env}"
|
||||
export BBAV5_LIGHTWEIGHT_EXTENSION_STORE="${BBAV5_LIGHTWEIGHT_EXTENSION_STORE:-$PROJECT_ROOT/.runtime/lightweight-extension/store}"
|
||||
export BBAV5_LIGHTWEIGHT_INPUT="${BBAV5_LIGHTWEIGHT_INPUT:-$PROJECT_ROOT/input}"
|
||||
export BBAV5_LIGHTWEIGHT_WORKSPACE="${BBAV5_LIGHTWEIGHT_WORKSPACE:-$PROJECT_ROOT/workspace}"
|
||||
export BBAV5_QUEUE_PREFIX="${BBAV5_QUEUE_PREFIX:-plantai_dev_9000}"
|
||||
export BBAV5_QUEUE_PREFIX="${BBAV5_QUEUE_PREFIX:-plantai_4090_9000}"
|
||||
export BBAV5_REDIS_URL="${BBAV5_REDIS_URL:-redis://localhost:6379/10}"
|
||||
WORKER_NAME_PREFIX="${BBAV5_WORKER_NAME_PREFIX:-${BBAV5_QUEUE_PREFIX//[^A-Za-z0-9_]/_}}"
|
||||
|
||||
LOG_DIR="${BBAV5_CELERY_LOG_DIR:-logs/celery_plantai_dev_9000}"
|
||||
LOG_DIR="${BBAV5_CELERY_LOG_DIR:-logs/celery_plantai_4090_9000}"
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
CELERY="$CONDA_ENV/bin/celery"
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Start PlantAI-dev Celery workers for the port-9000 runtime profile.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "${BBAV5_PROJECT_ROOT:-$SCRIPT_DIR}"
|
||||
|
||||
CONDA_BASE="${BBAV5_CONDA_BASE:-/home/xia-lab/miniconda3}"
|
||||
DEFAULT_ENV="${BBAV5_DEFAULT_ENV:-test4py311}"
|
||||
CONDA_ENV="$CONDA_BASE/envs/$DEFAULT_ENV"
|
||||
export PATH="$CONDA_ENV/bin:$PATH"
|
||||
|
||||
export BBAV5_PROJECT_ROOT="${BBAV5_PROJECT_ROOT:-$SCRIPT_DIR}"
|
||||
export PLANTAI_PROJECT_ROOT="${PLANTAI_PROJECT_ROOT:-$SCRIPT_DIR}"
|
||||
export BBAV5_QUEUE_PREFIX="${BBAV5_QUEUE_PREFIX:-plantai_dev_9000}"
|
||||
export BBAV5_REDIS_URL="${BBAV5_REDIS_URL:-redis://localhost:6379/10}"
|
||||
|
||||
WORKER_NAME_PREFIX="${BBAV5_WORKER_NAME_PREFIX:-${BBAV5_QUEUE_PREFIX//[^A-Za-z0-9_]/_}}"
|
||||
LOG_DIR="${BBAV5_CELERY_LOG_DIR:-logs/celery_plantai_dev_9000}"
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
CELERY="$CONDA_ENV/bin/celery"
|
||||
APP="celery_app"
|
||||
LOGLEVEL="${BBAV5_CELERY_LOGLEVEL:-info}"
|
||||
|
||||
DEFAULT_CONCURRENCY="${BBAV5_DEFAULT_CONCURRENCY:-1}"
|
||||
HEAVY_CONCURRENCY="${BBAV5_HEAVY_CONCURRENCY:-2}"
|
||||
MEDIUM_CONCURRENCY="${BBAV5_MEDIUM_CONCURRENCY:-2}"
|
||||
LIGHT_CONCURRENCY="${BBAV5_LIGHT_CONCURRENCY:-4}"
|
||||
RNASEQ_CONCURRENCY="${BBAV5_RNASEQ_CONCURRENCY:-2}"
|
||||
|
||||
worker_online() {
|
||||
local name="$1"
|
||||
if [ "${BBAV5_CELERY_FORCE_START:-0}" = "1" ]; then
|
||||
return 1
|
||||
fi
|
||||
pgrep -f -- "--hostname=${name}@%h" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
start_worker() {
|
||||
local name="$1"
|
||||
local queue="$2"
|
||||
local concurrency="$3"
|
||||
if [ "$concurrency" -le 0 ]; then
|
||||
echo "Skipping ${name}: concurrency=${concurrency}"
|
||||
return
|
||||
fi
|
||||
if worker_online "$name"; then
|
||||
echo "Skipping ${name}: worker process already exists (${name}@%h)"
|
||||
return
|
||||
fi
|
||||
echo "Starting ${name}: queue=${queue}, concurrency=${concurrency}"
|
||||
"$CELERY" -A "$APP" worker \
|
||||
--detach \
|
||||
--loglevel="$LOGLEVEL" \
|
||||
--hostname="${name}@%h" \
|
||||
--queues="$queue" \
|
||||
--concurrency="$concurrency" \
|
||||
--prefetch-multiplier=1 \
|
||||
--logfile="$LOG_DIR/${name}.log" \
|
||||
--pidfile="$LOG_DIR/${name}.pid"
|
||||
}
|
||||
|
||||
start_worker "${WORKER_NAME_PREFIX}_default" "${BBAV5_QUEUE_PREFIX}.default" "$DEFAULT_CONCURRENCY"
|
||||
start_worker "${WORKER_NAME_PREFIX}_heavy" "${BBAV5_QUEUE_PREFIX}.heavy" "$HEAVY_CONCURRENCY"
|
||||
start_worker "${WORKER_NAME_PREFIX}_medium" "${BBAV5_QUEUE_PREFIX}.medium" "$MEDIUM_CONCURRENCY"
|
||||
start_worker "${WORKER_NAME_PREFIX}_light" "${BBAV5_QUEUE_PREFIX}.light" "$LIGHT_CONCURRENCY"
|
||||
start_worker "${WORKER_NAME_PREFIX}_rnaseq" "${BBAV5_QUEUE_PREFIX}.rnaseq" "$RNASEQ_CONCURRENCY"
|
||||
|
||||
echo "Celery workers started with queue prefix: ${BBAV5_QUEUE_PREFIX}"
|
||||
echo "Redis: ${BBAV5_REDIS_URL}"
|
||||
echo "Logs: ${LOG_DIR}"
|
||||
+11
-10
@@ -24,6 +24,12 @@ done
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
if [ -f "$SCRIPT_DIR/.env" ]; then
|
||||
set -a
|
||||
. "$SCRIPT_DIR/.env"
|
||||
set +a
|
||||
fi
|
||||
|
||||
CONDA_BASE="${BBAV5_CONDA_BASE:-/home/xia-lab/miniconda3}"
|
||||
DEFAULT_ENV="${BBAV5_DEFAULT_ENV:-test4py311}"
|
||||
CONDA_ENV="$CONDA_BASE/envs/$DEFAULT_ENV"
|
||||
@@ -33,23 +39,18 @@ export PATH="$CONDA_ENV/bin:$PATH"
|
||||
# the caller's shell; GWAS/other sandboxes have dedicated restart scripts.
|
||||
export BBAV5_PROJECT_ROOT="$SCRIPT_DIR"
|
||||
export PLANTAI_PROJECT_ROOT="$SCRIPT_DIR"
|
||||
if [ -f "$SCRIPT_DIR/.env" ]; then
|
||||
set -a
|
||||
. "$SCRIPT_DIR/.env"
|
||||
set +a
|
||||
fi
|
||||
export BBAV5_PROJECT_ENV_FILE="${BBAV5_PROJECT_ENV_FILE:-$SCRIPT_DIR/.env}"
|
||||
export BBAV5_LIGHTWEIGHT_EXTENSION_STORE="${BBAV5_LIGHTWEIGHT_EXTENSION_STORE:-$SCRIPT_DIR/.runtime/lightweight-extension/store}"
|
||||
export BBAV5_LIGHTWEIGHT_INPUT="${BBAV5_LIGHTWEIGHT_INPUT:-$SCRIPT_DIR/input}"
|
||||
export BBAV5_LIGHTWEIGHT_WORKSPACE="${BBAV5_LIGHTWEIGHT_WORKSPACE:-$SCRIPT_DIR/workspace}"
|
||||
export BBAV5_QUEUE_PREFIX="${BBAV5_QUEUE_PREFIX:-plantai_dev_9000}"
|
||||
export BBAV5_QUEUE_PREFIX="${BBAV5_QUEUE_PREFIX:-plantai_4090_9000}"
|
||||
export BBAV5_REDIS_URL="${BBAV5_REDIS_URL:-redis://localhost:6379/10}"
|
||||
export PLANTAI_WEB_PORT="$PORT"
|
||||
export PLANTAI_RESTART_SCRIPT="$SCRIPT_DIR/restart_plantai_9000.sh"
|
||||
export GENE_FAMILY_MAS_API="http://127.0.0.1:7926"
|
||||
export DUAL_KB_QA_BASE_URL="http://127.0.0.1:7926"
|
||||
export RAG_API_URL="http://127.0.0.1:7926/api/v1/qa-v1-1-73/chat"
|
||||
export DUAL_KB_QA_ROUTE="qa-v1-1-73"
|
||||
export GENE_FAMILY_MAS_API="${GENE_FAMILY_MAS_API:-${DUAL_KB_QA_BASE_URL:-http://127.0.0.1:7920}}"
|
||||
export DUAL_KB_QA_BASE_URL="${DUAL_KB_QA_BASE_URL:-$GENE_FAMILY_MAS_API}"
|
||||
export RAG_API_URL="${RAG_API_URL:-$GENE_FAMILY_MAS_API}"
|
||||
export DUAL_KB_QA_ROUTE="${DUAL_KB_QA_ROUTE:-qa-v1-1-73}"
|
||||
export PLANTAI_AGENT_STATE_DB="$SCRIPT_DIR/.runtime/agent-state/agent-state.sqlite3"
|
||||
|
||||
PORT="$PLANTAI_WEB_PORT"
|
||||
|
||||
Reference in New Issue
Block a user