feat: deepseek summary agent + clean old memory logs + merge prep
This commit is contained in:
+1
-1
@@ -18,7 +18,7 @@ outputs/
|
|||||||
input/
|
input/
|
||||||
workspace/
|
workspace/
|
||||||
paper/
|
paper/
|
||||||
memory/
|
/memory/
|
||||||
memory_storage/
|
memory_storage/
|
||||||
backup/
|
backup/
|
||||||
backups/
|
backups/
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Plan Review Log: PPI/KaKs temporary frontend circuit breaker
|
||||||
|
|
||||||
|
Act 1 complete: the user selected a global frontend-only circuit breaker, retained plan visibility, paused batch and manual UI execution, preserved backend/API/CLI behavior, and required independent Ortholog execution to remain available.
|
||||||
|
|
||||||
|
MAX_ROUNDS=5
|
||||||
|
PLAN_FILE=.planning/ppi-kaks-temporary-frontend-circuit-breaker/PLAN.md
|
||||||
|
|
||||||
|
## Local read-only audit — frontend control flow
|
||||||
|
|
||||||
|
- Confirmed the global gate precedes normal stage scheduling.
|
||||||
|
- Required coverage of foreground batch, background batch, manual entry, shared submit, and stale download-monitor resume.
|
||||||
|
- Rejected reuse of `ppiKaksPauseStageIds()` because it pauses every planned Ortholog.
|
||||||
|
- Required the circuit breaker to activate before preflight can rewrite an existing Ortholog as PPI-owned.
|
||||||
|
|
||||||
|
## Local read-only audit — layer and contract boundaries
|
||||||
|
|
||||||
|
- Confirmed that PlanningAgent, backend, API, CLI, contracts, Manifest, stage-status, reproducibility, diagnosis, and result mapping should remain unchanged.
|
||||||
|
- Found that dependency-expanded KaKs Ortholog may lack frontend owner fields; required preserving original intent stage provenance in frontend batch state.
|
||||||
|
- Unknown Ortholog provenance must default to preserved/runnable.
|
||||||
|
|
||||||
|
## Local read-only audit — tests
|
||||||
|
|
||||||
|
- Required Node-executed behavior tests using the repository's `_source_block()` and `_run_node()` patterns, not constant/string-only proxy tests.
|
||||||
|
- Required positive controls proving unrelated stages still submit.
|
||||||
|
- Required explicit background-batch and stale-monitor tests.
|
||||||
|
- Recorded an unrelated pre-existing `openPpiResetDialog()` contract failure so it is not hidden or misattributed.
|
||||||
|
|
||||||
|
## External Codex review
|
||||||
|
|
||||||
|
The requested read-only Codex CLI review was not run. The escalation was rejected because it would disclose the local plan and potentially repository context to an external service without explicit user authorization for that disclosure. No workaround was attempted, and no `VERDICT: APPROVED` is claimed.
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
# Plan: Temporarily pause PPI/KaKs without blocking other analyses
|
||||||
|
|
||||||
|
_Locked via user decisions on 2026-08-05; implementation is not authorized by this plan alone._
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Keep `ppi` and `kaks` visible in every generated plan while both features are under revision, but make the web UI treat them as globally and explicitly paused. The UI must not run PPI/KaKs reference-species preflight, show reference-download dialogs, submit reference-download requests, or submit PPI/KaKs execution requests. Other planned stages and their confirmation dialogs must continue normally. Backend APIs, CLI programs, stage contracts, manifests, and biological methods remain unchanged so developers can still exercise those paths directly.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
1. Plans returned by the backend still contain `ppi` and `kaks`; no PlanningAgent pruning is introduced.
|
||||||
|
2. Before dependency preflight, the batch UI records every planned `ppi`/`kaks` stage as `paused` with the user-visible reason `功能修订中,本次自动跳过`.
|
||||||
|
3. With the temporary circuit breaker enabled, the UI does not call `/api/ppi/species-resolution` or `/api/reference-bundle/download` for those paused owners.
|
||||||
|
4. The UI never calls `/api/execute` with `stage` equal to `ppi` or `kaks` through normal batch or manual UI flows.
|
||||||
|
5. A PPI/KaKs-owned auto-inserted `ortholog` stage is paused only when its parameters identify that ownership via `autoInsertedFor`, `dependencyOwner`, or a PPI/KaKs dependency `referencePolicySource`. An independently requested, unowned `ortholog` stage remains runnable.
|
||||||
|
6. Non-PPI/KaKs stages continue through the existing confirmation queue and dependency scheduler after the paused stages are recorded.
|
||||||
|
7. Paused stages are reported as paused, not failed or completed, and no success Manifest is generated for them.
|
||||||
|
8. Turning off or removing the single temporary circuit-breaker declaration restores the existing PPI/KaKs preflight behavior without backend or contract migration.
|
||||||
|
9. Foreground batches, background batches, manual confirmation, stale open dialogs, and shared submit paths enforce the same pause behavior; none can bypass the circuit breaker or misreport a pause as failure.
|
||||||
|
|
||||||
|
## Root cause
|
||||||
|
|
||||||
|
`startBatchExecute()` currently awaits `ensureOrthologProducerForPpiKaks()` before it initializes and runs the ordinary stage scheduler. `ensureOrthologProducerForPpiKaks()` stops on the first owner for which `ensurePpiKaksDependencyOwner()` returns false. `startBatchExecute()` then returns before `executeStagesWithDependencies()`, so a PPI/KaKs dependency problem prevents every later confirmation dialog and otherwise independent analysis.
|
||||||
|
|
||||||
|
The observed job log contains repeated successful HTTP calls to `/api/ppi/species-resolution` but no `/api/reference-bundle/download` call for the failing attempt. That localizes the blocking behavior to frontend dependency/preflight control flow rather than an executed NCBI bundle download.
|
||||||
|
|
||||||
|
## CodeGraph and source findings
|
||||||
|
|
||||||
|
- `.codegraph/` exists, so `codegraph explore` was attempted first for the PPI/KaKs dialog and scheduling call path. It returned no usable output; targeted source reads were therefore used.
|
||||||
|
- `apps/web/vue3/index.html:14058` — `runPpiSpeciesResolutionPreflight()` owns the species-resolution request and blocking alerts.
|
||||||
|
- `apps/web/vue3/index.html:14354` — `ensurePpiKaksDependencyOwner()` owns per-owner reference decisions and download behavior.
|
||||||
|
- `apps/web/vue3/index.html:14491` — `ensureOrthologProducerForPpiKaks()` globally gates the batch.
|
||||||
|
- `apps/web/vue3/index.html:14980` — `startBatchExecute()` returns early when that gate is false.
|
||||||
|
- `apps/web/vue3/index.html:15037` — stored pause states are applied before dependency execution.
|
||||||
|
- `apps/web/vue3/index.html:15600-15900` — the shared stage confirmation queue and confirmation handlers control subsequent dialogs.
|
||||||
|
- `apps/web/vue3/index.html:12881` and `apps/web/vue3/index.html:18927` — manual confirmation and execution entry points must be guarded before scope loading, timers, or submission.
|
||||||
|
- `apps/web/vue3/index.html:21789-21882` — background batch execution is a separate scheduling path and must preserve `paused` rather than converting the circuit breaker into `failed`.
|
||||||
|
- `apps/web/vue3/index.html:13713` — an already-running legacy reference-download monitor can otherwise resume temporarily disabled owners.
|
||||||
|
- Auto-inserted Ortholog ownership is already recorded with `autoInsertedFor`, `dependencyOwner`, and `referencePolicySource` near `apps/web/vue3/index.html:14243-14339`.
|
||||||
|
- PlanningAgent dependency expansion can add the formal KaKs Ortholog without those frontend-only owner fields. The plan response exposes the original intent stages near `apps/web/vue3/main.py:22079-22089`, but the current frontend plan handoff near `apps/web/vue3/index.html:22617-22625` does not preserve them. The frontend must retain that evidence to distinguish an explicit independent Ortholog from a KaKs-only dependency.
|
||||||
|
|
||||||
|
## Allowed existing APIs and patterns
|
||||||
|
|
||||||
|
- Use the existing Vue reactive stage state: `stageExecutionStatus`, `stagePauseReasons`, `recordStagePause()`, and the batch pause summary.
|
||||||
|
- Use existing stage identity helper `getStageId()` and existing stage parameter objects; do not invent backend request fields.
|
||||||
|
- Follow the existing test pattern that extracts real JavaScript source blocks and executes them with Node in `tests/unit/test_ppi_owner_scheduling_task5.py`.
|
||||||
|
- Keep `/api/ppi/species-resolution`, `/api/reference-bundle/download`, `/api/execute`, CLI entry points, and Manifest schemas unchanged.
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
|
||||||
|
### Phase 1 — RED: executable regression tests
|
||||||
|
|
||||||
|
Modify only frontend scheduling tests first.
|
||||||
|
|
||||||
|
1. Add a behavior-level test for a small circuit-breaker helper extracted from the real HTML source. Given a plan containing ordinary stages, `ppi`, `kaks`, a PPI-owned Ortholog, and an independent Ortholog, assert that only `ppi`, `kaks`, and the owned Ortholog are selected for temporary pause.
|
||||||
|
- Cover PPI-owned, KaKs-owned, jointly owned, unrelated-auto, explicit-independent, and unknown-source Ortholog examples.
|
||||||
|
- Preserve the stage list itself; only statuses change.
|
||||||
|
2. Add a batch-start control-flow test or source-extracted harness proving that, while disabled, the existing `ensureOrthologProducerForPpiKaks()` callback is not invoked and scheduling proceeds.
|
||||||
|
3. Add a manual-confirmation guard test proving that PPI/KaKs resolve as paused and do not reach the execute-submit callback.
|
||||||
|
4. Add a shared-submit guard test covering stale open dialogs or alternate callers.
|
||||||
|
5. Add a background-batch test proving the owners and owned Ortholog remain paused rather than failed while unrelated background stages continue.
|
||||||
|
6. Add a stale-monitor test proving `resumePpiKaksPausedStages()` cannot restore disabled owners to pending.
|
||||||
|
7. Add negative assertions that the disabled branch does not invoke species-resolution, reference-bundle download, or PPI/KaKs `/api/execute` submission.
|
||||||
|
8. Run the focused tests and confirm they fail because the circuit-breaker behavior does not exist yet.
|
||||||
|
|
||||||
|
Documentation references:
|
||||||
|
|
||||||
|
- `tests/unit/test_ppi_owner_scheduling_task5.py` — `_source_block()` and Node execution pattern.
|
||||||
|
- `tests/unit/test_ppi_frontend_display_contract.py` — frontend contract assertions.
|
||||||
|
|
||||||
|
Anti-pattern guards:
|
||||||
|
|
||||||
|
- Do not add tests that only assert a constant string exists.
|
||||||
|
- Do not mock the helper under test.
|
||||||
|
- Do not weaken existing PPI owner-isolation tests.
|
||||||
|
|
||||||
|
### Phase 2 — GREEN: one frontend circuit breaker
|
||||||
|
|
||||||
|
Modify `apps/web/vue3/index.html` only.
|
||||||
|
|
||||||
|
1. Add one plainly named temporary feature declaration near batch execution state, for example `const PPI_KAKS_TEMPORARILY_DISABLED = true`, with a removal condition comment rather than a configurable framework.
|
||||||
|
2. Add one pure ownership predicate for an Ortholog stage. Its canonical condition is that `autoInsertedFor` contains `ppi` or `kaks`; `dependencyOwner` and the two existing PPI/KaKs `referencePolicySource` values may be accepted only as compatibility evidence for already-built stage objects. Never infer ownership merely because an Ortholog is present in the same batch.
|
||||||
|
- When the plan contains an untagged Ortholog added by dependency expansion, compare it with the preserved original intent stages: explicit `ortholog` intent means independent; absent `ortholog` intent combined with requested `kaks` means KaKs-owned.
|
||||||
|
- If intent provenance is absent or ambiguous, preserve Ortholog rather than guessing.
|
||||||
|
3. Add one helper that records the temporary pause reason for planned PPI/KaKs and owned Ortholog stages. It must not reuse `ppiKaksPauseStageIds()` because that helper currently adds any planned Ortholog and would violate independent-Ortholog preservation.
|
||||||
|
4. In `startBatchExecute()`, branch once:
|
||||||
|
- disabled: record the temporary pause set and continue;
|
||||||
|
- enabled: execute the existing `ensureOrthologProducerForPpiKaks()` path unchanged.
|
||||||
|
5. Preserve the existing status initialization and `applyStoredPpiKaksPauseStatuses()` flow, or introduce a narrowly named equivalent if the existing pause object cannot express precise stage IDs.
|
||||||
|
6. Add an early guard in `showConfirm()` before scope loading, dialog display, and the automatic countdown. Add a second guard at the start of `doExecute()` for a stale dialog opened before deployment/page-state refresh.
|
||||||
|
7. Add a final shared-submit guard before PPI/KaKs preflight and `/api/execute`. It is defense in depth for alternate callers, but its result must be interpreted as `paused`, never as a failed stage.
|
||||||
|
8. Apply the same pause selection to `startBackgroundBatchExecution()` so background execution excludes disabled owners and owned Ortholog, continues unrelated stages, and reports paused counts.
|
||||||
|
9. Prevent `resumePpiKaksPausedStages()` from changing temporarily disabled owners back to pending when an older reference-download monitor completes.
|
||||||
|
10. Do not change backend stage availability or returned plans.
|
||||||
|
11. At the frontend plan handoff, preserve the existing plan API's original intent stage list in batch state solely as ownership provenance; do not change the plan API schema or PlanningAgent.
|
||||||
|
|
||||||
|
Anti-pattern guards:
|
||||||
|
|
||||||
|
- No edits to `gfa_core/mas/agents/planning_v2.py`, `apps/web/pipeline_backend.py`, `apps/web/vue3/main.py`, CLI scripts, contracts, or Manifest writers.
|
||||||
|
- No job-ID special case.
|
||||||
|
- No broad `ortholog` pause by stage name alone.
|
||||||
|
- No fake completed state, empty success Manifest, or swallowed failure reported as success.
|
||||||
|
- No path that represents the temporary circuit breaker as an exception and then maps it to `failed`.
|
||||||
|
- No refactor of the large Vue file beyond the helpers required for this behavior.
|
||||||
|
|
||||||
|
### Phase 3 — GREEN verification and focused regression
|
||||||
|
|
||||||
|
1. Run the new focused tests.
|
||||||
|
2. Run all existing PPI frontend/owner tests.
|
||||||
|
3. Run the repository's HTML/JavaScript validation command used for `apps/web/vue3/index.html`.
|
||||||
|
4. Inspect the diff and verify every production change maps to an acceptance criterion.
|
||||||
|
|
||||||
|
Expected focused commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/home/xia-lab/miniconda3/envs/test4py311/bin/python -m pytest -q \
|
||||||
|
tests/unit/test_ppi_owner_scheduling_task5.py \
|
||||||
|
tests/unit/test_ppi_frontend_display_contract.py
|
||||||
|
/usr/bin/node --version
|
||||||
|
python validate_js.py
|
||||||
|
```
|
||||||
|
|
||||||
|
If the test environment provides a narrower JS validator command, use the repository-documented command rather than inventing one.
|
||||||
|
|
||||||
|
Known baseline note: the audit observed `test_ppi_frontend_display_contract.py` at 17 passed / 1 unrelated existing failure in the `openPpiResetDialog()` string contract. Record the exact fresh baseline before RED, and do not attribute that failure to this revision or weaken it to obtain green.
|
||||||
|
|
||||||
|
### Phase 4 — real UI/API validation
|
||||||
|
|
||||||
|
Use a non-destructive test job or a justified copied/subset job; do not mutate the user's completed scientific artifacts.
|
||||||
|
|
||||||
|
1. Request a plan containing `ppi`, `kaks`, and at least one unrelated stage; verify the plan still displays all stages.
|
||||||
|
2. Start the batch in the UI.
|
||||||
|
3. Verify PPI and KaKs immediately show paused with the exact temporary reason.
|
||||||
|
4. Verify no reference-species confirmation/download modal appears.
|
||||||
|
5. Verify the next unrelated stage confirmation appears and can be submitted.
|
||||||
|
6. Verify browser/network and server logs contain no new `/api/ppi/species-resolution`, `/api/reference-bundle/download`, or `/api/execute` PPI/KaKs request for this run.
|
||||||
|
7. Verify no new PPI/KaKs success Manifest exists.
|
||||||
|
8. Verify an explicitly requested independent Ortholog is not paused; separately verify an owned auto-inserted Ortholog is paused.
|
||||||
|
9. Exercise the background-batch path and verify disabled stages remain paused while an unrelated background stage proceeds.
|
||||||
|
10. Simulate completion of a pre-existing reference-download monitor and verify it cannot resume disabled owners.
|
||||||
|
|
||||||
|
This is workflow validation, not a biological full-data validation, because no PPI/KaKs method or output is being changed.
|
||||||
|
|
||||||
|
## Affected layers and synchronization
|
||||||
|
|
||||||
|
| Layer | Change |
|
||||||
|
|---|---|
|
||||||
|
| Frontend scheduling | Temporary availability declaration, precise pause selection, batch gate bypass, manual UI guard |
|
||||||
|
| Frontend status/result summary | Reuse paused status and pause reason; ensure it is not counted as failed/completed |
|
||||||
|
| Frontend tests | Add behavior-level red/green regression coverage |
|
||||||
|
| PlanningAgentV2 | No change; stages remain visible |
|
||||||
|
| Backend/API | No change |
|
||||||
|
| CLI/skills | No change |
|
||||||
|
| Stage contracts and identity | No change |
|
||||||
|
| Manifest/reproducibility | No change; paused stages produce no success Manifest |
|
||||||
|
| Diagnosis | No backend diagnostic change; the UI pause reason is explicit |
|
||||||
|
|
||||||
|
## Biological-method assumptions and fallbacks
|
||||||
|
|
||||||
|
- This circuit breaker makes no biological claim and does not alter PPI, Ka/Ks, STRING species selection, Ortholog mapping, or reference bundle methodology.
|
||||||
|
- No fallback species is selected automatically.
|
||||||
|
- Existing backend/CLI paths remain available only for deliberate development use.
|
||||||
|
|
||||||
|
## Risks and mitigations
|
||||||
|
|
||||||
|
1. **Owned Ortholog is not tagged in an older plan.** Preserve it rather than guessing. It may run independently, but an explicit stage must not be silently disabled. Add logging/diagnostic text if encountered.
|
||||||
|
2. **A later UI path submits directly to `/api/execute`.** Search all in-file submission paths for PPI/KaKs and cover the shared submission boundary with a regression test.
|
||||||
|
3. **Pause state is overwritten during batch initialization.** Verify the pause is stored before initialization and re-applied after stage statuses are initialized.
|
||||||
|
4. **Background execution maps the guard to failure.** Apply pause filtering before its execute/catch path and assert paused accounting explicitly.
|
||||||
|
5. **Background download monitor survives from an earlier attempt.** This change prevents new monitors and prevents their callbacks from resuming disabled owners; it does not cancel an already submitted external task. Document that existing tasks are unaffected.
|
||||||
|
6. **Temporary flag becomes permanent.** Use one declaration and a clear removal condition; do not distribute hard-coded booleans.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- Repairing PPI, Ka/Ks, Ortholog, STRING selection, or NCBI downloads.
|
||||||
|
- Changing dependency graphs or PlanningAgent output.
|
||||||
|
- Cancelling already running reference-bundle tasks.
|
||||||
|
- Backfilling or rewriting existing manifests.
|
||||||
|
- Changing API/CLI access for developers.
|
||||||
|
|
||||||
|
## Implementation handoff checklist
|
||||||
|
|
||||||
|
- [ ] User explicitly authorizes implementation after reviewing this plan.
|
||||||
|
- [ ] Focused regression tests are written and observed failing before production edits.
|
||||||
|
- [ ] Only `apps/web/vue3/index.html` and focused frontend test files are changed.
|
||||||
|
- [ ] Independent Ortholog preservation is covered by an executable test.
|
||||||
|
- [ ] Focused tests pass and existing PPI frontend tests introduce no new failures; any pre-existing baseline failure is reported separately.
|
||||||
|
- [ ] JavaScript validation passes.
|
||||||
|
- [ ] UI/network/log validation confirms unrelated analysis continues.
|
||||||
|
- [ ] Service restart requirement is reported; Celery restart should not be required for a frontend-only change unless deployment packaging dictates otherwise.
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# Findings
|
||||||
|
|
||||||
|
## Historical hypotheses to re-check
|
||||||
|
|
||||||
|
- PPI Gm LIS/SoyBase identifiers may not resolve to STRING identifiers.
|
||||||
|
- PPI failed Manifest may lose the skill-level issue and ID-resolution artifact.
|
||||||
|
- PPI technical reference may leak into biological scope/planning.
|
||||||
|
- KaKs may contain user-specific absolute tool paths.
|
||||||
|
- KaKs project skill may be a non-executing placeholder.
|
||||||
|
- KaKs method/provenance may disagree with the actual PAML yn00/MUSCLE commands.
|
||||||
|
- KaKs legacy execution may silently default to Arabidopsis.
|
||||||
|
|
||||||
|
## Current evidence
|
||||||
|
|
||||||
|
- Current main HEAD is `7399b0bf1`; WTX worktree is independently ahead at `68d305f5d`, so WTX history cannot be assumed to equal current main.
|
||||||
|
- Current main `apps/cli/run_kaks_v2.py` still hard-codes `/home/xzq/miniconda3/envs/test4py311` in the shebang, PATH/LIB setup, MUSCLE, pal2nal, yn00, and Rscript paths.
|
||||||
|
- Current main `skills/kaks/run.py:83-84` still logs completion while explicitly marked TODO placeholder.
|
||||||
|
- Current KaKs CLI defaults `neutral_rate=7.08e-9`, contains Ath-specific data comments/diagnostics, and records yn00 as `KaKs_Calculator (YN model)`.
|
||||||
|
- Current legacy backend KaKs path still defaults missing reference to `Ath` and invokes hard-coded `/home/xzq/miniconda3/bin/conda`.
|
||||||
|
- Current PlanningAgentV2 still maps explicit PPI species through a five-item static map and may write it into shared `referenceSpecies/refSpeciesList` with `scopeMode=target_plus_reference`.
|
||||||
|
- Current stage tool description still says PPI requires ortholog mapping and defaults PPI `species=ath`, `score=200`.
|
||||||
|
- Current PPI normalization and failed-manifest ownership still require detailed source tracing; search confirms the same issue codes and layers exist.
|
||||||
|
- PPI planning root cause is current and explicit: `_generate_default_stages()` mutates the shared intent params before per-stage params are built. An explicit PPI species in a five-item static map becomes shared `referenceSpecies/refSpeciesList` and may set `scopeMode=target_plus_reference`, so technical STRING ownership can alter biological scope for every later stage.
|
||||||
|
- `normalize_gene_ids_for_string()` currently contains only the rice transcript-to-gene rule. The PPI skill can additionally consume an evidence TSV through `ReferenceAliasIndex`, so the current Gm question depends on whether backend supplies an appropriate job-local `--id-map`; absence of a Gm mapping rule alone is not yet enough to conclude the production path must fail.
|
||||||
|
- Stage tool/catalog drift is confirmed: PPI metadata still describes ortholog mapping as required and defaults `species=ath`, inconsistent with the newer snapshot/direct-target execution path.
|
||||||
|
- PPI skill failed manifests are capable of preserving exact `issue_code`, metrics, artifacts, and provenance. The suspected information loss must therefore occur after the skill exits, in backend finalization/merge logic.
|
||||||
|
- Backend projected PPI requires an ID-map and passes `--id-map`, but direct STRING-supported targets only pass it opportunistically if `<input>/<ppi_storage_key>/<storage_key>.pep_cds_idmap.tsv` happens to exist. For direct Gm, `ppi_storage_key` falls back to the STRING species token (`gma`/taxon), while job identifiers use LIS/SoyBase namespace; there is no current Gm transform in policy. This reproduces the structural precondition for 0/56 mapping unless a correctly named job-local ID map is present.
|
||||||
|
- PPI failure ownership defect is confirmed in current source: the skill writes the exact failed manifest under `workspace/ppi/<prefix>.manifest.json`, but backend copies skill artifacts only when exit code is 0. On nonzero exit, it never reads/copies/merges that manifest; it creates or mutates `final/<prefix>.ppi.manifest.json` with generic error/provenance and can leave `artifacts={}`. This discards the skill-level ID-resolution issue/metrics/artifact and can also mutate a pre-existing final manifest.
|
||||||
|
- Existing tests cover successful PPI artifact synchronization and owner isolation, but the initial search found no test asserting skill-failed-manifest preservation through backend finalization.
|
||||||
|
- KaKs has two materially different backend paths. The scope path is fail-closed for missing target/reference species and files and uses `sys.executable`; the legacy no-scope path silently defaults the reference to `Ath`, searches Ft/Ath-specific filenames, and invokes `/home/xzq/miniconda3/bin/conda`.
|
||||||
|
- Both KaKs paths call the same CLI, whose shebang/environment and MUSCLE/pal2nal/yn00/Rscript executables are hard-coded under `/home/xzq`. Therefore scope correctness does not solve deployment portability on a `/home/xia-lab` installation.
|
||||||
|
- `run_kaks_v2.py` does generate a failed manifest and exits nonzero when no pair succeeds, but the backend returns a generic stage manifest on CLI failure and does not explicitly surface/merge the CLI failed manifest into status/diagnosis.
|
||||||
|
- KaKs execution method is PAML `yn00` after MUSCLE protein alignment and pal2nal codon projection, but CLI Manifest names the calculator `KaKs_Calculator (YN model)`. The project skill/config additionally advertise CodeML M0/M1/M7/M8 and MUSCLE 5.1 while the authoritative backend never invokes the placeholder skill and the CLI uses MUSCLE 3-style `-in/-out` syntax.
|
||||||
|
- The default neutral rate `7.08e-9` is labeled Arabidopsis in the project skill but is passed to every target/reference run unless overridden; the manifest records the number without species applicability/source provenance.
|
||||||
|
- The current test inventory is rich for PPI/KaKs preflight isolation and frontend dependency ownership, but the search found no dedicated tests that execute `run_kaks_v2.py`, validate its tool resolver, assert nonzero failed-manifest propagation, or audit method/version truthfulness.
|
||||||
|
- The normal frontend execution path is intentionally disabled for both stages: `ppiKaksTemporaryCircuitBreakerEnabled = true`. Batch execution marks PPI and KaKs paused, blocks their `/api/execute` submission, and also pauses an Ortholog stage that was auto-inserted solely for unavailable PPI/KaKs owners. Manual execution is blocked with “功能修订中,当前暂不可用”.
|
||||||
|
- Therefore the most immediate current user-visible state is not “PPI/KaKs sometimes fail”; it is “the frontend never submits either stage”. Backend/CLI defects remain latent and can still affect direct API or future re-enablement.
|
||||||
|
- The circuit breaker has dedicated unit tests proving later non-PPI/KaKs stages continue after pause; it is deliberate current behavior, not an accidental deadlock.
|
||||||
|
- Environment classification: the mounted SJ/WTX-style worktree has broken absolute `input` and `workspace` symlinks into `/home/xzq`, while `data` is valid. On the actual 5090 production host, every hard-coded KaKs executable checked (MUSCLE, pal2nal, yn00, Rscript, conda) exists. Thus hard-coded paths are a confirmed portability/design defect and break the `/home/xia-lab` worktree, but they do not by themselves prove current 5090 production execution failure.
|
||||||
|
- Existing Gm/TCP evidence exactly confirms cross-layer PPI loss: skill manifest has `ppi_string_id_resolution_failed`, 0/56 mappings, metrics and an ID-resolution TSV; final manifest has no issue code/metrics and empty artifacts. Both carry a misleading `resource_error` provenance and unused `reference_species=At` for a direct Gm query.
|
||||||
|
- Existing Os/PEBP KaKs real run proves the core production computation can succeed on 5090: 17/17 pairs, result table and plots. It simultaneously confirms methodology drift: actual MUSCLE 3.8 + PAML yn00 is recorded as `KaKs_Calculator`, yn00 version is `unknown`, and the universal neutral-rate value is used without source/applicability metadata.
|
||||||
|
- Current full PPI/KaKs-named unit suite result on production: 151 collected, 150 passed, 1 failed. The failure is the natural-language PPI Reset entry point.
|
||||||
|
- PPI Reset root cause is current: `isPpiResetIntent()` is defined and detects the intended phrases, and the dialog/API implementation exists, but the predicate is never called by `handleNlInput()` and there is no `openPpiResetDialog()` call. The button path `openPpiResetDialog(stage)` still exists, so this is an orphaned natural-language route rather than a broken reset backend.
|
||||||
|
- Current SJ main and WTX contain byte-identical PPI/KaKs core files: KaKs CLI/skill/config/docs, PPI policy/skill/provenance, backend, PlanningAgentV2, and stage tools. Only `apps/web/vue3/index.html` differs among the checked files. Therefore WTX does not contain a hidden backend/CLI PPI/KaKs fix that is merely awaiting merge.
|
||||||
|
- The circuit breaker was introduced by commit `2623d3114` on 2026-08-06, although that commit is titled `feat: update header and hide extension`. This is a traceability problem: a material PPI/KaKs runtime behavior change was bundled under an unrelated frontend commit title. WTX does not contain the circuit breaker; SJ main does.
|
||||||
|
- Diagnosis drift is confirmed. PPI diagnostics derive their code primarily from `ppi_provenance.diagnosis_code`; the real Gm final provenance says `ppi_string_resource_error`, so DiagnosisAgent cannot recover the exact skill issue after backend discarded it. Its declared PPI failure list includes `ppi_string_id_mapping_low` but not the skill's actual `ppi_string_id_resolution_failed` code.
|
||||||
|
- KaKs DiagnosisAgent correctly detects missing reference scope, but its recommendation says to use a project default reference when unspecified. That conflicts with the KaKs contract's fail-closed rule (“不得自动换用 At”), so diagnosis can recommend the same unsafe legacy behavior the contract forbids.
|
||||||
|
- Minimal current PlanningAgentV2 reproduction confirms the PPI scope bug, not just static suspicion. Input target=`Gm`, `scopeMode=target_only`, `ppiSpecies=gma`, stages=`motif,ppi` produces both stages with `referenceSpecies=['Gm']`, `scopeMode=target_plus_reference`, `analysisRoute=multi_species`, and scope/member-set IDs containing `ref-Gm`. Thus a PPI technical selector changes unrelated motif biological identity and even makes the target its own reference.
|
||||||
|
- The failed Gm job contains `input/Gm.id_alias.tsv` (168,133 lines), but it maps LIS raw IDs to essentially the same LIS canonical IDs; it is not a STRING alias map. Backend also does not discover this file, because PPI looks for `<input>/<ppi_storage_key>/<ppi_storage_key>.pep_cds_idmap.tsv`. Resolution rows show only raw/versionless LIS candidates and all 56 unresolved. Root cause is not “STRING lacks Gm”; it is the absence of a validated LIS/SoyBase→STRING namespace mapping plus no compatible evidence-discovery contract.
|
||||||
|
- Live 9010 `/api/stage-status` confirms the user-visible Gm PPI status is generic/wrong: failed, zero artifacts, error only “Manifest has no artifacts”, empty diagnosis report, and provenance `ppi_string_resource_error` with stale `reference_species=At`. The exact 0/56 ID-resolution root cause and TSV do not reach status.
|
||||||
|
- Live 9010 status for historical `Sv.HKL` confirms PPI computation is not globally broken: the stage is completed with 6/6 IDs mapped, 15 interactions, six artifacts, target-only biological scope, and STRING taxon 4555 as technical ownership.
|
||||||
|
- Live 9010 full-identity status for historical `Os.PEBP` confirms KaKs computation is not globally broken: the stage is completed with 17/17 pairs and three aggregate artifacts under the expected target/reference scope and member-set identity.
|
||||||
|
|
||||||
|
## Final classification
|
||||||
|
|
||||||
|
### Confirmed current defects
|
||||||
|
|
||||||
|
1. PPI/KaKs are globally blocked in the normal frontend execution path by the enabled temporary circuit breaker; this is deliberate pause behavior, not a backend exception.
|
||||||
|
2. PPI planning mutates shared biological scope from a technical STRING selector, contaminating unrelated stages and allowing target=reference scope.
|
||||||
|
3. PPI nonzero-exit finalization discards the skill's exact issue, metrics, and resolution artifact, causing generic and sometimes stale user-facing diagnosis.
|
||||||
|
4. Direct Gm PPI lacks a validated LIS/SoyBase-to-STRING mapping/evidence-discovery contract; the available job alias table is neither compatible nor consumed.
|
||||||
|
5. KaKs has divergent scope and legacy backend semantics; the legacy path silently defaults to Ath contrary to the fail-closed contract.
|
||||||
|
6. The project KaKs skill is a placeholder that reports completion without running KaKs, while production uses a separate CLI path.
|
||||||
|
7. KaKs executable/environment paths are deployment-specific and its declared methodology/provenance do not truthfully match MUSCLE 3 + pal2nal + PAML yn00 execution.
|
||||||
|
8. Natural-language PPI Reset is orphaned because its intent predicate is never invoked, despite the dialog/API and button path existing.
|
||||||
|
|
||||||
|
### Confirmed but currently latent or environment-dependent
|
||||||
|
|
||||||
|
- PPI/KaKs backend defects are hidden from the normal UI while the frontend circuit breaker remains enabled, but remain reachable by direct API or future re-enablement.
|
||||||
|
- KaKs hard-coded `/home/xzq` paths are a portability defect and break the mounted `/home/xia-lab` worktree; those exact tools currently exist on the 5090 production host, so this alone is not evidence of a current production computation failure.
|
||||||
|
- KaKs failed-manifest loss is strongly indicated by current source structure, but no real failed KaKs run was used to confirm the final user-visible outcome in this diagnosis.
|
||||||
|
|
||||||
|
### Explicitly disproved overbroad conclusions
|
||||||
|
|
||||||
|
- PPI is not globally nonfunctional: a validated Sv projected run completed successfully.
|
||||||
|
- KaKs is not globally nonfunctional: an Os-to-At real run completed 17/17 pairs.
|
||||||
|
- STRING support for Gm is not the root cause of the Gm failure; identifier namespace/evidence wiring is.
|
||||||
|
- WTX does not contain an unsynchronized hidden core fix for these paths; checked core files are byte-identical to SJ main.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Progress
|
||||||
|
|
||||||
|
- 2026-08-08: Read the 2026-07-22 production PPI record and 2026-08-06 WTX audit record.
|
||||||
|
- 2026-08-08: Started current-state diagnosis under Karpathy, systematic-debugging, and CropAI constraints.
|
||||||
|
- 2026-08-08: Attempted CodeGraph-first PPI and KaKs trace; both queries returned no output, so diagnosis will use targeted source search.
|
||||||
|
- 2026-08-08: Established Git baseline and searched current PPI/KaKs planning, backend, CLI, skill, provenance, and diagnosis symbols.
|
||||||
|
- 2026-08-08: Traced current PPI planning mutation, direct/projected ID-map wiring, and nonzero-exit manifest handling to concrete source lines.
|
||||||
|
- 2026-08-08: Confirmed the current frontend circuit breaker blocks all manual and batch PPI/KaKs submissions while allowing unrelated stages to continue.
|
||||||
|
- 2026-08-08: Compared mounted-path health with production tool availability and inspected the Gm PPI and Os/At KaKs real manifests.
|
||||||
|
- 2026-08-08: Ran all 11 PPI/KaKs-named unit files on production (150 passed, 1 failed) and traced the failure to an unused PPI Reset intent predicate.
|
||||||
|
- 2026-08-08: Compared current SJ main and WTX key files; core PPI/KaKs layers are identical, frontend index differs.
|
||||||
|
- 2026-08-08: Traced the SJ-only circuit breaker to commit `2623d3114` and confirmed it was bundled under an unrelated commit title.
|
||||||
|
- 2026-08-08: Audited stage-status/DiagnosisAgent issue handling and found PPI issue-code loss plus a KaKs fail-closed recommendation contradiction.
|
||||||
|
- 2026-08-08: Reproduced PlanningAgentV2 PPI scope contamination with a minimal no-LLM call and confirmed the Gm job has an unwired alias file.
|
||||||
|
- 2026-08-08: Queried live 9010 Gm PPI stage-status and confirmed exact root-cause/artifact loss reaches the user-facing status layer.
|
||||||
|
- 2026-08-08: Queried live 9010 successful Sv PPI and Os/At KaKs statuses, proving both computation cores can succeed under supported identities.
|
||||||
|
- 2026-08-08: Completed issue classification without modifying product code or restarting services.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# PPI/KaKs Current-State Diagnosis
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Re-evaluate the historical PPI/KaKs findings against the current main-project source, tests, deployment paths, and existing real-job evidence. Diagnose only; do not edit product code.
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- Every reported issue has current source or runtime evidence.
|
||||||
|
- Historical-only or already-fixed findings are explicitly separated.
|
||||||
|
- Each issue is traced to its owning layer and root cause.
|
||||||
|
- PPI and KaKs are assessed independently so one stage is not used as evidence for the other.
|
||||||
|
|
||||||
|
## Phases
|
||||||
|
|
||||||
|
- [complete] Phase 1: Establish current Git/code/deployment baseline.
|
||||||
|
- [complete] Phase 2: Trace PPI planning -> preflight -> backend -> skill -> manifest/status.
|
||||||
|
- [complete] Phase 3: Trace KaKs planning -> preflight -> backend -> CLI/skill -> manifest/status.
|
||||||
|
- [complete] Phase 4: Run focused tests and inspect existing real-job evidence.
|
||||||
|
- [complete] Phase 5: Classify confirmed, fixed, environmental, and unverified findings.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- No production code edits.
|
||||||
|
- No service restart or new real analysis task unless separately authorized.
|
||||||
|
- Do not infer current defects solely from the July/August records.
|
||||||
|
|
||||||
|
## Errors Encountered
|
||||||
|
|
||||||
|
| Error | Attempt | Resolution |
|
||||||
|
|---|---:|---|
|
||||||
|
| `codegraph explore` returned no output for both PPI and KaKs traces | 1 | Treat the mounted index as unavailable; fall back to targeted `rg` and symbol reads without rebuilding it. |
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# PPI/KaKs 最小可用修订计划(Karpathy 审核版)
|
||||||
|
|
||||||
|
## 唯一目标
|
||||||
|
|
||||||
|
让正常家族分析中的 PPI 和 KaKs 能从前端真实提交并完成;任一阶段失败时,后续无关阶段仍继续调度。
|
||||||
|
|
||||||
|
## 明确假设
|
||||||
|
|
||||||
|
1. 本轮生产环境是 5090;现有 `/home/xzq` KaKs 工具路径在该环境可用,因此不处理可移植性。
|
||||||
|
2. KaKs 的 scope + Ortholog + `run_kaks_v2.py` 路径已经有 Os→At 17/17 成功证据,除非标准前端请求没有进入这条路径,否则不改 KaKs 后端或算法。
|
||||||
|
3. 当前必须支持用户实际失败的 Gm/TCP PPI;Gm ID 映射只能使用 STRING 官方接口/别名证据,不接受字符串猜测。
|
||||||
|
4. “不阻塞后续”只指调度隔离,不要求本轮重构诊断、Manifest 或状态体系。
|
||||||
|
|
||||||
|
## 最终验收标准
|
||||||
|
|
||||||
|
1. Gm/TCP 的 56 个真实 ID 能完成 PPI,而不是 0/56。
|
||||||
|
2. Os→At PEBP KaKs 仍完成 17/17 pairs。
|
||||||
|
3. 前端手动与批量执行都会向 `/api/execute` 提交 PPI/KaKs,不再标记临时 `paused`。
|
||||||
|
4. `ppiSpecies` 不再改变 motif 等无关阶段的 biological scope。
|
||||||
|
5. 分别模拟 PPI 和 KaKs 执行失败,后续无关阶段仍被提交。
|
||||||
|
|
||||||
|
## 第 0 步:只读确认两个执行前提
|
||||||
|
|
||||||
|
### PPI 映射前提
|
||||||
|
|
||||||
|
- 用现有 Gm/TCP 56 个 ID 调用当前 STRING 官方映射入口/检查官方 alias 数据。
|
||||||
|
- 记录原始 ID、提交 ID、STRING ID 和来源。
|
||||||
|
- 只有确认一条可重复的官方映射路径后才写代码;如果 STRING 官方证据不能映射这些 ID,停止实施并报告数据前提,不制造启发式规则。
|
||||||
|
|
||||||
|
### KaKs 路由前提
|
||||||
|
|
||||||
|
- 从正常前端请求体追到 `/api/execute` 和 `pipeline_backend.py`,确认是否携带 scope、reference species 和 Ortholog mapping。
|
||||||
|
- 若已进入现有成功 scope 路径,KaKs 代码零修改。
|
||||||
|
- 若只缺一个现有参数,只修该参数传递点;不改 CLI 和分析方法。
|
||||||
|
|
||||||
|
## 第 1 步:修复 PPI scope 污染
|
||||||
|
|
||||||
|
### 必要修改
|
||||||
|
|
||||||
|
- `gfa_core/mas/agents/planning_v2.py`
|
||||||
|
- 移除把 `ppiSpecies` 写入共享 `referenceSpecies/refSpeciesList/scopeMode` 的逻辑。
|
||||||
|
- 保留 PPI stage 自己的 `ppiSpecies` 参数。
|
||||||
|
- 不改主 Agent、IntentAgent、其他 stage 规划和 scope 生成。
|
||||||
|
|
||||||
|
### 验证
|
||||||
|
|
||||||
|
- 回归用例:target=`Gm`、scopeMode=`target_only`、ppiSpecies=`gma`、stages=`motif,ppi`。
|
||||||
|
- motif 保持 target-only;PPI 收到 `ppiSpecies=gma`。
|
||||||
|
- 只增加这一条回归测试,并运行相关现有规划测试。
|
||||||
|
|
||||||
|
## 第 2 步:接入已证实的 Gm→STRING 映射
|
||||||
|
|
||||||
|
### 必要修改
|
||||||
|
|
||||||
|
- 优先只改 `apps/web/pipeline_backend.py`,在现有 PPI 命令构造点把第 0 步确认的映射证据传给现有 resolver。
|
||||||
|
- 只有现有 resolver 无法读取该证据格式时,才最小修改 `gfa_core/ppi_string_policy.py`。
|
||||||
|
- 不新增通用映射框架,不扩展其他物种,不做 ID 猜测规则。
|
||||||
|
|
||||||
|
### 验证
|
||||||
|
|
||||||
|
- 完整运行 `job_1785919737565_421af1` 的 56 个 Gm/TCP ID。
|
||||||
|
- 验证映射数不再是 0/56、PPI stage 成功且产生网络产物。
|
||||||
|
- 回归 Sv/HKL 已成功路线,防止破坏现有 projected PPI。
|
||||||
|
|
||||||
|
## 第 3 步:解除熔断并验证调度隔离
|
||||||
|
|
||||||
|
### 必要修改
|
||||||
|
|
||||||
|
- `apps/web/vue3/index.html`
|
||||||
|
- 将 PPI/KaKs 临时熔断关闭;优先采用最小开关变更,不删除现有辅助代码。
|
||||||
|
- 不在前端补规划,不改变阶段列表或下游 Agent。
|
||||||
|
- KaKs 仅在第 0 步确认标准请求参数丢失时,修改那个唯一传递点。
|
||||||
|
- 如果现有“单阶段失败后继续循环”逻辑已经通过测试,不修改调度实现。
|
||||||
|
|
||||||
|
### 验证
|
||||||
|
|
||||||
|
- 手动执行 PPI、KaKs:确认真实 `/api/execute` 请求。
|
||||||
|
- 批量正常场景:Gm PPI 和 Os→At KaKs 分别通过真实数据完成。
|
||||||
|
- 前端调度回归:分别让 PPI、KaKs 的执行请求失败,断言下一无关阶段仍被提交;若现有代码已满足,只保留测试、不改实现。
|
||||||
|
- 最后检查相关单测、业务 diff,并重启 9010 后用实际 API 再验证一次。
|
||||||
|
|
||||||
|
## 本轮明确不做
|
||||||
|
|
||||||
|
- 不修 PPI Reset 自然语言入口。
|
||||||
|
- 不改失败 Manifest 合并、DiagnosisAgent 或通用状态体系;它们不阻止阶段执行和后续调度。
|
||||||
|
- 不实现 KaKs placeholder skill。
|
||||||
|
- 不统一 `/home/xzq` 路径,不做跨机器可移植性重构。
|
||||||
|
- 不修改 KaKs 算法、模型、图表、方法文档或 legacy 路径。
|
||||||
|
- 不扩展更多 STRING 物种映射。
|
||||||
|
- 不重构主 Agent、IntentAgent、下游 Agent 或前端规划。
|
||||||
|
- 不清理无关技术债。
|
||||||
|
|
||||||
|
## 顺序约束
|
||||||
|
|
||||||
|
必须按 0 → 1 → 2 → 3 执行。只有 PPI scope 和 Gm 映射都通过真实数据后才关闭前端熔断。
|
||||||
+87
-12
@@ -1,10 +1,10 @@
|
|||||||
#!/home/xzq/miniconda3/envs/test4py311/bin/python
|
#!/home/xzq/miniconda3/envs/test4py311/bin/python
|
||||||
"""Scope-aware whole-job summary stage.
|
"""Scope-aware whole-job summary and DeepSeek report stage.
|
||||||
|
|
||||||
Summary is an objective result index and quality-control layer. It reads all
|
The deterministic layer first builds a traceable manifest/artifact index. By
|
||||||
stage manifests in a job, groups them by scope_id, extracts key metrics and
|
default that evidence is then passed to DeepSeek to produce the human-readable
|
||||||
artifacts, and writes machine-readable and human-readable summaries. It does
|
Chinese report. The evidence index remains a separate artifact so generated
|
||||||
not perform deep biological interpretation.
|
prose can always be audited against local results.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -16,6 +16,10 @@ from pathlib import Path
|
|||||||
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
if str(PROJECT_ROOT) not in sys.path:
|
||||||
|
sys.path.insert(0, str(PROJECT_ROOT))
|
||||||
|
|
||||||
CONDA_BIN = "/home/xzq/miniconda3/envs/test4py311/bin"
|
CONDA_BIN = "/home/xzq/miniconda3/envs/test4py311/bin"
|
||||||
if CONDA_BIN not in os.environ.get("PATH", ""):
|
if CONDA_BIN not in os.environ.get("PATH", ""):
|
||||||
os.environ["PATH"] = CONDA_BIN + ":" + os.environ.get("PATH", "")
|
os.environ["PATH"] = CONDA_BIN + ":" + os.environ.get("PATH", "")
|
||||||
@@ -256,6 +260,10 @@ def main():
|
|||||||
parser.add_argument("--scope-id", help="optional explicit scope_id filter")
|
parser.add_argument("--scope-id", help="optional explicit scope_id filter")
|
||||||
parser.add_argument("--force", action="store_true", help="overwrite existing results")
|
parser.add_argument("--force", action="store_true", help="overwrite existing results")
|
||||||
parser.add_argument("--log-file", help="log file path")
|
parser.add_argument("--log-file", help="log file path")
|
||||||
|
parser.add_argument("--no-deepseek", action="store_true", help="write only the deterministic evidence index")
|
||||||
|
parser.add_argument("--framework", help="optional Markdown framework for the DeepSeek report")
|
||||||
|
parser.add_argument("--deepseek-model", default="", help="optional DeepSeek model override")
|
||||||
|
parser.add_argument("--deepseek-max-tokens", type=int, default=16000)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
manifest_path = Path(args.manifest).expanduser().resolve() if args.manifest else None
|
manifest_path = Path(args.manifest).expanduser().resolve() if args.manifest else None
|
||||||
@@ -284,11 +292,11 @@ def main():
|
|||||||
artifact_rows = [row for item in summaries for row in item["artifact_rows"]]
|
artifact_rows = [row for item in summaries for row in item["artifact_rows"]]
|
||||||
checks = build_checks(summaries)
|
checks = build_checks(summaries)
|
||||||
|
|
||||||
report = final_dir / f"{prefix}.summary_report.md"
|
evidence_index = final_dir / f"{prefix}.summary_evidence_index.md"
|
||||||
files_tsv = final_dir / f"{prefix}.summary.files.tsv"
|
files_tsv = final_dir / f"{prefix}.summary.files.tsv"
|
||||||
manifest_out = final_dir / f"{prefix}.summary.manifest.json"
|
manifest_out = final_dir / f"{prefix}.summary.manifest.json"
|
||||||
write_files_tsv(files_tsv, artifact_rows)
|
write_files_tsv(files_tsv, artifact_rows)
|
||||||
write_report(report, prefix, summaries, checks, files_tsv)
|
write_report(evidence_index, prefix, summaries, checks, files_tsv)
|
||||||
|
|
||||||
manifest_data = {
|
manifest_data = {
|
||||||
"manifest_version": "1.0",
|
"manifest_version": "1.0",
|
||||||
@@ -306,22 +314,24 @@ def main():
|
|||||||
"summaries": summaries,
|
"summaries": summaries,
|
||||||
"checks": checks,
|
"checks": checks,
|
||||||
"artifacts": {
|
"artifacts": {
|
||||||
"summary_report": {"path": f"final/{report.name}", "type": "markdown", "size": report.stat().st_size},
|
"summary_evidence_index": {"path": f"final/{evidence_index.name}", "type": "markdown", "size": evidence_index.stat().st_size},
|
||||||
"files_tsv": {"path": f"final/{files_tsv.name}", "type": "tsv", "size": files_tsv.stat().st_size},
|
"files_tsv": {"path": f"final/{files_tsv.name}", "type": "tsv", "size": files_tsv.stat().st_size},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
manifest_out.write_text(json.dumps(manifest_data, indent=2, ensure_ascii=False), encoding="utf-8")
|
manifest_out.write_text(json.dumps(manifest_data, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||||
|
|
||||||
|
scoped_manifest = None
|
||||||
|
scoped_data = None
|
||||||
if scope_id:
|
if scope_id:
|
||||||
scoped_items = [x for x in summaries if x["scope_id"] == scope_id]
|
scoped_items = [x for x in summaries if x["scope_id"] == scope_id]
|
||||||
scoped_rows = [row for x in scoped_items for row in x["artifact_rows"]]
|
scoped_rows = [row for x in scoped_items for row in x["artifact_rows"]]
|
||||||
scoped_checks = [x for x in checks if x["scope_id"] in {scope_id, "all"}]
|
scoped_checks = [x for x in checks if x["scope_id"] in {scope_id, "all"}]
|
||||||
token = sanitize_token(scope_id)
|
token = sanitize_token(scope_id)
|
||||||
scoped_report = final_dir / f"{prefix}.{token}.summary_report.md"
|
scoped_evidence_index = final_dir / f"{prefix}.{token}.summary_evidence_index.md"
|
||||||
scoped_files = final_dir / f"{prefix}.{token}.summary.files.tsv"
|
scoped_files = final_dir / f"{prefix}.{token}.summary.files.tsv"
|
||||||
scoped_manifest = final_dir / f"{prefix}.{token}.summary.manifest.json"
|
scoped_manifest = final_dir / f"{prefix}.{token}.summary.manifest.json"
|
||||||
write_files_tsv(scoped_files, scoped_rows)
|
write_files_tsv(scoped_files, scoped_rows)
|
||||||
write_report(scoped_report, prefix, scoped_items, scoped_checks, scoped_files)
|
write_report(scoped_evidence_index, prefix, scoped_items, scoped_checks, scoped_files)
|
||||||
scoped_data = dict(manifest_data)
|
scoped_data = dict(manifest_data)
|
||||||
scoped_data.update({
|
scoped_data.update({
|
||||||
"scope_id": scope_id,
|
"scope_id": scope_id,
|
||||||
@@ -334,14 +344,79 @@ def main():
|
|||||||
"summaries": scoped_items,
|
"summaries": scoped_items,
|
||||||
"checks": scoped_checks,
|
"checks": scoped_checks,
|
||||||
"artifacts": {
|
"artifacts": {
|
||||||
"summary_report": {"path": f"final/{scoped_report.name}", "type": "markdown", "size": scoped_report.stat().st_size},
|
"summary_evidence_index": {"path": f"final/{scoped_evidence_index.name}", "type": "markdown", "size": scoped_evidence_index.stat().st_size},
|
||||||
"files_tsv": {"path": f"final/{scoped_files.name}", "type": "tsv", "size": scoped_files.stat().st_size},
|
"files_tsv": {"path": f"final/{scoped_files.name}", "type": "tsv", "size": scoped_files.stat().st_size},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
scoped_manifest.write_text(json.dumps(scoped_data, indent=2, ensure_ascii=False), encoding="utf-8")
|
scoped_manifest.write_text(json.dumps(scoped_data, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||||
|
|
||||||
|
if args.no_deepseek:
|
||||||
|
fallback_report = final_dir / (
|
||||||
|
f"{prefix}.{sanitize_token(scope_id)}.summary_report.md" if scope_id else f"{prefix}.summary_report.md"
|
||||||
|
)
|
||||||
|
source_index = scoped_evidence_index if scope_id else evidence_index
|
||||||
|
fallback_report.write_text(source_index.read_text(encoding="utf-8"), encoding="utf-8")
|
||||||
|
target_manifest = scoped_manifest if scope_id and scoped_manifest else manifest_out
|
||||||
|
target_data = read_json(target_manifest) or manifest_data
|
||||||
|
target_data.setdefault("artifacts", {})["summary_report"] = {
|
||||||
|
"path": f"final/{fallback_report.name}",
|
||||||
|
"type": "markdown",
|
||||||
|
"size": fallback_report.stat().st_size,
|
||||||
|
"generation": "deterministic_no_deepseek",
|
||||||
|
}
|
||||||
|
target_data["deepseek_generation"] = {"status": "skipped", "reason": "--no-deepseek"}
|
||||||
|
target_manifest.write_text(json.dumps(target_data, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
from gfa_core.mas.agents.deepseek_summary import generate_deepseek_summary
|
||||||
|
|
||||||
|
result = generate_deepseek_summary(
|
||||||
|
final_dir=final_dir,
|
||||||
|
prefix=prefix,
|
||||||
|
job_id=workspace.name,
|
||||||
|
summaries=summaries,
|
||||||
|
scope_id=scope_id,
|
||||||
|
framework_path=Path(args.framework).expanduser() if args.framework else None,
|
||||||
|
model=args.deepseek_model,
|
||||||
|
max_tokens=max(4096, int(args.deepseek_max_tokens)),
|
||||||
|
)
|
||||||
|
effective_scope = str(result.get("scope_id") or "")
|
||||||
|
target_manifest = scoped_manifest if scope_id and scoped_manifest else manifest_out
|
||||||
|
target_data = read_json(target_manifest) or manifest_data
|
||||||
|
report_path = Path(result["report_path"])
|
||||||
|
evidence_path = Path(result["evidence_path"])
|
||||||
|
metadata_path = Path(result["metadata_path"])
|
||||||
|
target_data.setdefault("artifacts", {}).update({
|
||||||
|
"summary_report": {
|
||||||
|
"path": f"final/{report_path.name}", "type": "markdown", "size": report_path.stat().st_size,
|
||||||
|
"generation": "deepseek_api",
|
||||||
|
},
|
||||||
|
"summary_evidence": {
|
||||||
|
"path": f"final/{evidence_path.name}", "type": "json", "size": evidence_path.stat().st_size,
|
||||||
|
},
|
||||||
|
"deepseek_metadata": {
|
||||||
|
"path": f"final/{metadata_path.name}", "type": "json", "size": metadata_path.stat().st_size,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
target_data["deepseek_generation"] = {
|
||||||
|
"status": "success",
|
||||||
|
"scope_id": effective_scope,
|
||||||
|
"provider": result["metadata"].get("provider"),
|
||||||
|
"model": result["metadata"].get("model"),
|
||||||
|
"validation": result["metadata"].get("validation"),
|
||||||
|
}
|
||||||
|
target_manifest.write_text(json.dumps(target_data, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||||
|
except Exception as exc:
|
||||||
|
target_manifest = scoped_manifest if scope_id and scoped_manifest else manifest_out
|
||||||
|
target_data = read_json(target_manifest) or manifest_data
|
||||||
|
target_data["status"] = "failed"
|
||||||
|
target_data["deepseek_generation"] = {"status": "failed", "error": str(exc)[:2000]}
|
||||||
|
target_manifest.write_text(json.dumps(target_data, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||||
|
print(f"Error: DeepSeek summary generation failed: {exc}", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
print("Summary complete")
|
print("Summary complete")
|
||||||
print(f" - {report}")
|
print(f" - {evidence_index}")
|
||||||
print(f" - {files_tsv}")
|
print(f" - {files_tsv}")
|
||||||
print(f" - {manifest_out}")
|
print(f" - {manifest_out}")
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
|||||||
/home/xzq/5090/PlantAI-dev4090-7/data
|
../../../PlantAI-dev4090-7/data/
|
||||||
@@ -0,0 +1,718 @@
|
|||||||
|
"""DeepSeek-backed, evidence-locked gene-family summary reports.
|
||||||
|
|
||||||
|
The LLM is used only to turn verified local results into readable Chinese. All
|
||||||
|
numbers, result boundaries and figure names come from local manifests and the
|
||||||
|
existing evidence interpreter. The report, prompt evidence and API metadata
|
||||||
|
are persisted separately so a generated claim can be audited later.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from collections import defaultdict
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, Iterable, List, Optional
|
||||||
|
|
||||||
|
from gfa_core.mas.agents.interpretation_evidence import (
|
||||||
|
build_evidence_report,
|
||||||
|
extract_stage_evidence,
|
||||||
|
)
|
||||||
|
from gfa_core.mas.llm.base import LLMMessage
|
||||||
|
from gfa_core.mas.llm.providers.deepseek import DeepSeekProvider
|
||||||
|
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parents[3]
|
||||||
|
DEFAULT_FRAMEWORK = PROJECT_ROOT / "prompts" / "gene_family_summary_report_framework_zh.md"
|
||||||
|
IMAGE_SUFFIXES = {".png", ".pdf", ".svg", ".jpg", ".jpeg"}
|
||||||
|
STAGE_HINTS = [
|
||||||
|
"reference_classification",
|
||||||
|
"subfamily_confirm",
|
||||||
|
"interspecies_synteny",
|
||||||
|
"phylogeny",
|
||||||
|
"identify",
|
||||||
|
"motif",
|
||||||
|
"domain",
|
||||||
|
"structure",
|
||||||
|
"combo",
|
||||||
|
"promoter",
|
||||||
|
"tfbs",
|
||||||
|
"synteny",
|
||||||
|
"karyotype",
|
||||||
|
"ortholog",
|
||||||
|
"kaks",
|
||||||
|
"pep",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class DeepSeekSummaryError(RuntimeError):
|
||||||
|
"""Raised when an evidence-locked report cannot be generated."""
|
||||||
|
|
||||||
|
|
||||||
|
def _read_json(path: Path) -> Dict[str, Any]:
|
||||||
|
try:
|
||||||
|
value = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
return value if isinstance(value, dict) else {}
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _load_env_file(path: Path) -> Dict[str, str]:
|
||||||
|
values: Dict[str, str] = {}
|
||||||
|
if not path.is_file():
|
||||||
|
return values
|
||||||
|
for raw in path.read_text(encoding="utf-8", errors="ignore").splitlines():
|
||||||
|
line = raw.strip()
|
||||||
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
continue
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
values[key.strip()] = value.strip().strip('"').strip("'")
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def load_deepseek_settings(*, model_override: str = "") -> Dict[str, str]:
|
||||||
|
values: Dict[str, str] = {}
|
||||||
|
for env_path in (
|
||||||
|
PROJECT_ROOT / ".env.v15_deepseek",
|
||||||
|
PROJECT_ROOT / ".env",
|
||||||
|
PROJECT_ROOT / "config" / ".env",
|
||||||
|
):
|
||||||
|
for key, value in _load_env_file(env_path).items():
|
||||||
|
values.setdefault(key, value)
|
||||||
|
api_key = (
|
||||||
|
os.environ.get("DEEPSEEK_API_KEY", "").strip()
|
||||||
|
or os.environ.get("NEW_API_KEY", "").strip()
|
||||||
|
or values.get("DEEPSEEK_API_KEY", "").strip()
|
||||||
|
or values.get("NEW_API_KEY", "").strip()
|
||||||
|
)
|
||||||
|
if not api_key:
|
||||||
|
raise DeepSeekSummaryError("DeepSeek API key is not configured (DEEPSEEK_API_KEY or NEW_API_KEY).")
|
||||||
|
base_url = (
|
||||||
|
os.environ.get("DEEPSEEK_BASE_URL", "").strip()
|
||||||
|
or os.environ.get("NEW_API_BASE_URL", "").strip()
|
||||||
|
or values.get("DEEPSEEK_BASE_URL", "").strip()
|
||||||
|
or values.get("NEW_API_BASE_URL", "").strip()
|
||||||
|
or "https://api.deepseek.com"
|
||||||
|
)
|
||||||
|
model = (
|
||||||
|
model_override.strip()
|
||||||
|
or os.environ.get("DEEPSEEK_MODEL", "").strip()
|
||||||
|
or os.environ.get("NEW_API_MODEL", "").strip()
|
||||||
|
or values.get("DEEPSEEK_MODEL", "").strip()
|
||||||
|
or values.get("NEW_API_MODEL", "").strip()
|
||||||
|
or "deepseek-chat"
|
||||||
|
)
|
||||||
|
return {"api_key": api_key, "base_url": base_url, "model": model}
|
||||||
|
|
||||||
|
|
||||||
|
def select_primary_scope(summaries: Iterable[Dict[str, Any]], requested: str = "") -> str:
|
||||||
|
if str(requested or "").strip():
|
||||||
|
return str(requested).strip()
|
||||||
|
counts: Dict[str, int] = defaultdict(int)
|
||||||
|
for item in summaries:
|
||||||
|
scope = str(item.get("scope_id") or "").strip()
|
||||||
|
stage = str(item.get("stage") or "").strip()
|
||||||
|
if scope and stage not in {"summary", "genome_prepare", "config_autofill"}:
|
||||||
|
counts[scope] += 1
|
||||||
|
if not counts:
|
||||||
|
return ""
|
||||||
|
return max(
|
||||||
|
counts,
|
||||||
|
key=lambda scope: (
|
||||||
|
int("target_plus_reference" in scope),
|
||||||
|
int("subfamily_evidence" not in scope),
|
||||||
|
counts[scope],
|
||||||
|
scope,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _infer_stage(name: str, role: str = "") -> str:
|
||||||
|
text = f"{role} {name}".lower()
|
||||||
|
if "tfbs" in text or "motif_source" in text or "gene_motif" in text:
|
||||||
|
return "tfbs"
|
||||||
|
if "interspecies" in text:
|
||||||
|
return "interspecies_synteny"
|
||||||
|
if "circular_tree" in text or ".tree." in text or text.endswith("tree.png"):
|
||||||
|
return "phylogeny"
|
||||||
|
if "protparam" in text or "tmhmm" in text or "pep_" in text:
|
||||||
|
return "pep"
|
||||||
|
for stage in STAGE_HINTS:
|
||||||
|
if stage in text:
|
||||||
|
return stage
|
||||||
|
return "other"
|
||||||
|
|
||||||
|
|
||||||
|
def _logical_figure_key(path: Path) -> str:
|
||||||
|
return path.stem.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def collect_figure_inventory(
|
||||||
|
final_dir: Path,
|
||||||
|
summaries: Iterable[Dict[str, Any]],
|
||||||
|
*,
|
||||||
|
scope_id: str = "",
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
artifact_map: Dict[str, Dict[str, str]] = {}
|
||||||
|
for item in summaries:
|
||||||
|
item_scope = str(item.get("scope_id") or "")
|
||||||
|
if scope_id and item_scope and item_scope != scope_id:
|
||||||
|
# Foundational target-only figures are not reused in a reference scope.
|
||||||
|
continue
|
||||||
|
stage = str(item.get("stage") or "")
|
||||||
|
for artifact in item.get("artifact_rows") or []:
|
||||||
|
raw = str(artifact.get("path") or "")
|
||||||
|
if not raw:
|
||||||
|
continue
|
||||||
|
path = Path(raw)
|
||||||
|
if path.suffix.lower() not in IMAGE_SUFFIXES:
|
||||||
|
continue
|
||||||
|
artifact_map[path.name] = {
|
||||||
|
"stage": stage or _infer_stage(path.name, str(artifact.get("artifact") or "")),
|
||||||
|
"role": str(artifact.get("artifact") or ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates: List[Path] = []
|
||||||
|
if final_dir.is_dir():
|
||||||
|
candidates.extend(
|
||||||
|
path for path in final_dir.iterdir()
|
||||||
|
if path.is_file()
|
||||||
|
and path.suffix.lower() in IMAGE_SUFFIXES
|
||||||
|
and ".bak" not in path.name.lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
grouped: Dict[str, List[Path]] = defaultdict(list)
|
||||||
|
for path in candidates:
|
||||||
|
grouped[_logical_figure_key(path)].append(path)
|
||||||
|
|
||||||
|
inventory: List[Dict[str, Any]] = []
|
||||||
|
seen_content: Dict[str, str] = {}
|
||||||
|
preference = {".png": 0, ".jpg": 1, ".jpeg": 1, ".svg": 2, ".pdf": 3}
|
||||||
|
for _, paths in sorted(grouped.items()):
|
||||||
|
paths.sort(key=lambda p: (preference.get(p.suffix.lower(), 9), p.name))
|
||||||
|
preferred = paths[0]
|
||||||
|
try:
|
||||||
|
digest = hashlib.sha256(preferred.read_bytes()).hexdigest()
|
||||||
|
except OSError:
|
||||||
|
digest = ""
|
||||||
|
if digest and digest in seen_content:
|
||||||
|
continue
|
||||||
|
if digest:
|
||||||
|
seen_content[digest] = preferred.name
|
||||||
|
mapped = artifact_map.get(preferred.name, {})
|
||||||
|
role = mapped.get("role", "")
|
||||||
|
stage = mapped.get("stage", "") or _infer_stage(preferred.name, role)
|
||||||
|
inventory.append({
|
||||||
|
"figure_id": f"FIG-{len(inventory) + 1:03d}",
|
||||||
|
"file": preferred.name,
|
||||||
|
"stage": stage,
|
||||||
|
"artifact_role": role,
|
||||||
|
"available_formats": sorted({p.suffix.lower().lstrip(".") for p in paths}),
|
||||||
|
"marker": f"[[FIG:{preferred.name}]]",
|
||||||
|
})
|
||||||
|
return inventory
|
||||||
|
|
||||||
|
|
||||||
|
def _foundation_identify(final_dir: Path, prefix: str) -> Dict[str, Any]:
|
||||||
|
candidates: List[Path] = []
|
||||||
|
exact = final_dir / f"{prefix}.identify.manifest.json"
|
||||||
|
if exact.is_file():
|
||||||
|
candidates.append(exact)
|
||||||
|
candidates.extend(
|
||||||
|
path for path in final_dir.glob(f"{prefix}*.identify.manifest.json")
|
||||||
|
if path not in candidates
|
||||||
|
)
|
||||||
|
for path in candidates:
|
||||||
|
manifest = _read_json(path)
|
||||||
|
if manifest.get("stage") == "identify" and manifest.get("status") in {"success", "partial_success"}:
|
||||||
|
bundle = extract_stage_evidence("identify", manifest)
|
||||||
|
return {
|
||||||
|
"manifest": path.name,
|
||||||
|
"scope_id": manifest.get("scope_id", ""),
|
||||||
|
"status": manifest.get("status", ""),
|
||||||
|
"metrics": manifest.get("metrics", {}),
|
||||||
|
"parsed": bundle.get("parsed", {}),
|
||||||
|
"evidence": bundle.get("evidence", []),
|
||||||
|
"risks": bundle.get("risks", []),
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _compact_report(report: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
evidence_cards = report.get("evidence_cards") or []
|
||||||
|
return {
|
||||||
|
"metadata": report.get("metadata", {}),
|
||||||
|
"source_summary": report.get("source_summary", {}),
|
||||||
|
"stage_flow": report.get("stage_flow", {}),
|
||||||
|
"key_findings": report.get("key_findings", []),
|
||||||
|
"structured_result_interpretations": report.get("structured_result_interpretations", {}),
|
||||||
|
"grouped_result_interpretations": report.get("grouped_result_interpretations", {}),
|
||||||
|
"scientific_discoveries": report.get("scientific_discoveries", []),
|
||||||
|
"risk_flags": report.get("risk_flags", []),
|
||||||
|
"stage_reviews": report.get("stage_reviews", []),
|
||||||
|
"publication_claims": report.get("publication_claims", []),
|
||||||
|
"major_claims": report.get("major_claims", []),
|
||||||
|
"next_steps": report.get("next_steps", []),
|
||||||
|
"evidence_cards": evidence_cards[:100],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_summary_evidence(
|
||||||
|
*,
|
||||||
|
final_dir: Path,
|
||||||
|
prefix: str,
|
||||||
|
job_id: str,
|
||||||
|
summaries: List[Dict[str, Any]],
|
||||||
|
scope_id: str,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
report = build_evidence_report(
|
||||||
|
final_dir,
|
||||||
|
prefix,
|
||||||
|
job_id=job_id,
|
||||||
|
scope_id=scope_id,
|
||||||
|
analysis_route="target_plus_reference" if "target_plus_reference" in scope_id else "",
|
||||||
|
)
|
||||||
|
if not report:
|
||||||
|
raise DeepSeekSummaryError(f"No evidence report could be built for scope: {scope_id or 'default'}")
|
||||||
|
evidence = _compact_report(report)
|
||||||
|
included = list((evidence.get("metadata") or {}).get("included_stages") or [])
|
||||||
|
identify = _foundation_identify(final_dir, prefix)
|
||||||
|
if identify and "identify" not in included:
|
||||||
|
evidence["foundation_identify"] = identify
|
||||||
|
included.insert(0, "identify")
|
||||||
|
identify_metrics = identify.get("metrics") or {}
|
||||||
|
final_members = identify_metrics.get("final_members")
|
||||||
|
candidate_members = identify_metrics.get("candidate_members")
|
||||||
|
hmm_members = identify_metrics.get("hmm_members")
|
||||||
|
blast_members = identify_metrics.get("blast_members")
|
||||||
|
evidence.setdefault("structured_result_interpretations", {})["identify"] = {
|
||||||
|
"stage": "identify",
|
||||||
|
"stage_name": "基因家族成员鉴定",
|
||||||
|
"status": identify.get("status") or "success",
|
||||||
|
"interpretation_level": "publishable_result",
|
||||||
|
"result_interpretation": (
|
||||||
|
f"目标物种全蛋白集共有 {identify_metrics.get('total_proteins', 'N/A')} 条蛋白;"
|
||||||
|
f"HMM 搜索得到 {hmm_members} 个命中,BLAST 搜索记录 {blast_members} 个命中,"
|
||||||
|
f"联合筛选形成 {candidate_members} 个候选,最终确认 {final_members} 个家族成员。"
|
||||||
|
"HMM、BLAST、候选集和最终集的关系必须按本地记录描述,不能自行写成取交集或取并集。"
|
||||||
|
),
|
||||||
|
"evidence_basis": identify.get("evidence") or [],
|
||||||
|
"limitations": [
|
||||||
|
"鉴定证据来自目标物种 target_only 基础清单,并作为 target_plus_reference 后续分析的成员基础。",
|
||||||
|
"鉴定成功不能直接证明所有成员具有相同功能。",
|
||||||
|
],
|
||||||
|
"boundary": "不得把全基因组蛋白数写成家族成员数,也不得猜测联合筛选的集合运算方式。",
|
||||||
|
}
|
||||||
|
metadata = evidence["metadata"]
|
||||||
|
metadata["included_stages"] = included
|
||||||
|
metadata["included_stages_for_deepseek"] = included
|
||||||
|
missing = list(evidence["metadata"].get("missing_expected_stages") or [])
|
||||||
|
missing = [x for x in missing if x != "identify"]
|
||||||
|
metadata["missing_expected_stages"] = missing
|
||||||
|
metadata["missing_expected_stages_for_deepseek"] = missing
|
||||||
|
expected = list(metadata.get("expected_stages") or [])
|
||||||
|
metadata["completed_expected_stages"] = [stage for stage in expected if stage in included]
|
||||||
|
metadata["stage_count"] = len(included)
|
||||||
|
metadata["is_final"] = bool(expected) and not missing
|
||||||
|
stage_flow = evidence.setdefault("stage_flow", {})
|
||||||
|
stages = list(stage_flow.get("stages") or [])
|
||||||
|
stages.insert(0, {
|
||||||
|
"id": "identify",
|
||||||
|
"name": "基因家族成员鉴定",
|
||||||
|
"status": identify.get("status") or "success",
|
||||||
|
"key_result": f"最终确认 {final_members} 个成员",
|
||||||
|
})
|
||||||
|
stage_flow["stages"] = stages
|
||||||
|
stage_flow["visualization"] = " -> ".join(item.get("id", "") for item in stages)
|
||||||
|
source_summary = evidence.setdefault("source_summary", {})
|
||||||
|
source_summary["manifest_count"] = int(source_summary.get("manifest_count") or 0) + 1
|
||||||
|
evidence["figures"] = collect_figure_inventory(final_dir, summaries, scope_id=scope_id)
|
||||||
|
return evidence
|
||||||
|
|
||||||
|
|
||||||
|
def _system_prompt() -> str:
|
||||||
|
return """你是植物基因家族分析结果报告作者。你只能使用用户提供的本地证据,不能补造数字、成员、分组、功能或文献。
|
||||||
|
任务是把机器结果写成普通研究人员能直接看懂的中文报告。少用专业词;首次出现的专业词必须用一句日常语言解释。
|
||||||
|
每张图必须逐图解读,但不能假装看到了证据包未提供的像素细节。若只有文件名和阶段事实,应明确写“结合该阶段统计进行解释,具体成员级图形细节需打开原图复核”。
|
||||||
|
所有关联性、预测性结果都必须保留边界,不得使用“证明、决定、必然、直接调控”等过强措辞。
|
||||||
|
风险清单优先级高于一般解释。高风险阶段不得写成可靠生物学结论。
|
||||||
|
输出只允许完整 Markdown 正文,不要输出代码围栏、JSON、写作说明或思考过程。"""
|
||||||
|
|
||||||
|
|
||||||
|
def _user_prompt(*, framework: str, evidence: Dict[str, Any], job_id: str, prefix: str, scope_id: str) -> str:
|
||||||
|
figures = evidence.get("figures") or []
|
||||||
|
return f"""请为真实任务生成最终中文报告。
|
||||||
|
|
||||||
|
任务编号:{job_id}
|
||||||
|
分析前缀:{prefix}
|
||||||
|
分析范围:{scope_id or '默认范围'}
|
||||||
|
|
||||||
|
硬性要求:
|
||||||
|
1. 按“鉴定→系统发育与分类→motif→domain→基因结构→组合图→启动子/TFBS→种内与种间共线性→染色体分布→ortholog→PEP→综合结论”的顺序组织。
|
||||||
|
2. 首页给出普通读者能看懂的一页式摘要,并列出完成阶段、缺失阶段和最重要风险。
|
||||||
|
3. 对 figures 中的每个逻辑图写一个小节,并原样保留对应 marker,例如 [[FIG:xxx.png]]。共有 {len(figures)} 个逻辑图;同一图的 PDF/PNG 不重复解释。
|
||||||
|
4. 每个图小节写:图用来回答什么、从本任务事实能看出什么、通俗解释、不能说明什么。没有足够的成员级数值时必须直说,不能编造。
|
||||||
|
5. 核型阶段存在高风险时,必须明确说明不能据图得出染色体分布结论。
|
||||||
|
6. 种内共线性没有检测到家族成员基因对时,不能写成片段复制驱动家族扩张。
|
||||||
|
7. 最终成员数以 identify 事实为准;全基因组背景数量不能写成家族成员数。
|
||||||
|
8. 报告末尾给出候选成员筛选框架、主要限制、下一步验证建议和“读者最需要记住的五句话”。
|
||||||
|
9. 不要保留任何 {{{{占位符}}}}、TODO 或空表格;没有数据的内容直接写“本任务未提供”。
|
||||||
|
10. 语言以短句为主,结论先行。专业词后立即补通俗解释。
|
||||||
|
|
||||||
|
以下是报告结构参考。它决定覆盖范围和表达方式,不代表其中的示例数字是本任务事实:
|
||||||
|
|
||||||
|
--- 框架开始 ---
|
||||||
|
{framework}
|
||||||
|
--- 框架结束 ---
|
||||||
|
|
||||||
|
以下 JSON 是本任务唯一允许使用的事实来源:
|
||||||
|
|
||||||
|
--- 本地证据开始 ---
|
||||||
|
{json.dumps(evidence, ensure_ascii=False, separators=(',', ':'))}
|
||||||
|
--- 本地证据结束 ---
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _strip_fence(text: str) -> str:
|
||||||
|
value = str(text or "").strip()
|
||||||
|
if value.startswith("```"):
|
||||||
|
value = re.sub(r"^```(?:markdown|md)?\s*", "", value, flags=re.I)
|
||||||
|
value = re.sub(r"\s*```$", "", value)
|
||||||
|
return value.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_report(report: str, *, job_id: str, figures: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||||
|
markers = [str(item.get("marker") or "") for item in figures]
|
||||||
|
missing = [marker for marker in markers if marker and marker not in report]
|
||||||
|
heading_count = len(re.findall(r"(?m)^##+\s+", report))
|
||||||
|
placeholders = sorted(set(re.findall(r"\{\{[^{}]+\}\}", report)))
|
||||||
|
return {
|
||||||
|
"character_count": len(report),
|
||||||
|
"heading_count": heading_count,
|
||||||
|
"job_id_present": job_id in report,
|
||||||
|
"figure_count": len(markers),
|
||||||
|
"covered_figure_count": len(markers) - len(missing),
|
||||||
|
"missing_figure_markers": missing,
|
||||||
|
"placeholders": placeholders,
|
||||||
|
"passed": bool(len(report) >= 4000 and heading_count >= 8 and job_id in report and not missing and not placeholders),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _supplement_prompt(missing: List[str], evidence: Dict[str, Any]) -> str:
|
||||||
|
wanted = [item for item in evidence.get("figures") or [] if item.get("marker") in set(missing)]
|
||||||
|
return f"""上一版报告漏掉了部分图。只输出一个 Markdown 章节,标题为“## 补充图表解读”。
|
||||||
|
必须逐项原样保留 marker,并为每项写:图的用途、本地证据允许的解释、不能得出的结论。
|
||||||
|
不得补造图中具体成员、颜色、分组或数值。
|
||||||
|
|
||||||
|
缺失图:
|
||||||
|
{json.dumps(wanted, ensure_ascii=False)}
|
||||||
|
|
||||||
|
可用阶段解释和风险:
|
||||||
|
{json.dumps({'structured': evidence.get('structured_result_interpretations', {}), 'risks': evidence.get('risk_flags', [])}, ensure_ascii=False)}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _items_for_stages(items: Iterable[Any], stages: set[str]) -> List[Any]:
|
||||||
|
selected: List[Any] = []
|
||||||
|
for item in items or []:
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
continue
|
||||||
|
item_stages = item.get("stages") or [item.get("stage")]
|
||||||
|
if isinstance(item_stages, str):
|
||||||
|
item_stages = [item_stages]
|
||||||
|
if stages.intersection(str(stage) for stage in item_stages if stage):
|
||||||
|
selected.append(item)
|
||||||
|
return selected
|
||||||
|
|
||||||
|
|
||||||
|
def _group_prompt(
|
||||||
|
*,
|
||||||
|
title: str,
|
||||||
|
stages: List[str],
|
||||||
|
evidence: Dict[str, Any],
|
||||||
|
figures: List[Dict[str, Any]],
|
||||||
|
) -> str:
|
||||||
|
stage_set = set(stages)
|
||||||
|
structured = evidence.get("structured_result_interpretations") or {}
|
||||||
|
payload = {
|
||||||
|
"stages": stages,
|
||||||
|
"structured_result_interpretations": {
|
||||||
|
stage: structured.get(stage) for stage in stages if structured.get(stage)
|
||||||
|
},
|
||||||
|
"key_findings": _items_for_stages(evidence.get("key_findings") or [], stage_set),
|
||||||
|
"risk_flags": _items_for_stages(evidence.get("risk_flags") or [], stage_set),
|
||||||
|
"stage_reviews": _items_for_stages(evidence.get("stage_reviews") or [], stage_set),
|
||||||
|
"evidence_cards": _items_for_stages(evidence.get("evidence_cards") or [], stage_set)[:40],
|
||||||
|
"figures": figures,
|
||||||
|
}
|
||||||
|
if "identify" in stage_set and evidence.get("foundation_identify"):
|
||||||
|
payload["foundation_identify"] = evidence["foundation_identify"]
|
||||||
|
return f"""请只撰写报告章节“## {title}”,不要写整篇报告,也不要重复首页信息。
|
||||||
|
覆盖阶段:{', '.join(stages)}。
|
||||||
|
|
||||||
|
要求:
|
||||||
|
- 先用一段通俗文字说明这些阶段得到了什么,再按图逐项解释。
|
||||||
|
- 共有 {len(figures)} 个逻辑图。每张图必须建立三级标题,并原样保留 marker。
|
||||||
|
- 每个图写“图的用途—本任务可确认的结果—通俗解释—结论边界”。
|
||||||
|
- 没有成员级或像素级事实时,必须写需要打开原图复核,不能猜颜色、成员和具体分组。
|
||||||
|
- 风险提示优先。高风险结果必须明确降级。
|
||||||
|
- 本章节最后写一个“阶段小结”,只总结本地证据。
|
||||||
|
- 只输出 Markdown 章节正文。
|
||||||
|
|
||||||
|
本地证据:
|
||||||
|
{json.dumps(payload, ensure_ascii=False, separators=(',', ':'))}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _overview_prompt(*, evidence: Dict[str, Any], job_id: str, prefix: str, scope_id: str) -> str:
|
||||||
|
payload = {
|
||||||
|
"metadata": evidence.get("metadata", {}),
|
||||||
|
"source_summary": evidence.get("source_summary", {}),
|
||||||
|
"stage_flow": evidence.get("stage_flow", {}),
|
||||||
|
"foundation_identify": evidence.get("foundation_identify", {}),
|
||||||
|
"risk_flags": evidence.get("risk_flags", []),
|
||||||
|
}
|
||||||
|
return f"""请撰写基因家族结果报告的开头部分,只输出以下三个二级章节:
|
||||||
|
## 一、项目与数据范围
|
||||||
|
## 二、一页式结论摘要
|
||||||
|
## 三、流程完成情况与可信度
|
||||||
|
|
||||||
|
任务编号:{job_id}
|
||||||
|
前缀:{prefix}
|
||||||
|
范围:{scope_id}
|
||||||
|
|
||||||
|
使用短句和通俗中文。摘要写5—8条最重要结论。必须写清最终成员数、完成阶段、缺失阶段、主要风险和结论边界。
|
||||||
|
禁止补造证据;不能把全基因组背景数写成家族成员数。只输出 Markdown 章节正文。
|
||||||
|
|
||||||
|
本地证据:
|
||||||
|
{json.dumps(payload, ensure_ascii=False, separators=(',', ':'))}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _conclusion_prompt(*, evidence: Dict[str, Any]) -> str:
|
||||||
|
payload = {
|
||||||
|
"scientific_discoveries": evidence.get("scientific_discoveries", []),
|
||||||
|
"publication_claims": evidence.get("publication_claims", []),
|
||||||
|
"major_claims": evidence.get("major_claims", []),
|
||||||
|
"risk_flags": evidence.get("risk_flags", []),
|
||||||
|
"next_steps": evidence.get("next_steps", []),
|
||||||
|
"foundation_identify": evidence.get("foundation_identify", {}),
|
||||||
|
}
|
||||||
|
return f"""请撰写基因家族结果报告的结尾,只输出以下四个二级章节:
|
||||||
|
## 十、候选成员与多证据综合
|
||||||
|
## 十一、主要限制
|
||||||
|
## 十二、下一步验证建议
|
||||||
|
## 十三、读者最需要记住的五句话
|
||||||
|
|
||||||
|
候选成员没有足够成员级证据时,不要强行编排名;改为给出可执行筛选规则和待补证据。
|
||||||
|
把预测和相关结果写成线索,不得写成已证明功能。高风险问题必须进入主要限制并排在修复建议前面。
|
||||||
|
只输出 Markdown 章节正文。
|
||||||
|
|
||||||
|
本地证据:
|
||||||
|
{json.dumps(payload, ensure_ascii=False, separators=(',', ':'))}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _call_section(
|
||||||
|
provider: DeepSeekProvider,
|
||||||
|
prompt: str,
|
||||||
|
*,
|
||||||
|
max_tokens: int,
|
||||||
|
) -> tuple[str, Dict[str, Any]]:
|
||||||
|
response = provider.call(
|
||||||
|
[LLMMessage(role="system", content=_system_prompt()), LLMMessage(role="user", content=prompt)],
|
||||||
|
temperature=0.12,
|
||||||
|
max_tokens=max_tokens,
|
||||||
|
disable_thinking=True,
|
||||||
|
)
|
||||||
|
text = _strip_fence(response.content)
|
||||||
|
if len(text) < 300 or not re.search(r"(?m)^##\s+", text):
|
||||||
|
raise DeepSeekSummaryError(
|
||||||
|
f"DeepSeek returned an incomplete section (finish_reason={response.finish_reason}, chars={len(text)}): {text[:500]}"
|
||||||
|
)
|
||||||
|
return text, {
|
||||||
|
"provider": response.provider,
|
||||||
|
"model": response.model,
|
||||||
|
"usage": response.usage or {},
|
||||||
|
"finish_reason": response.finish_reason,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def generate_deepseek_summary(
|
||||||
|
*,
|
||||||
|
final_dir: Path,
|
||||||
|
prefix: str,
|
||||||
|
job_id: str,
|
||||||
|
summaries: List[Dict[str, Any]],
|
||||||
|
scope_id: str = "",
|
||||||
|
framework_path: Optional[Path] = None,
|
||||||
|
model: str = "",
|
||||||
|
max_tokens: int = 16000,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
final_dir = final_dir.resolve()
|
||||||
|
effective_scope = select_primary_scope(summaries, scope_id)
|
||||||
|
framework_file = (framework_path or DEFAULT_FRAMEWORK).expanduser().resolve()
|
||||||
|
if not framework_file.is_file():
|
||||||
|
raise DeepSeekSummaryError(f"Report framework not found: {framework_file}")
|
||||||
|
evidence = build_summary_evidence(
|
||||||
|
final_dir=final_dir,
|
||||||
|
prefix=prefix,
|
||||||
|
job_id=job_id,
|
||||||
|
summaries=summaries,
|
||||||
|
scope_id=effective_scope,
|
||||||
|
)
|
||||||
|
settings = load_deepseek_settings(model_override=model)
|
||||||
|
provider = DeepSeekProvider(
|
||||||
|
api_key=settings["api_key"],
|
||||||
|
model=settings["model"],
|
||||||
|
temperature=0.15,
|
||||||
|
max_tokens=max_tokens,
|
||||||
|
api_base=settings["base_url"],
|
||||||
|
)
|
||||||
|
started = time.time()
|
||||||
|
framework = framework_file.read_text(encoding="utf-8", errors="ignore")
|
||||||
|
framework_outline = "\n".join(
|
||||||
|
line for line in framework.splitlines() if line.startswith("## ")
|
||||||
|
)
|
||||||
|
section_records: List[Dict[str, Any]] = []
|
||||||
|
overview, call_meta = _call_section(
|
||||||
|
provider,
|
||||||
|
_overview_prompt(
|
||||||
|
evidence=evidence,
|
||||||
|
job_id=job_id,
|
||||||
|
prefix=prefix,
|
||||||
|
scope_id=effective_scope,
|
||||||
|
) + "\n\n报告框架章节纲要:\n" + framework_outline,
|
||||||
|
max_tokens=min(max_tokens, 4500),
|
||||||
|
)
|
||||||
|
section_records.append({"section": "overview", **call_meta})
|
||||||
|
|
||||||
|
figures = evidence.get("figures") or []
|
||||||
|
group_specs = [
|
||||||
|
(
|
||||||
|
"四、成员鉴定、系统发育与分类",
|
||||||
|
["identify", "phylogeny", "reference_classification", "subfamily_confirm"],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"五、保守片段、功能区域、基因结构与组合证据",
|
||||||
|
["motif", "domain", "structure", "combo"],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"六、启动子与 TFBS 调控线索",
|
||||||
|
["promoter", "tfbs"],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"七、基因组分布、共线性与跨物种关系",
|
||||||
|
["synteny", "interspecies_synteny", "karyotype", "ortholog", "kaks"],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"八、候选蛋白 PEP 特征",
|
||||||
|
["pep"],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"九、其他结果图",
|
||||||
|
["other"],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
sections = [overview]
|
||||||
|
covered_stages: set[str] = set()
|
||||||
|
for title, stages in group_specs:
|
||||||
|
group_figures = [item for item in figures if str(item.get("stage") or "other") in set(stages)]
|
||||||
|
structured = evidence.get("structured_result_interpretations") or {}
|
||||||
|
has_stage_evidence = any(structured.get(stage) for stage in stages) or (
|
||||||
|
"identify" in stages and evidence.get("foundation_identify")
|
||||||
|
)
|
||||||
|
if not group_figures and not has_stage_evidence:
|
||||||
|
continue
|
||||||
|
section_text, call_meta = _call_section(
|
||||||
|
provider,
|
||||||
|
_group_prompt(title=title, stages=stages, evidence=evidence, figures=group_figures),
|
||||||
|
max_tokens=min(max_tokens, 6500),
|
||||||
|
)
|
||||||
|
sections.append(section_text)
|
||||||
|
section_records.append({
|
||||||
|
"section": title,
|
||||||
|
"stages": stages,
|
||||||
|
"figure_count": len(group_figures),
|
||||||
|
**call_meta,
|
||||||
|
})
|
||||||
|
covered_stages.update(stages)
|
||||||
|
|
||||||
|
conclusion, call_meta = _call_section(
|
||||||
|
provider,
|
||||||
|
_conclusion_prompt(evidence=evidence),
|
||||||
|
max_tokens=min(max_tokens, 5000),
|
||||||
|
)
|
||||||
|
sections.append(conclusion)
|
||||||
|
section_records.append({"section": "conclusion", **call_meta})
|
||||||
|
report = (
|
||||||
|
f"# {prefix} 基因家族分析结果通俗解读报告\n\n"
|
||||||
|
f"任务编号:`{job_id}` \n"
|
||||||
|
f"分析范围:`{effective_scope or 'default'}` \n"
|
||||||
|
f"生成方式:DeepSeek API 分段生成;本地证据锁定 \n\n"
|
||||||
|
+ "\n\n".join(sections)
|
||||||
|
)
|
||||||
|
validation = _validate_report(report, job_id=job_id, figures=evidence.get("figures") or [])
|
||||||
|
supplement_usage: Dict[str, Any] = {}
|
||||||
|
if validation["missing_figure_markers"]:
|
||||||
|
supplement = provider.call(
|
||||||
|
[
|
||||||
|
LLMMessage(role="system", content=_system_prompt()),
|
||||||
|
LLMMessage(role="user", content=_supplement_prompt(validation["missing_figure_markers"], evidence)),
|
||||||
|
],
|
||||||
|
temperature=0.1,
|
||||||
|
max_tokens=min(max_tokens, 8000),
|
||||||
|
disable_thinking=True,
|
||||||
|
)
|
||||||
|
supplement_text = _strip_fence(supplement.content)
|
||||||
|
if supplement_text:
|
||||||
|
report = report.rstrip() + "\n\n" + supplement_text + "\n"
|
||||||
|
supplement_usage = supplement.usage or {}
|
||||||
|
section_records.append({
|
||||||
|
"section": "missing_figure_supplement",
|
||||||
|
"usage": supplement_usage,
|
||||||
|
"finish_reason": supplement.finish_reason,
|
||||||
|
})
|
||||||
|
validation = _validate_report(report, job_id=job_id, figures=evidence.get("figures") or [])
|
||||||
|
if not validation["passed"]:
|
||||||
|
raise DeepSeekSummaryError(
|
||||||
|
"DeepSeek report failed validation: "
|
||||||
|
+ json.dumps(validation, ensure_ascii=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
scope_token = re.sub(r"[^0-9A-Za-z_.-]+", "-", effective_scope).strip(".-")
|
||||||
|
name_root = f"{prefix}.{scope_token}" if scope_token else prefix
|
||||||
|
report_path = final_dir / f"{name_root}.summary_report.md"
|
||||||
|
evidence_path = final_dir / f"{name_root}.summary.evidence.json"
|
||||||
|
metadata_path = final_dir / f"{name_root}.summary.deepseek.json"
|
||||||
|
evidence_path.write_text(json.dumps(evidence, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
report_path.write_text(report.rstrip() + "\n", encoding="utf-8")
|
||||||
|
metadata = {
|
||||||
|
"schema": "deepseek_summary_generation_v1",
|
||||||
|
"status": "success",
|
||||||
|
"generated_at": datetime.now().isoformat(),
|
||||||
|
"job_id": job_id,
|
||||||
|
"prefix": prefix,
|
||||||
|
"scope_id": effective_scope,
|
||||||
|
"provider": "deepseek",
|
||||||
|
"model": settings["model"],
|
||||||
|
"base_url": settings["base_url"],
|
||||||
|
"duration_seconds": round(time.time() - started, 3),
|
||||||
|
"section_calls": section_records,
|
||||||
|
"supplement_usage": supplement_usage,
|
||||||
|
"framework": str(framework_file),
|
||||||
|
"evidence_file": str(evidence_path),
|
||||||
|
"report_file": str(report_path),
|
||||||
|
"validation": validation,
|
||||||
|
}
|
||||||
|
metadata_path.write_text(json.dumps(metadata, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
return {
|
||||||
|
"scope_id": effective_scope,
|
||||||
|
"report_path": report_path,
|
||||||
|
"evidence_path": evidence_path,
|
||||||
|
"metadata_path": metadata_path,
|
||||||
|
"metadata": metadata,
|
||||||
|
}
|
||||||
@@ -58,6 +58,7 @@ class DeepSeekProvider(BaseLLMProvider):
|
|||||||
tools = kwargs.get("tools")
|
tools = kwargs.get("tools")
|
||||||
if tools is not None:
|
if tools is not None:
|
||||||
request_kwargs["tools"] = tools
|
request_kwargs["tools"] = tools
|
||||||
|
if tools is not None or kwargs.get("disable_thinking", False):
|
||||||
request_kwargs["extra_body"] = {"thinking": {"type": "disabled"}}
|
request_kwargs["extra_body"] = {"thinking": {"type": "disabled"}}
|
||||||
tool_choice = kwargs.get("tool_choice")
|
tool_choice = kwargs.get("tool_choice")
|
||||||
if tool_choice is not None:
|
if tool_choice is not None:
|
||||||
|
|||||||
@@ -804,11 +804,14 @@ CAPABILITIES: Dict[str, StageCapability] = {
|
|||||||
],
|
],
|
||||||
provides=[
|
provides=[
|
||||||
ArtifactProduct("summary_report", "markdown", "{prefix}.summary_report.md"),
|
ArtifactProduct("summary_report", "markdown", "{prefix}.summary_report.md"),
|
||||||
|
ArtifactProduct("summary_evidence_index", "markdown", "{prefix}.summary_evidence_index.md"),
|
||||||
|
ArtifactProduct("summary_evidence", "json", "{prefix}.summary.evidence.json"),
|
||||||
|
ArtifactProduct("deepseek_metadata", "json", "{prefix}.summary.deepseek.json"),
|
||||||
ArtifactProduct("summary_manifest", "manifest", "{prefix}.summary.manifest.json"),
|
ArtifactProduct("summary_manifest", "manifest", "{prefix}.summary.manifest.json"),
|
||||||
],
|
],
|
||||||
scope_modes=["target_only", "target_plus_reference", "all_selected", "rnaseq"],
|
scope_modes=["target_only", "target_plus_reference", "all_selected", "rnaseq"],
|
||||||
preflight_checks=["at_least_one_success_manifest"],
|
preflight_checks=["at_least_one_success_manifest"],
|
||||||
postflight_checks=["summary_contains_stage_evidence"],
|
postflight_checks=["summary_contains_stage_evidence", "deepseek_report_generation_proven"],
|
||||||
repair_candidates=["manifest_recovery", "artifact_reresolve"],
|
repair_candidates=["manifest_recovery", "artifact_reresolve"],
|
||||||
rerun_policy="auto_after_manifest_recovery",
|
rerun_policy="auto_after_manifest_recovery",
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
## 阶段信息
|
## 阶段信息
|
||||||
|
|
||||||
- **阶段名称**: summary
|
- **阶段名称**: summary
|
||||||
- **阶段描述**: 汇总整个 job 的 manifest、artifact、核心指标和一致性检查;作为结果入口和质控层,不承担深度生物学解读。
|
- **阶段描述**: 先汇总整个 job 的 manifest、artifact、核心指标和一致性检查,再通过 DeepSeek API 将本地证据写成通俗、可追溯的结果解读报告。
|
||||||
- **脚本**: `apps/cli/run_summary.py`
|
- **脚本**: `apps/cli/run_summary.py`
|
||||||
|
|
||||||
## 输入契约
|
## 输入契约
|
||||||
@@ -30,15 +30,18 @@ Summary 应囊括所有已运行阶段,包括但不限于:
|
|||||||
| 文件 | 类型 | 描述 |
|
| 文件 | 类型 | 描述 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `{prefix}.summary_report.md` | markdown | 人可读汇总报告 |
|
| `{prefix}.summary_report.md` | markdown | 人可读汇总报告 |
|
||||||
|
| `{prefix}.summary_evidence_index.md` | markdown | 确定性结果索引与完整性检查;不经过 LLM |
|
||||||
|
| `{prefix}.summary.evidence.json` | json | 提交给 DeepSeek 的本地事实包 |
|
||||||
|
| `{prefix}.summary.deepseek.json` | json | 模型、用量、图表覆盖率和报告校验记录 |
|
||||||
| `{prefix}.summary.files.tsv` | tsv | artifact 文件索引 |
|
| `{prefix}.summary.files.tsv` | tsv | artifact 文件索引 |
|
||||||
| `{prefix}.summary.manifest.json` | json | 机器可读总汇总 |
|
| `{prefix}.summary.manifest.json` | json | 机器可读总汇总 |
|
||||||
| `{prefix}.{scope_id}.summary_report.md` | markdown | 指定 scope 的报告 |
|
| `{prefix}.{scope_id}.summary_report.md` | markdown | 指定 scope 的报告 |
|
||||||
| `{prefix}.{scope_id}.summary.files.tsv` | tsv | 指定 scope 的文件索引 |
|
| `{prefix}.{scope_id}.summary.files.tsv` | tsv | 指定 scope 的文件索引 |
|
||||||
| `{prefix}.{scope_id}.summary.manifest.json` | json | 指定 scope 的机器汇总 |
|
| `{prefix}.{scope_id}.summary.manifest.json` | json | 指定 scope 的机器汇总 |
|
||||||
|
|
||||||
## Summary 与深度解读的边界
|
## Summary 的确定性层与 DeepSeek 层
|
||||||
|
|
||||||
Summary 做:
|
确定性层做:
|
||||||
|
|
||||||
- 阶段状态汇总
|
- 阶段状态汇总
|
||||||
- 输出文件索引
|
- 输出文件索引
|
||||||
@@ -46,11 +49,18 @@ Summary 做:
|
|||||||
- scope/family/species 一致性检查
|
- scope/family/species 一致性检查
|
||||||
- 缺失依赖或 artifact 检查
|
- 缺失依赖或 artifact 检查
|
||||||
|
|
||||||
Summary 不做:
|
DeepSeek 层做:
|
||||||
|
|
||||||
- 论文式结果解释
|
- 读取确定性层生成的证据包,不直接扫描和猜测未知结果
|
||||||
- 候选基因排序的主观判断
|
- 按分析阶段逐项解释结果和每一个逻辑图
|
||||||
- 跨数据库机制推断
|
- 用短句和通俗中文说明“看到了什么、可能说明什么、不能说明什么”
|
||||||
- 文献背景补全
|
- 汇总候选成员证据、限制和下一步验证建议
|
||||||
|
|
||||||
深度解读应读取 `summary.manifest.json` 和关键结果文件,作为单独阶段执行。
|
DeepSeek 层禁止:
|
||||||
|
|
||||||
|
- 补造本地证据中没有的数字、成员、分组、功能或文献
|
||||||
|
- 把预测、相关或候选线索写成已经证明的功能
|
||||||
|
- 在高风险或缺失阶段给出肯定结论
|
||||||
|
- 把全基因组背景数量误写成家族成员数
|
||||||
|
|
||||||
|
默认运行必须成功调用 DeepSeek API 并通过报告完整性检查;`--no-deepseek` 只用于离线测试或管理员排障,生成物必须标记为确定性回退报告。
|
||||||
|
|||||||
@@ -1,137 +0,0 @@
|
|||||||
# 2026-03-27 修订记录 - Promoter分析可视化完善
|
|
||||||
|
|
||||||
## 项目
|
|
||||||
bbav20311 - Promoter分析模块
|
|
||||||
|
|
||||||
## 今日完成内容
|
|
||||||
|
|
||||||
### 1. Promoter分析核心功能增强
|
|
||||||
|
|
||||||
#### 1.1 unknown/Unnamed元件过滤
|
|
||||||
- **问题**: PlantCARE输出包含大量"unknown"和"Unnamed__X"元件
|
|
||||||
- **解决**: 在`parse_plantcare_output()`中添加过滤逻辑
|
|
||||||
- **效果**: 4,271 → 3,699有效元件(过滤572个)
|
|
||||||
- **输出**: `plantCARE_output_filtered.tab`, `filtered_elements_stats.txt`
|
|
||||||
|
|
||||||
#### 1.2 启动子分析报告生成
|
|
||||||
- **新增**: `generate_promoter_report()`函数
|
|
||||||
- **内容**:
|
|
||||||
- 分析概览、Top 20元件、13类别统计
|
|
||||||
- 生物学解读(激素、胁迫、TF结合等)
|
|
||||||
- 高级分析: 富集分析、共现模式、复杂度评分
|
|
||||||
- **输出**: `{prefix}.promoter_report.md`
|
|
||||||
|
|
||||||
#### 1.3 高级分析功能
|
|
||||||
| 分析类型 | 函数 | 输出文件 | 结果 |
|
|
||||||
|---------|------|---------|------|
|
|
||||||
| 亚家族富集 | `calculate_enrichment()` | `{prefix}_enrichment_analysis.txt` | 160个富集对 |
|
|
||||||
| 元件共现 | `calculate_cooccurrence()` | `{prefix}_cooccurrence_analysis.txt` | 1,819个显著对 |
|
|
||||||
| 复杂度评分 | `calculate_promoter_complexity()` | `{prefix}_promoter_complexity.txt` | 23个基因分析 |
|
|
||||||
|
|
||||||
### 2. 增强可视化 (8种图表)
|
|
||||||
|
|
||||||
#### 2.1 最终版本V4
|
|
||||||
**脚本**: `scripts/promoter_v4_final.R`
|
|
||||||
|
|
||||||
| 编号 | 图表类型 | 说明 |
|
|
||||||
|------|----------|------|
|
|
||||||
| 01 | 三合一组合 | 外部树图+7分类结构+红绿热图 |
|
|
||||||
| 02 | 7大分类细分 | 每类Top 10元件 |
|
|
||||||
| 03 | 全热图 | 全基因×全元件,红绿配色 |
|
|
||||||
| 04 | 堆叠图 | 7分类堆叠 |
|
|
||||||
|
|
||||||
#### 2.2 关键改进
|
|
||||||
- **进化树图**: 使用`pdftoppm`将PDF转为PNG读取,保留原有样式
|
|
||||||
- **红绿配色**: RdYlGn调色板
|
|
||||||
- `#d73027`(深红) → `#fc8d59`(橙红) → `#fee08b`(黄)
|
|
||||||
- `#d9ef8b`(浅绿) → `#91cf60`(绿) → `#1a9850`(深绿)
|
|
||||||
- **图例**: 热图右侧显示Count图例
|
|
||||||
|
|
||||||
#### 2.3 7大分类定义
|
|
||||||
| 分类 | 颜色 | 包含元件 |
|
|
||||||
|------|------|---------|
|
|
||||||
| Core promoter | #e41a1c | TATA, CAAT, Inr |
|
|
||||||
| Light response | #377eb8 | G-box, GT1, I-box |
|
|
||||||
| Hormone signaling | #4daf4a | ABRE, GA, JA, SA, Auxin, Ethylene |
|
|
||||||
| Stress response | #984ea3 | DRE, LTR, ARE, STRE |
|
|
||||||
| TF binding | #ff7f00 | MYB, MYC, WRKY |
|
|
||||||
| Development | #f781bf | RY, AAGAA, CAT |
|
|
||||||
| Other | #999999 | 未分类 |
|
|
||||||
|
|
||||||
### 3. 修改的文件
|
|
||||||
|
|
||||||
#### Python代码
|
|
||||||
- `apps/cli/run_promoter.py`
|
|
||||||
- 添加`should_filter_element()`
|
|
||||||
- 添加`categorize_element()`
|
|
||||||
- 添加`generate_promoter_report()`
|
|
||||||
- 添加`calculate_enrichment()`
|
|
||||||
- 添加`calculate_cooccurrence()`
|
|
||||||
- 添加`calculate_promoter_complexity()`
|
|
||||||
|
|
||||||
#### R代码
|
|
||||||
- `scripts/promoter_v4_final.R` (最终版本)
|
|
||||||
|
|
||||||
#### 修订记录文档
|
|
||||||
- `REVISION_PROMOTER_20250327.md`
|
|
||||||
- `REVISION_PROMOTER_V2_20250327.md`
|
|
||||||
- `REVISION_PROMOTER_V3_20250327.md`
|
|
||||||
- `REVISION_PROMOTER_V4_20250327.md`
|
|
||||||
- `REVISION_PROMOTER_V4_FINAL_20250327.md`
|
|
||||||
|
|
||||||
### 4. 生物学发现 (Ft.SPL示例)
|
|
||||||
|
|
||||||
#### 元件分布
|
|
||||||
- Core promoter: 2,565 (69.3%) - TATA-box(1,460), CAAT-box(852)
|
|
||||||
- Hormone signaling: 255 (6.9%)
|
|
||||||
- Light response: 188 (5.1%)
|
|
||||||
- Stress response: 177 (4.8%)
|
|
||||||
- TF binding: 196 (5.3%)
|
|
||||||
|
|
||||||
#### 共现模式
|
|
||||||
- CAAT-box + MYB: 23基因
|
|
||||||
- CAAT-box + TATA-box: 23基因
|
|
||||||
- Hub元件: Box 4, CAAT-box, MYB (各75连接)
|
|
||||||
|
|
||||||
#### 复杂度
|
|
||||||
- 平均: 241.4分
|
|
||||||
- 最高: FtPinG0006337300 (361.44分)
|
|
||||||
- 所有基因均为"Very High"复杂度
|
|
||||||
|
|
||||||
### 5. 运行方式
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Python分析
|
|
||||||
python apps/cli/run_promoter.py \
|
|
||||||
--input-dir input/job_xxx \
|
|
||||||
--workspace workspace/job_xxx \
|
|
||||||
--prefix Ft.SPL
|
|
||||||
|
|
||||||
# R可视化
|
|
||||||
Rscript scripts/promoter_v4_final.R \
|
|
||||||
[树PDF] [分组文件] [启动子文件] [描述文件] [输出前缀]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 测试状态
|
|
||||||
```
|
|
||||||
✅ Syntax check: PASSED
|
|
||||||
✅ Filter function: 572 elements filtered
|
|
||||||
✅ Categorization: 13 categories assigned
|
|
||||||
✅ Report generation: SUCCESS
|
|
||||||
✅ Enrichment analysis: 160 pairs
|
|
||||||
✅ Co-occurrence analysis: 1,819 pairs
|
|
||||||
✅ Complexity scoring: 23 genes
|
|
||||||
✅ R visualization: 8/8 files generated
|
|
||||||
✅ External tree PDF reading: SUCCESS
|
|
||||||
✅ Red-green color scheme: RdYlGn applied
|
|
||||||
```
|
|
||||||
|
|
||||||
## 时间
|
|
||||||
- 开始: 2026-03-27 10:56
|
|
||||||
- 完成: 2026-03-27 20:30
|
|
||||||
- 总时长: ~10小时
|
|
||||||
|
|
||||||
## 备注
|
|
||||||
- 使用test4py311环境运行R脚本
|
|
||||||
- 需要R包: tidyverse, ggtree, treeio, ape, patchwork, aplot, RColorBrewer
|
|
||||||
- 系统依赖: pdftoppm (用于PDF转PNG)
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
# 2026-03-28 ~ 2026-03-29 工作记录 - bbav20311 Promoter分析可视化优化
|
|
||||||
|
|
||||||
> 日期: 2026-03-28 15:09 ~ 2026-03-29 02:00
|
|
||||||
> 参与: xialab的幸运星
|
|
||||||
> 主题: Promoter分析模块可视化升级与热图融合
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 工作目标
|
|
||||||
|
|
||||||
优化Promoter(启动子顺式作用元件)分析模块的可视化输出,融合多种热图绘制方法,创建更符合发表要求的组合图。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 主要工作内容
|
|
||||||
|
|
||||||
### 2.1 热图绘制方法对比与融合
|
|
||||||
|
|
||||||
| 脚本 | 来源 | 特点 | 问题 |
|
|
||||||
|:---|:---|:---|:---|
|
|
||||||
| `promoter_v4_final.R` | 现有V4版本 | 7大分类、外部树图、红绿配色 | 连续渐变不够清晰 |
|
|
||||||
| `promoter_analysis_complete.R` | complete版本 | 方块+数字、蓝白分级 | 元件命名处理逻辑有问题 |
|
|
||||||
| `promoter_v5_fused_heatmap.R` | **融合版** | V4分类 + complete显示方式 | ✅ 采用 |
|
|
||||||
|
|
||||||
**融合策略**:
|
|
||||||
- 保留V4的7大分类逻辑
|
|
||||||
- 采用complete的方块+数字显示
|
|
||||||
- 9级蓝白分级配色
|
|
||||||
|
|
||||||
### 2.2 5面板组合图开发 (V6)
|
|
||||||
|
|
||||||
创建全新的5面板组合图布局:
|
|
||||||
|
|
||||||
```
|
|
||||||
A (左) B (中左) C (中) D (右) E (最右)
|
|
||||||
进化树 + 亚家族竖线 + 7大分类堆叠 + 热图(方块+数字) + 图例
|
|
||||||
```
|
|
||||||
|
|
||||||
**文件**: `scripts/promoter_v6_combined.R`
|
|
||||||
|
|
||||||
**特点**:
|
|
||||||
- 画布自动调整 (9000×6000)
|
|
||||||
- Panel C宽度增加 (8 → 12)
|
|
||||||
- 热图方格为正方形
|
|
||||||
- 实线边框 (grey60)
|
|
||||||
- 0值显示白色背景
|
|
||||||
|
|
||||||
### 2.3 集成到分析流程
|
|
||||||
|
|
||||||
修改 `apps/cli/run_promoter.py`:
|
|
||||||
|
|
||||||
1. **集成V6脚本** - 自动生成5面板组合图
|
|
||||||
2. **保留其他统计图** - 甜甜圈、柱形图、UpSet等
|
|
||||||
3. **移除旧版** - `_02_combined.png` 不再生成
|
|
||||||
4. **修复manifest** - 添加`figures`字段供前端显示
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 关键修改文件
|
|
||||||
|
|
||||||
### 3.1 新增/修改的R脚本
|
|
||||||
|
|
||||||
| 文件 | 路径 | 说明 |
|
|
||||||
|:---|:---|:---|
|
|
||||||
| `promoter_v5_fused_heatmap.R` | `scripts/` | 融合版热图 (V5) |
|
|
||||||
| `promoter_v6_combined.R` | `scripts/` | **5面板组合图 (V6)** |
|
|
||||||
|
|
||||||
### 3.2 修改的Python脚本
|
|
||||||
|
|
||||||
| 文件 | 路径 | 修改内容 |
|
|
||||||
|:---|:---|:---|
|
|
||||||
| `run_promoter.py` | `apps/cli/` | 集成V6,添加figures字段 |
|
|
||||||
|
|
||||||
### 3.3 生成的可视化文件
|
|
||||||
|
|
||||||
| 文件 | 说明 |
|
|
||||||
|:---|:---|
|
|
||||||
| `Ft.SPL_promoter_v6_combined.png` | **新版5面板组合图** |
|
|
||||||
| `Ft.SPL_promoter_01_heatmap.png` | 独立热图 |
|
|
||||||
| `Ft.SPL_promoter_03_element_barplot.png` | 元件柱形图 |
|
|
||||||
| `Ft.SPL_promoter_03b_big7_barplot.png` | 7大分类柱形图 |
|
|
||||||
| `Ft.SPL_promoter_04_category_stacked.png` | 分类堆叠图 |
|
|
||||||
| `Ft.SPL_promoter_05_upset.png` | UpSet图 |
|
|
||||||
| `Ft.SPL_promoter_06_category_donut.png` | 甜甜圈图 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 7大分类定义
|
|
||||||
|
|
||||||
| 分类 | 颜色 | 包含元件 |
|
|
||||||
|:---|:---|:---|
|
|
||||||
| Core promoter | #e41a1c | TATA-box, CAAT-box, Inr, Y-patch |
|
|
||||||
| Light response | #377eb8 | G-box, GT1, I-box, Sp1, Box.4, GATA |
|
|
||||||
| Hormone signaling | #4daf4a | ABRE, GARE, CGTCA, TCA, TGA, ERE |
|
|
||||||
| Stress response | #984ea3 | DRE, LTR, ARE, STRE, W-box |
|
|
||||||
| TF binding | #ff7f00 | MYB, MYC, WRKY |
|
|
||||||
| Development | #f781bf | RY, AAGAA, CAT, GCN4 |
|
|
||||||
| Other | #999999 | 未分类 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 技术细节
|
|
||||||
|
|
||||||
### 5.1 热图颜色分级
|
|
||||||
|
|
||||||
```r
|
|
||||||
count_colors <- c(
|
|
||||||
"[0,1)" = "#f7fbff", # 白色(0值)
|
|
||||||
"[1,2)" = "#c6dbef", # 浅蓝
|
|
||||||
"[2,3)" = "#9ecae1",
|
|
||||||
"[3,5)" = "#6baed6",
|
|
||||||
"[5,10)" = "#4292c6",
|
|
||||||
"[10,20)" = "#2171b5",
|
|
||||||
"[20,50)" = "#08519c",
|
|
||||||
"[50,100)"= "#08306b",
|
|
||||||
"[100,200]"="#041d3a" # 深蓝
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 画布自适应计算
|
|
||||||
|
|
||||||
```r
|
|
||||||
# 根据内容自动计算画布大小
|
|
||||||
total_width <- (x_D_right - x_A_left) + 3 # 加上图例空间
|
|
||||||
total_height <- max_y + 2
|
|
||||||
max_width <- 30
|
|
||||||
max_height <- 20
|
|
||||||
final_width <- min(total_width, max_width)
|
|
||||||
final_height <- min(total_height, max_height)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 测试结果
|
|
||||||
|
|
||||||
### 6.1 测试任务
|
|
||||||
- **任务ID**: job_1774711833127
|
|
||||||
- **基因**: 24个 Ft.SPL 基因
|
|
||||||
- **启动子元件**: 3699条记录
|
|
||||||
|
|
||||||
### 6.2 生成文件验证
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ ls final/Ft.SPL_promoter*.png
|
|
||||||
|
|
||||||
Ft.SPL_promoter_01_heatmap.png (131K)
|
|
||||||
Ft.SPL_promoter_03b_big7_barplot.png (38K)
|
|
||||||
Ft.SPL_promoter_03_element_barplot.png (21K)
|
|
||||||
Ft.SPL_promoter_04_category_stacked.png (68K)
|
|
||||||
Ft.SPL_promoter_05_upset.png (32K)
|
|
||||||
Ft.SPL_promoter_06_category_donut.png (60K)
|
|
||||||
Ft.SPL_promoter_v6_combined.png (701K) # 新版5面板
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.3 Manifest验证
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"figures": [
|
|
||||||
{"name": "Ft.SPL_promoter_01_heatmap", ...},
|
|
||||||
{"name": "Ft.SPL_promoter_03_element_barplot", ...},
|
|
||||||
{"name": "Ft.SPL_promoter_03b_big7_barplot", ...},
|
|
||||||
{"name": "Ft.SPL_promoter_04_category_stacked", ...},
|
|
||||||
{"name": "Ft.SPL_promoter_05_upset", ...},
|
|
||||||
{"name": "Ft.SPL_promoter_06_category_donut", ...},
|
|
||||||
{"name": "Ft.SPL_promoter_v6_combined", ...} # 新版
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 问题与解决
|
|
||||||
|
|
||||||
| 问题 | 原因 | 解决方案 |
|
|
||||||
|:---|:---|:---|
|
|
||||||
| V6图不显示在结果中 | manifest缺少figures字段 | 添加figures数组 |
|
|
||||||
| 堆叠图太拥挤 | Panel C宽度不足 | width_C从8增加到12 |
|
|
||||||
| 热图方格变形 | element_width设置不当 | 改为正方形 (gene_height * 2) |
|
|
||||||
| 旧版_02_combined仍在生成 | 未移除V2调用 | 修改流程,只生成V6和其他统计图 |
|
|
||||||
| 其他统计图丢失 | V2调用被跳过 | 先运行V6,再运行V2生成其他图 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 时间记录
|
|
||||||
|
|
||||||
| 时间段 | 时长 | 内容 |
|
|
||||||
|:---|:---|:---|
|
|
||||||
| 15:09 - 17:18 | 2小时 | 分析热图绘制方法,创建V5融合版 |
|
|
||||||
| 17:18 - 20:00 | 3小时 | 开发V6 5面板组合图 |
|
|
||||||
| 20:00 - 23:30 | 3.5小时 | 集成到流程,修复manifest |
|
|
||||||
| 23:30 - 02:00 | 2.5小时 | 调试优化,测试验证 |
|
|
||||||
| **总计** | **11小时** | **完成所有目标** |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 状态
|
|
||||||
|
|
||||||
- **V5融合版热图**: ✅ 完成
|
|
||||||
- **V6 5面板组合图**: ✅ 完成
|
|
||||||
- **集成到分析流程**: ✅ 完成
|
|
||||||
- **manifest修复**: ✅ 完成
|
|
||||||
- **测试验证**: ✅ 通过
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 下一步建议
|
|
||||||
|
|
||||||
1. **用户反馈收集** - 根据实际使用调整面板宽度
|
|
||||||
2. **颜色方案优化** - 如有需要可调整7大分类颜色
|
|
||||||
3. **字体大小调整** - 根据期刊要求调整
|
|
||||||
4. **批量测试** - 测试更多基因家族数据
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*记录时间: 2026-03-29 02:00*
|
|
||||||
*记录位置: `/home/xia-lab/wutx/00test/bbav20311/memory/2026-03-28.md`*
|
|
||||||
*状态: ✅ 已保存*
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
# 2026-03-29 竞品分析记录 - bbav20311 vs PangenePro vs PlantTribes2
|
|
||||||
|
|
||||||
> **🔴 TOP PRIORITY - 后续版本升级参考**
|
|
||||||
>
|
|
||||||
> 创建时间: 2026-03-29 16:16
|
|
||||||
> 分析者: xialab的幸运星
|
|
||||||
> 状态: 待后续版本实现
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 分析目标
|
|
||||||
|
|
||||||
对比 bbav20311 与两个成熟竞品 (PangenePro、PlantTribes2),识别可直接借鉴的功能点,为后续版本升级提供参考。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 竞品概览
|
|
||||||
|
|
||||||
| 维度 | **bbav20311** | **PangenePro** | **PlantTribes2** |
|
|
||||||
|:---|:---|:---|:---|
|
|
||||||
| **定位** | 可发表级基因家族分析平台 | 多基因组泛基因家族鉴定 | 比较基因家族分析框架 |
|
|
||||||
| **核心创新** | Agent驱动 + 技能化 + 16阶段全流程 | 泛基因组(core/dispensable/unique)分类 | 预计算orthogroup + Galaxy集成 |
|
|
||||||
| **技术栈** | Python/FastAPI + Vue3 + R + BioPython | Python + R + Bash | Perl/Python + 多工具集成 |
|
|
||||||
| **部署方式** | 本地/服务器 + Web界面 | 命令行(Linux only) | Bioconda/Docker/Galaxy |
|
|
||||||
| **目标用户** | 生物信息学研究者 | 泛基因组研究者 | 广泛植物研究者 |
|
|
||||||
| **论文链接** | - | [PMC12255874](https://pmc.ncbi.nlm.nih.gov/articles/PMC12255874/) | [Frontiers 2022](https://www.frontiersin.org/journals/plant-science/articles/10.3389/fpls.2022.1011199/full) |
|
|
||||||
| **GitHub** | - | [kinza111/PangenePro](https://github.com/kinza111/PangenePro) | [dePamphilis/PlantTribes](https://github.com/dePamphilis/PlantTribes) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 核心功能对比矩阵
|
|
||||||
|
|
||||||
### 3.1 基因家族鉴定
|
|
||||||
|
|
||||||
| 功能 | bbav20311 | PangenePro | PlantTribes2 |
|
|
||||||
|:---|:---:|:---:|:---:|
|
|
||||||
| 鉴定方法 | HMMER + BLAST + 过滤 | BLASTp + InterProScan | BLASTP/HMMScan vs 预计算scaffold |
|
|
||||||
| 多基因组支持 | ✅ 是(物种间共线性) | ✅ **核心功能**(种内多品种) | ✅ 是 |
|
|
||||||
| 冗余/isoform处理 | 手动配置 | seqtk自动去冗余 | ESTScan/TransDecoder |
|
|
||||||
| 结构域验证 | ✅ HMMER | ✅ InterProScan API | ✅ HMMScan |
|
|
||||||
| 分类策略 | 亚家族 + 系统发育 | **core/dispensable/unique** | orthogroup聚类 |
|
|
||||||
|
|
||||||
### 3.2 系统发育与进化分析
|
|
||||||
|
|
||||||
| 功能 | bbav20311 | PangenePro | PlantTribes2 |
|
|
||||||
|:---|:---:|:---:|:---:|
|
|
||||||
| 建树工具 | IQ-TREE/RAxML/FastTree | - | RAxML/FastTreeMP |
|
|
||||||
| 多序列比对 | MAFFT | - | MAFFT/PASTA |
|
|
||||||
| Ka/Ks计算 | ✅ KaKs_Calculator | - | ✅ **PAML + EMMIX** |
|
|
||||||
| 进化速率 | - | - | ✅ dN/dS 成对比较 |
|
|
||||||
| **大规模复制事件** | MCScanX | - | ✅ **专门检测** |
|
|
||||||
|
|
||||||
### 3.3 可视化输出
|
|
||||||
|
|
||||||
| 功能 | bbav20311 | PangenePro | PlantTribes2 |
|
|
||||||
|:---|:---:|:---:|:---:|
|
|
||||||
| 热图 | ✅ **V6 五面板组合图** | 基础热图 | 基础 |
|
|
||||||
| 系统发育树 | ✅ 美化 + 亚家族标注 | - | 基础 |
|
|
||||||
| 共线性 | ✅ MCScanX 可视化 | - | - |
|
|
||||||
| 启动子元件 | ✅ PlantCARE + 7分类 | - | - |
|
|
||||||
| Venn/UpSet | ✅ UpSet图 | ✅ Venn + UpSet + 柱状图 | - |
|
|
||||||
| 结构域 | ✅ 蛋白结构域图 | - | - |
|
|
||||||
| Motif | ✅ MEME + 自定义R脚本 | - | - |
|
|
||||||
|
|
||||||
**🏆 bbav20311 可视化优势**: Promoter V6 五面板组合图已达到发表级别
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 🔴 可直接借鉴功能清单 (按优先级排序)
|
|
||||||
|
|
||||||
### P0 - 高优先级 (建议下一版本实现)
|
|
||||||
|
|
||||||
| 来源 | 功能 | 应用场景 | 实现难度 | 技术要点 |
|
|
||||||
|:---|:---|:---|:---:|:---|
|
|
||||||
| **PangenePro** | **泛基因分类** (core/dispensable/unique) | RNA-seq样本间基因存在/缺失变异分析 | 中 | 基于orthogroup聚类后的样本存在矩阵分类 |
|
|
||||||
| **PangenePro** | **多品种泛基因鉴定** | 作物不同品种间基因家族比较 | 中 | 支持多基因组输入,自动PAV检测 |
|
|
||||||
| **PlantTribes2** | **Ka/Ks分析优化** | 替换KaKs_Calculator,增加统计检验 | 低 | 集成PAML (codeml) + EMMIX混合模型 |
|
|
||||||
| **PlantTribes2** | **大规模复制事件检测** | 基因家族扩张与WGD关联分析 | 中 | Ks分布分析 + 基因保留率计算 |
|
|
||||||
|
|
||||||
### P1 - 中优先级 (可作为增强功能)
|
|
||||||
|
|
||||||
| 来源 | 功能 | 应用场景 | 实现难度 |
|
|
||||||
|:---|:---|:---|:---:|
|
|
||||||
| **PlantTribes2** | 预计算orthogroup scaffold | 加速基因家族分类 | 高 |
|
|
||||||
| **PlantTribes2** | ESTScan/TransDecoder集成 | 处理无注释基因组的转录本 | 中 |
|
|
||||||
| **PangenePro** | 自动化isoform过滤 | 减少手动配置 | 低 |
|
|
||||||
| **PlantTribes2** | Galaxy集成经验 | 如考虑上线公共平台可参考 | 中 |
|
|
||||||
|
|
||||||
### P2 - 低优先级 (了解即可)
|
|
||||||
|
|
||||||
| 来源 | 功能 | 原因 |
|
|
||||||
|:---|:---|:---|
|
|
||||||
| PlantTribes2 | AssemblyPostProcessor | bbav20311假设输入已有注释 |
|
|
||||||
| PangenePro | InterProScan API | 你们已用HMMER本地版,更稳定 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 技术实现建议
|
|
||||||
|
|
||||||
### 5.1 泛基因分类模块 (新阶段建议)
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 建议新增: run_pangenome.py
|
|
||||||
# 输入: 多品种/样本的基因家族鉴定结果
|
|
||||||
# 输出:
|
|
||||||
# - core_genes.txt (所有样本共有)
|
|
||||||
# - dispensable_genes.txt (部分样本共有)
|
|
||||||
# - unique_genes.txt (单个样本特有)
|
|
||||||
# - 可视化: Venn图 + 柱状图 + UpSet图
|
|
||||||
|
|
||||||
# 分类逻辑参考 PangenePro:
|
|
||||||
# - core: 存在于所有输入基因组
|
|
||||||
# - dispensable: 存在于>1个但<全部基因组
|
|
||||||
# - unique: 仅存在于单个基因组
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 isoform自动处理
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 在 identify 阶段集成 seqtk (来自 PangenePro)
|
|
||||||
seqtk subseq proteins.fasta longest_isoform_ids.txt > filtered.fasta
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.3 Ka/Ks 分析增强
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 建议: 集成 PAML (codeml) + EMMIX 混合模型
|
|
||||||
# 现有 KaKs_Calculator 作为快速模式
|
|
||||||
# PAML 作为精确模式(可选)
|
|
||||||
|
|
||||||
# PlantTribes2 使用:
|
|
||||||
# - PAML codeml 计算dN/dS
|
|
||||||
# - EMMIX 进行混合模型聚类
|
|
||||||
# - 识别不同的进化速率类别
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.4 大规模复制事件检测
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 建议新增: run_wgd.py
|
|
||||||
# 使用 MCScanX 结果 + Ks分布分析
|
|
||||||
# 输出:
|
|
||||||
# - WGD事件时间推断
|
|
||||||
# - 基因保留率
|
|
||||||
# - Ks分布图
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 架构对比总结
|
|
||||||
|
|
||||||
### bbav20311 独特优势
|
|
||||||
|
|
||||||
| 方面 | 说明 |
|
|
||||||
|:---|:---|
|
|
||||||
| **Agent架构** | 技能化设计比传统pipeline更灵活 |
|
|
||||||
| **可视化** | Promoter V6 五面板图已达发表级别 |
|
|
||||||
| **全流程覆盖** | 16个阶段比两个竞品更全面 |
|
|
||||||
| **Web界面** | Vue3 + FastAPI 比命令行更友好 |
|
|
||||||
| **契约化设计** | 输入输出标准化,利于可复现性 |
|
|
||||||
|
|
||||||
### 建议改进方向
|
|
||||||
|
|
||||||
| 优先级 | 方向 | 参考来源 |
|
|
||||||
|:---|:---|:---|
|
|
||||||
| P0 | 泛基因分类 (core/dispensable/unique) | PangenePro |
|
|
||||||
| P0 | Ka/Ks 统计检验增强 | PlantTribes2 |
|
|
||||||
| P1 | 大规模复制事件检测 | PlantTribes2 |
|
|
||||||
| P1 | 多品种/多样本比较分析 | PangenePro |
|
|
||||||
| P2 | 预计算orthogroup加速 | PlantTribes2 |
|
|
||||||
| P2 | 容器化部署 (Docker) | PlantTribes2 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 后续行动建议
|
|
||||||
|
|
||||||
### 短期 (1-2个月)
|
|
||||||
1. [ ] 设计 `run_pangenome.py` 阶段原型
|
|
||||||
2. [ ] 调研 PAML + EMMIX 集成方案
|
|
||||||
3. [ ] 评估 isoform 自动过滤的必要性
|
|
||||||
|
|
||||||
### 中期 (3-6个月)
|
|
||||||
1. [ ] 实现泛基因分类完整流程
|
|
||||||
2. [ ] 升级 Ka/Ks 分析模块
|
|
||||||
3. [ ] 添加大规模复制事件检测
|
|
||||||
|
|
||||||
### 长期 (6个月+)
|
|
||||||
1. [ ] 构建预计算orthogroup scaffold
|
|
||||||
2. [ ] 容器化部署支持
|
|
||||||
3. [ ] Galaxy平台集成调研
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 参考资料
|
|
||||||
|
|
||||||
### PangenePro
|
|
||||||
- 论文: https://pmc.ncbi.nlm.nih.gov/articles/PMC12255874/
|
|
||||||
- GitHub: https://github.com/kinza111/PangenePro
|
|
||||||
- DOI: 10.5281/zenodo.14661747
|
|
||||||
|
|
||||||
### PlantTribes2
|
|
||||||
- 论文: https://www.frontiersin.org/journals/plant-science/articles/10.3389/fpls.2022.1011199/full
|
|
||||||
- GitHub: https://github.com/dePamphilis/PlantTribes
|
|
||||||
- 手册: https://github.com/dePamphilis/PlantTribes/blob/master/docs/PlantTribes.md
|
|
||||||
- Scaffold数据: http://bigdata.bx.psu.edu/PlantTribes_scaffolds/
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*记录时间: 2026-03-29 16:16*
|
|
||||||
*记录位置: `/home/xia-lab/wutx/00test/bbav20311/memory/2026-03-29_comparative_analysis.md`*
|
|
||||||
*状态: ✅ 已完整记录*
|
|
||||||
@@ -1,249 +0,0 @@
|
|||||||
# bbav20311 分析脚本规范性评估报告
|
|
||||||
|
|
||||||
> 生成时间: 2026-03-30 01:45
|
|
||||||
> 评估标准: 以 run_phylogeny.py 为模板
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 评估标准(8项要素)
|
|
||||||
|
|
||||||
| 要素 | 说明 |
|
|
||||||
|:---|:---|
|
|
||||||
| 1. 标准文档字符串 | 清晰的输入输出说明 |
|
|
||||||
| 2. get_tool_versions() | 获取工具版本信息 |
|
|
||||||
| 3. compute_file_hash() | 计算文件SHA256哈希 |
|
|
||||||
| 4. validate_previous_stage() | 验证上游manifest |
|
|
||||||
| 5. prepare_directories() | 创建标准目录结构 |
|
|
||||||
| 6. 生成manifest.json | 符合manifest_v1.schema.json |
|
|
||||||
| 7. 生成report.md | 人可读分析报告 |
|
|
||||||
| 8. 可复现性信息 | cmd历史、参数、版本等 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 评估结果
|
|
||||||
|
|
||||||
### 1. run_phylogeny.py (标准模板) ✅
|
|
||||||
**评分: 10/10**
|
|
||||||
- ✅ 标准文档字符串
|
|
||||||
- ✅ get_tool_versions()
|
|
||||||
- ✅ compute_file_hash()
|
|
||||||
- ✅ validate_identify_output() (验证上游)
|
|
||||||
- ✅ prepare_directories()
|
|
||||||
- ✅ 生成manifest.json
|
|
||||||
- ✅ 生成report.md
|
|
||||||
- ✅ 可复现性信息完整
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. run_genome_prepare.py
|
|
||||||
**评分: 7/10**
|
|
||||||
- ✅ 标准文档字符串
|
|
||||||
- ✅ get_tool_versions()
|
|
||||||
- ✅ compute_file_hash()
|
|
||||||
- ❌ **缺失**: validate_previous_stage() (无上游)
|
|
||||||
- ✅ prepare_directories() (在类中)
|
|
||||||
- ⚠️ **部分**: manifest.json生成简单
|
|
||||||
- ❌ **缺失**: report.md生成
|
|
||||||
- ⚠️ **部分**: 可复现性信息不完整
|
|
||||||
|
|
||||||
**需修订**:
|
|
||||||
1. 完善manifest.json格式(符合schema)
|
|
||||||
2. 添加report.md生成
|
|
||||||
3. 完善可复现性信息
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. run_identify.py
|
|
||||||
**评分: 8/10**
|
|
||||||
- ✅ 标准文档字符串
|
|
||||||
- ✅ get_tool_versions() (通过run_cmd)
|
|
||||||
- ✅ compute_file_hash()
|
|
||||||
- ❌ **缺失**: validate_previous_stage() (无上游,但应验证输入)
|
|
||||||
- ❌ **缺失**: prepare_directories() (分散在代码中)
|
|
||||||
- ✅ 生成manifest.json
|
|
||||||
- ✅ 生成report.md
|
|
||||||
- ✅ 可复现性信息
|
|
||||||
|
|
||||||
**需修订**:
|
|
||||||
1. 添加prepare_directories()函数
|
|
||||||
2. 统一目录创建逻辑
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. run_motif.py
|
|
||||||
**评分: 7/10**
|
|
||||||
- ✅ 标准文档字符串
|
|
||||||
- ✅ get_tool_versions()
|
|
||||||
- ✅ compute_file_hash()
|
|
||||||
- ⚠️ **部分**: 有resolve_protein_fasta()但非标准validate
|
|
||||||
- ❌ **缺失**: prepare_directories()
|
|
||||||
- ✅ 生成manifest.json
|
|
||||||
- ✅ 生成report.md
|
|
||||||
- ⚠️ **部分**: 可复现性信息
|
|
||||||
|
|
||||||
**需修订**:
|
|
||||||
1. 添加prepare_directories()
|
|
||||||
2. 标准化validate_previous_stage()
|
|
||||||
3. 完善可复现性信息
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. run_domain.py
|
|
||||||
**评分: 7/10**
|
|
||||||
- ✅ 标准文档字符串
|
|
||||||
- ✅ get_tool_versions()
|
|
||||||
- ✅ compute_file_hash()
|
|
||||||
- ⚠️ **部分**: 有find_latest_manifest()但非标准validate
|
|
||||||
- ❌ **缺失**: prepare_directories()
|
|
||||||
- ✅ 生成manifest.json
|
|
||||||
- ✅ 生成report.md
|
|
||||||
- ⚠️ **部分**: 可复现性信息
|
|
||||||
|
|
||||||
**需修订**:
|
|
||||||
1. 添加prepare_directories()
|
|
||||||
2. 标准化validate_previous_stage()
|
|
||||||
3. 完善可复现性信息
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. run_promoter.py
|
|
||||||
**评分: 8/10**
|
|
||||||
- ✅ 标准文档字符串
|
|
||||||
- ✅ get_tool_versions()
|
|
||||||
- ✅ compute_file_hash()
|
|
||||||
- ❌ **缺失**: validate_previous_stage() (需要phylogeny产物)
|
|
||||||
- ❌ **缺失**: prepare_directories()
|
|
||||||
- ✅ 生成manifest.json
|
|
||||||
- ✅ 生成report.md
|
|
||||||
- ✅ 可复现性信息
|
|
||||||
|
|
||||||
**需修订**:
|
|
||||||
1. 添加prepare_directories()
|
|
||||||
2. 添加validate_phylogeny_output()
|
|
||||||
3. 添加figures字段到manifest
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7. run_expression.py
|
|
||||||
**评分: 4/10** ⚠️ 最不完整
|
|
||||||
- ⚠️ **部分**: 简单文档字符串
|
|
||||||
- ❌ **缺失**: get_tool_versions()
|
|
||||||
- ❌ **缺失**: compute_file_hash()
|
|
||||||
- ❌ **缺失**: validate_previous_stage()
|
|
||||||
- ❌ **缺失**: prepare_directories()
|
|
||||||
- ❌ **缺失**: manifest.json生成
|
|
||||||
- ❌ **缺失**: report.md生成
|
|
||||||
- ❌ **缺失**: 可复现性信息
|
|
||||||
|
|
||||||
**需大幅修订**:
|
|
||||||
1. 重写为标准结构
|
|
||||||
2. 添加所有缺失要素
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 8. run_karyotype.py
|
|
||||||
**评分: 6/10**
|
|
||||||
- ✅ 标准文档字符串
|
|
||||||
- ✅ get_tool_versions()
|
|
||||||
- ✅ compute_file_hash()
|
|
||||||
- ⚠️ **部分**: validate_inputs()但非标准validate_previous_stage
|
|
||||||
- ✅ prepare_directories()
|
|
||||||
- ❌ **缺失**: manifest.json生成
|
|
||||||
- ❌ **缺失**: report.md生成
|
|
||||||
- ❌ **缺失**: 可复现性信息
|
|
||||||
|
|
||||||
**需修订**:
|
|
||||||
1. 添加manifest.json生成
|
|
||||||
2. 添加report.md生成
|
|
||||||
3. 添加可复现性信息
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 9. run_synteny.py
|
|
||||||
**评分: 6/10**
|
|
||||||
- ✅ 标准文档字符串
|
|
||||||
- ✅ get_tool_versions()
|
|
||||||
- ✅ compute_file_hash()
|
|
||||||
- ⚠️ **部分**: validate_inputs()但非标准
|
|
||||||
- ❌ **缺失**: prepare_directories()
|
|
||||||
- ❌ **缺失**: manifest.json生成
|
|
||||||
- ❌ **缺失**: report.md生成
|
|
||||||
- ⚠️ **部分**: 可复现性信息
|
|
||||||
|
|
||||||
**需修订**:
|
|
||||||
1. 添加prepare_directories()
|
|
||||||
2. 添加manifest.json生成
|
|
||||||
3. 添加report.md生成
|
|
||||||
4. 完善可复现性信息
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 10. run_interspecies_synteny.py
|
|
||||||
**评分: 6/10**
|
|
||||||
- ✅ 标准文档字符串
|
|
||||||
- ✅ get_tool_versions()
|
|
||||||
- ✅ compute_file_hash()
|
|
||||||
- ❌ **缺失**: validate_previous_stage()
|
|
||||||
- ✅ prepare_directories()
|
|
||||||
- ❌ **缺失**: manifest.json生成
|
|
||||||
- ❌ **缺失**: report.md生成
|
|
||||||
- ⚠️ **部分**: 可复现性信息
|
|
||||||
|
|
||||||
**需修订**:
|
|
||||||
1. 添加manifest.json生成
|
|
||||||
2. 添加report.md生成
|
|
||||||
3. 完善可复现性信息
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 修订优先级排序
|
|
||||||
|
|
||||||
### 🔴 P0 - 高优先级(基础依赖)
|
|
||||||
| 顺序 | 脚本 | 评分 | 原因 |
|
|
||||||
|:---:|:---|:---:|:---|
|
|
||||||
| 1 | run_genome_prepare.py | 7/10 | 所有分析的起点 |
|
|
||||||
| 2 | run_identify.py | 8/10 | 核心鉴定步骤 |
|
|
||||||
| 3 | run_phylogeny.py | 10/10 | 已是标准,无需修订 |
|
|
||||||
|
|
||||||
### 🟡 P1 - 中优先级(主要分析)
|
|
||||||
| 顺序 | 脚本 | 评分 | 原因 |
|
|
||||||
|:---:|:---|:---:|:---|
|
|
||||||
| 4 | run_motif.py | 7/10 | 依赖identify |
|
|
||||||
| 5 | run_domain.py | 7/10 | 依赖identify |
|
|
||||||
| 6 | run_promoter.py | 8/10 | 依赖phylogeny |
|
|
||||||
|
|
||||||
### 🟢 P2 - 低优先级(进阶分析)
|
|
||||||
| 顺序 | 脚本 | 评分 | 原因 |
|
|
||||||
|:---:|:---|:---:|:---|
|
|
||||||
| 7 | run_synteny.py | 6/10 | 进阶分析 |
|
|
||||||
| 8 | run_karyotype.py | 6/10 | 依赖synteny |
|
|
||||||
| 9 | run_interspecies_synteny.py | 6/10 | 进阶分析 |
|
|
||||||
| 10 | run_expression.py | 4/10 | 独立流程,需大幅重写 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 修订计划总览
|
|
||||||
|
|
||||||
### Phase 1: 基础阶段 (P0)
|
|
||||||
- [ ] run_genome_prepare.py - 完善manifest和report
|
|
||||||
- [ ] run_identify.py - 添加prepare_directories
|
|
||||||
|
|
||||||
### Phase 2: 核心分析阶段 (P1)
|
|
||||||
- [ ] run_motif.py - 标准化结构
|
|
||||||
- [ ] run_domain.py - 标准化结构
|
|
||||||
- [ ] run_promoter.py - 添加依赖验证
|
|
||||||
|
|
||||||
### Phase 3: 进阶分析阶段 (P2)
|
|
||||||
- [ ] run_synteny.py - 完整manifest和report
|
|
||||||
- [ ] run_karyotype.py - 完整manifest和report
|
|
||||||
- [ ] run_interspecies_synteny.py - 完整manifest和report
|
|
||||||
- [ ] run_expression.py - 重写为标准结构
|
|
||||||
|
|
||||||
### Phase 4: MAS集成
|
|
||||||
- [ ] 创建Stage适配器层
|
|
||||||
- [ ] 连接RAG API
|
|
||||||
- [ ] 多智能体联动
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*评估完成,准备开始修订*
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# 2026-03-30 修订进度记录
|
|
||||||
|
|
||||||
> 时间: 01:47
|
|
||||||
> 备份状态: ✅ 已完成 (bbav20311_backup_20260330, gene-family-mas-v2_backup_20260330)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 修订计划
|
|
||||||
|
|
||||||
### Phase 1: 高优先级修订(并行执行中)
|
|
||||||
|
|
||||||
| 脚本 | 子Agent | 状态 | 开始时间 | 完成时间 |
|
|
||||||
|:---|:---|:---:|:---:|:---:|
|
|
||||||
| run_expression.py | agent:main:subagent:e775fc93-ecf4-4827-ac6f-4dc8ac452094 | 🔄 修订中 | 01:47 | - |
|
|
||||||
| run_interspecies_synteny.py | agent:main:subagent:b3090aee-a277-430b-8cdc-67d34a7a3550 | ✅ 已完成 | 01:47 | 01:50 |
|
|
||||||
| run_promoter.py | agent:main:subagent:48814e02-acbc-485d-82cf-5c42939369ab | ✅ 已完成 | 01:47 | 01:51 |
|
|
||||||
|
|
||||||
**已完成修订**:
|
|
||||||
- ✅ run_interspecies_synteny.py - manifest格式已符合schema v1标准
|
|
||||||
- ✅ run_promoter.py - 添加prepare_directories(),完善manifest(含figures字段)
|
|
||||||
|
|
||||||
### Phase 2: 中优先级修订(待执行)
|
|
||||||
|
|
||||||
| 脚本 | 评分 | 主要问题 |
|
|
||||||
|:---|:---:|:---|
|
|
||||||
| run_genome_prepare.py | 8.5/10 | manifest格式不完全规范 |
|
|
||||||
| run_synteny.py | 9/10 | prepare_directories()未独立 |
|
|
||||||
| run_karyotype.py | 9/10 | prepare_directories()未独立 |
|
|
||||||
|
|
||||||
### Phase 3: 低优先级修订(可选)
|
|
||||||
|
|
||||||
| 脚本 | 评分 | 主要问题 |
|
|
||||||
|:---|:---:|:---|
|
|
||||||
| run_motif.py | 9.5/10 | 几乎完整,仅需微调 |
|
|
||||||
| run_domain.py | 9.5/10 | 几乎完整,仅需微调 |
|
|
||||||
| run_identify.py | 9/10 | 良好,仅需微调 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 时间节点
|
|
||||||
|
|
||||||
- [x] 01:41 - 完成分析脚本规范性评估
|
|
||||||
- [x] 01:47 - 启动Phase 1修订(3个并行任务)
|
|
||||||
- [ ] 03:30 - 第一次备份
|
|
||||||
- [ ] 03:50 - 第二次备份
|
|
||||||
- [ ] 04:10 - 检查修订进度
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 修订位置确认
|
|
||||||
|
|
||||||
所有修订都在备份目录进行:
|
|
||||||
- 源目录: /home/xia-lab/wutx/00test/bbav20311/
|
|
||||||
- 备份目录: /home/xia-lab/wutx/00test/bbav20311_backup_20260330/
|
|
||||||
- 修订在备份目录进行,测试通过后复制回源目录
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*记录位置: /home/xia-lab/wutx/00test/bbav20311/memory/2026-03-30-revision-progress.md*
|
|
||||||
*状态: ✅ 已记录*
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
# 2026-03-30 工作记录 - bbav20311 项目状态更新
|
|
||||||
|
|
||||||
> 日期: 2026-03-30
|
|
||||||
> 项目: /home/xia-lab/wutx/00test/bbav20311
|
|
||||||
> 状态: 文档整理与架构设计阶段
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 今日概述
|
|
||||||
|
|
||||||
今日主要进行项目文档整理和架构设计工作,无新增代码修改。重点整理了多物种基因家族分析的工作流设计方案。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 当前项目状态
|
|
||||||
|
|
||||||
### 1. 已完成工作(3月28-29日)
|
|
||||||
|
|
||||||
#### Promoter分析模块优化 ✅
|
|
||||||
- **V6五面板组合图**: 已完成并集成到分析流程
|
|
||||||
- **可视化输出**: 热图、系统发育树、亚家族标注、7大分类堆叠
|
|
||||||
- **文件位置**: `scripts/promoter_v6_combined.R`
|
|
||||||
|
|
||||||
#### 竞品分析文档 ✅
|
|
||||||
- **对比对象**: PangenePro、PlantTribes2
|
|
||||||
- **分析结果**: 识别了可直接借鉴的功能点
|
|
||||||
- **优先级**: P0(泛基因分类、Ka/Ks增强)、P1(复制事件检测)
|
|
||||||
- **文件位置**: `memory/2026-03-29_comparative_analysis.md`
|
|
||||||
|
|
||||||
### 2. 架构设计文档(3月29日创建)
|
|
||||||
|
|
||||||
| 文档 | 路径 | 内容 |
|
|
||||||
|:---|:---|:---|
|
|
||||||
| 多物种工作流设计 | `docs/multi_species_workflow_design.md` | 目标物种vs参考物种概念、文件命名规范 |
|
|
||||||
| 基于文件夹的工作流 | `docs/folder_based_workflow.md` | 物理文件夹结构设计方案 |
|
|
||||||
| 多智能体架构设计 | `docs/multi_agent_genome_prepare.md` | Master/Species/File/Quality Agent架构 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 文档存储位置
|
|
||||||
|
|
||||||
### 项目内记忆文档
|
|
||||||
```
|
|
||||||
/home/xia-lab/wutx/00test/bbav20311/memory/
|
|
||||||
├── 2026-03-27.md # Promoter分析修订
|
|
||||||
├── 2026-03-28.md # Promoter V6组合图
|
|
||||||
└── 2026-03-29_comparative_analysis.md # 竞品分析
|
|
||||||
```
|
|
||||||
|
|
||||||
### 架构设计文档
|
|
||||||
```
|
|
||||||
/home/xia-lab/wutx/00test/bbav20311/docs/
|
|
||||||
├── multi_species_workflow_design.md # 多物种工作流概念设计
|
|
||||||
├── folder_based_workflow.md # 文件夹结构方案
|
|
||||||
└── multi_agent_genome_prepare.md # 多智能体架构设计
|
|
||||||
```
|
|
||||||
|
|
||||||
### OpenClaw工作区记忆
|
|
||||||
```
|
|
||||||
/home/xia-lab/.openclaw/workspace/memory-bbav20311/
|
|
||||||
├── README.md # 项目索引
|
|
||||||
├── 2026-03-20.md # RNA-seq集成
|
|
||||||
├── 2026-03-20-rnaseq-integration.md # RNA-seq详细记录
|
|
||||||
├── 2026-03-22.md # 多物种分析
|
|
||||||
└── 2026-03-23.md # 状态更新
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 待办事项状态
|
|
||||||
|
|
||||||
### 当前待办(来自README.md)
|
|
||||||
|
|
||||||
| 任务 | 状态 | 备注 |
|
|
||||||
|:---|:---:|:---|
|
|
||||||
| 从bbav2迁移各阶段代码 | ⏳ 进行中 | identify, phylogeny已完成 |
|
|
||||||
| 验证Python 3.11兼容性 | ⏳ 待验证 | 基础功能已测试 |
|
|
||||||
| 测试端口7865服务启动 | ⏳ 待验证 | FastAPI后端 |
|
|
||||||
| 逐一验证11个分析阶段 | ⏳ 进行中 | expression已验证 |
|
|
||||||
| 多物种工作流实现 | 📝 设计中 | 3个设计方案待评审 |
|
|
||||||
|
|
||||||
### 竞品分析建议的实现优先级
|
|
||||||
|
|
||||||
**P0 - 高优先级**
|
|
||||||
- [ ] 泛基因分类 (core/dispensable/unique)
|
|
||||||
- [ ] Ka/Ks分析增强 (PAML + EMMIX)
|
|
||||||
|
|
||||||
**P1 - 中优先级**
|
|
||||||
- [ ] 大规模复制事件检测
|
|
||||||
- [ ] 多品种/多样本比较分析
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 技术债务
|
|
||||||
|
|
||||||
### 已知问题
|
|
||||||
1. **Promoter分析**: V6组合图已集成,但需用户反馈调优
|
|
||||||
2. **RNA-seq集成**: Step 1 (QC)完成,Step 2-5待实现
|
|
||||||
3. **多物种支持**: 设计方案完成,待代码实现
|
|
||||||
|
|
||||||
### 近期计划
|
|
||||||
1. 评审并选择多物种工作流方案(A/B/C)
|
|
||||||
2. 实现选定的多物种genome_prepare
|
|
||||||
3. 更新identify阶段支持多参考物种BLAST
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 文件完整性检查
|
|
||||||
|
|
||||||
### 已记录文件 ✅
|
|
||||||
- [x] 2026-03-27.md - Promoter分析修订记录
|
|
||||||
- [x] 2026-03-28.md - Promoter V6组合图详细记录
|
|
||||||
- [x] 2026-03-29_comparative_analysis.md - 竞品分析完整记录
|
|
||||||
- [x] docs/multi_species_workflow_design.md - 多物种工作流设计
|
|
||||||
- [x] docs/folder_based_workflow.md - 文件夹结构方案
|
|
||||||
- [x] docs/multi_agent_genome_prepare.md - 多智能体架构设计
|
|
||||||
|
|
||||||
### OpenClaw工作区同步状态 ✅
|
|
||||||
- [x] memory-bbav20311/README.md - 项目索引已更新
|
|
||||||
- [x] 所有历史记录已归档
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 记录完整性确认
|
|
||||||
|
|
||||||
| 检查项 | 状态 |
|
|
||||||
|:---|:---:|
|
|
||||||
| 代码修改记录 | ✅ 完整(3月28-29日) |
|
|
||||||
| 架构设计文档 | ✅ 完整(3个设计文档) |
|
|
||||||
| 竞品分析报告 | ✅ 完整 |
|
|
||||||
| 待办事项跟踪 | ✅ 已更新 |
|
|
||||||
| 文件存储位置 | ✅ 已记录 |
|
|
||||||
|
|
||||||
**结论**: 截至2026-03-30,bbav20311项目的修订记录已完整保存。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*记录时间: 2026-03-30 00:55*
|
|
||||||
*记录位置: `/home/xia-lab/wutx/00test/bbav20311/memory/2026-03-30.md`*
|
|
||||||
*状态: ✅ 已完整记录*
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
# 2026-04-01 - bbav20311 前端集成与API配置修订记录
|
|
||||||
|
|
||||||
## 项目信息
|
|
||||||
- **项目**: bbav20311 (bbav2复制版本,端口7865)
|
|
||||||
- **路径**: `/home/xia-lab/wutx/00test/bbav20311`
|
|
||||||
- **记录时间**: 2026-04-01
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 一、任务3完成:前端集成测试
|
|
||||||
|
|
||||||
### 已完成测试项
|
|
||||||
|
|
||||||
| 测试项 | 状态 | 说明 |
|
|
||||||
|--------|------|------|
|
|
||||||
| `/api/agent/tasks` | ✅ | 任务列表API正常 |
|
|
||||||
| `/api/agent/execute` | ✅ | 任务提交API正常 |
|
|
||||||
| `/api/agent/status/{id}` | ✅ | 状态查询API正常 |
|
|
||||||
| `/api/agent/plan` | ✅ | 新增任务规划API正常 |
|
|
||||||
| `/api/agent/chat` | ⚠️ | API正常,但LLM配置未完成 |
|
|
||||||
|
|
||||||
### 修复内容
|
|
||||||
|
|
||||||
1. **langgraph依赖安装**
|
|
||||||
- 安装命令: `pip3 install langgraph --break-system-packages`
|
|
||||||
- 状态: ✅ 完成
|
|
||||||
|
|
||||||
2. **pydantic-settings依赖安装**
|
|
||||||
- 安装命令: `pip3 install pydantic-settings --break-system-packages`
|
|
||||||
- 状态: ✅ 完成
|
|
||||||
|
|
||||||
3. **服务稳定性优化**
|
|
||||||
- 修复后台任务异步处理逻辑
|
|
||||||
- 移除`asyncio.new_event_loop()`,改用原生`await`
|
|
||||||
- 添加`start_server.sh`自动重启脚本
|
|
||||||
- 状态: ✅ 完成
|
|
||||||
|
|
||||||
4. **前端问题修复**
|
|
||||||
- 修复JavaScript语法错误(`intentHistory`定义问题)
|
|
||||||
- 添加工作目录复制按钮
|
|
||||||
- 添加上传文件列表显示(限制5个文件,超出滚动)
|
|
||||||
- 修复`addAgentMessage`中`intentHistory`引用错误
|
|
||||||
- 状态: ✅ 完成
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 二、阿里云百炼API配置(进行中)
|
|
||||||
|
|
||||||
### 已修改文件
|
|
||||||
|
|
||||||
1. **`/gfa_core/mas/config.py`**
|
|
||||||
- 添加`openai_api_key`字段
|
|
||||||
- 添加`openai_base_url`字段(默认`https://coding.dashscope.aliyuncs.com/v1`)
|
|
||||||
- 修改`load_llm_config()`注入阿里云配置
|
|
||||||
|
|
||||||
2. **`/gfa_core/mas/llm/manager.py`**
|
|
||||||
- 修改`_get_api_key()`支持从config读取`openai_api_key`
|
|
||||||
- 修改`_get_available_providers()`检查`openai_api_key`
|
|
||||||
|
|
||||||
3. **`/gfa_core/mas/llm/providers/kimi.py`**
|
|
||||||
- 修改base_url为`https://coding.dashscope.aliyuncs.com/v1`
|
|
||||||
|
|
||||||
4. **`/apps/web/vue3/main.py`**
|
|
||||||
- 注入阿里云百炼配置到llm_config
|
|
||||||
|
|
||||||
5. **`/config/llm.yaml`**(新建)
|
|
||||||
- 配置阿里云百炼Coding Plan支持的模型列表
|
|
||||||
|
|
||||||
### API Key配置状态
|
|
||||||
|
|
||||||
- **Key位置**: `gfa_core/mas/config.py`中`openai_api_key`字段
|
|
||||||
- **Key值**: `sk-sp-c83ef364329141bca21dfe09f2d09667`
|
|
||||||
- **状态**: ⚠️ 已配置但未完全生效,Agent聊天API仍报错
|
|
||||||
|
|
||||||
### 待解决问题
|
|
||||||
|
|
||||||
Agent聊天API返回错误:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"message": "No LLM API key configured. Please set at least one of: ZHIPUAI_API_KEY, MOONSHOT_API_KEY, MINIMAX_API_KEY, DEEPSEEK_API_KEY"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**可能原因**:
|
|
||||||
1. Orchestrator内部重新创建了LLMManager
|
|
||||||
2. 配置未正确传递到Orchestrator
|
|
||||||
3. 需要重启服务使配置生效
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 三、前端功能说明
|
|
||||||
|
|
||||||
### 模型选择器
|
|
||||||
- 当前支持: Kimi、GLM-4、MiniMax
|
|
||||||
- 默认: Kimi
|
|
||||||
- 注意: 阿里云百炼Coding Plan支持更多模型(千问、智谱、Kimi、MiniMax)
|
|
||||||
|
|
||||||
### 技能执行/文献问答切换
|
|
||||||
- **技能执行**: 调用Agent进行基因家族分析
|
|
||||||
- **文献问答**: 连接RAG知识库回答文献问题(需要gene-family-mas-v2服务)
|
|
||||||
|
|
||||||
### 文件上传
|
|
||||||
- 支持多文件上传
|
|
||||||
- 显示最近5个文件,超出滚动查看
|
|
||||||
- 支持示例文件下载
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 四、服务启动命令
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 方式1: 直接启动
|
|
||||||
cd /home/xia-lab/wutx/00test/bbav20311/apps/web/vue3
|
|
||||||
python3 main.py
|
|
||||||
|
|
||||||
# 方式2: 使用自动重启脚本
|
|
||||||
cd /home/xia-lab/wutx/00test/bbav20311/apps/web/vue3
|
|
||||||
./start_server.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 五、待办事项
|
|
||||||
|
|
||||||
1. ⏳ 解决LLM API配置未生效问题
|
|
||||||
2. ⏳ 验证完整流程:从用户输入到结果展示
|
|
||||||
3. ⏳ 启动gene-family-mas-v2服务以支持RAG功能
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*记录时间: 2026-04-01 17:30*
|
|
||||||
*来源: agent:main 执行记录*
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
# 2026-04-02 - 意图识别调试记录
|
|
||||||
|
|
||||||
## 问题描述
|
|
||||||
|
|
||||||
**现象**: "帮我执行motif分析"被识别为基因家族鉴定,而不是motif分析
|
|
||||||
|
|
||||||
**已修改代码**: `/gfa_core/mas/core/orchestrator.py`
|
|
||||||
- 在`_analysis_node`中添加了LLM意图识别逻辑
|
|
||||||
- 添加了DEBUG打印: `print(f"[DEBUG] Intent analysis: type={analysis_type}...")`
|
|
||||||
|
|
||||||
## 调试过程
|
|
||||||
|
|
||||||
### 1. 代码修改确认
|
|
||||||
|
|
||||||
```python
|
|
||||||
# _analysis_node 中添加了意图识别
|
|
||||||
intent_prompt = """你是一个基因家族分析系统的意图识别器...
|
|
||||||
分析类型(必须是以下之一):
|
|
||||||
- identify: 基因家族鉴定
|
|
||||||
- phylogeny: 进化树构建
|
|
||||||
- motif: Motif分析
|
|
||||||
...
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 测试现象
|
|
||||||
|
|
||||||
- 代码修改已保存
|
|
||||||
- 清除了Python缓存(`__pycache__`和`.pyc`)
|
|
||||||
- 重启了FastAPI服务
|
|
||||||
- **问题**: 日志中没有出现"[DEBUG] Intent analysis"打印
|
|
||||||
|
|
||||||
### 3. 可能原因分析
|
|
||||||
|
|
||||||
| 原因 | 状态 |
|
|
||||||
|------|------|
|
|
||||||
| 代码未生效 | ❓ 文件已确认包含修改 |
|
|
||||||
| 路由问题 | ❓ 可能未走到`_analysis_node` |
|
|
||||||
| 异常捕获 | ❓ 可能抛异常被捕获 |
|
|
||||||
| print缓冲 | ❓ 可能未立即写入日志 |
|
|
||||||
| 多版本问题 | ✅ 已排除,只有一个文件 |
|
|
||||||
|
|
||||||
### 4. 待排查
|
|
||||||
|
|
||||||
1. 确认`_analysis_node`是否被调用
|
|
||||||
2. 检查是否有异常被捕获
|
|
||||||
3. 检查langgraph路由逻辑
|
|
||||||
4. 确认print输出是否被重定向
|
|
||||||
|
|
||||||
## 当前状态
|
|
||||||
|
|
||||||
- FastAPI服务: ✅ 运行中
|
|
||||||
- Redis: ✅ 运行中
|
|
||||||
- Celery Worker: ⚠️ 不稳定(已改用同步执行)
|
|
||||||
- 代码修改: ✅ 已保存
|
|
||||||
- 调试: ⏳ 待继续
|
|
||||||
|
|
||||||
## 下一步计划
|
|
||||||
|
|
||||||
1. 晚上重试,继续调试
|
|
||||||
2. 添加更多DEBUG打印,确认代码执行路径
|
|
||||||
3. 检查langgraph state传递机制
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*记录时间: 2026-04-02 01:46*
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
# 2026-04-02 - bbav20311 意图识别与RAG回复修复
|
|
||||||
|
|
||||||
## 项目信息
|
|
||||||
- **项目**: bbav20311
|
|
||||||
- **路径**: `/home/xia-lab/wutx/00test/bbav20311`
|
|
||||||
- **记录时间**: 2026-04-02
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 修复内容
|
|
||||||
|
|
||||||
### 1. 意图识别优化
|
|
||||||
|
|
||||||
**问题**: "帮我执行motif分析"被错误识别为基因家族鉴定
|
|
||||||
|
|
||||||
**修复文件**: `/gfa_core/mas/core/orchestrator.py`
|
|
||||||
|
|
||||||
**修改内容**:
|
|
||||||
- 在`_analysis_node`中添加LLM意图识别
|
|
||||||
- 支持识别以下分析类型:
|
|
||||||
- `identify`: 基因家族鉴定
|
|
||||||
- `phylogeny`: 进化树构建
|
|
||||||
- `motif`: Motif分析
|
|
||||||
- `domain`: 结构域分析
|
|
||||||
- `structure`: 基因结构分析
|
|
||||||
- `promoter`: 启动子分析
|
|
||||||
- `synteny`: 共线性分析
|
|
||||||
|
|
||||||
**代码片段**:
|
|
||||||
```python
|
|
||||||
# 使用LLM进行意图识别
|
|
||||||
intent_prompt = """你是一个基因家族分析系统的意图识别器...
|
|
||||||
分析类型(必须是以下之一):
|
|
||||||
- identify: 基因家族鉴定
|
|
||||||
- phylogeny: 进化树构建
|
|
||||||
- motif: Motif分析
|
|
||||||
...""")
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. RAG回复优化
|
|
||||||
|
|
||||||
**问题**: 回复泄露了CLI命令和系统内部信息
|
|
||||||
|
|
||||||
**修复文件**: `/gfa_core/mas/core/orchestrator.py`
|
|
||||||
|
|
||||||
**修改内容**:
|
|
||||||
- 修改knowledge节点的prompt
|
|
||||||
- 添加系统提示,禁止暴露内部信息
|
|
||||||
- 明确要求只回答用户问题本身
|
|
||||||
|
|
||||||
**修改前**:
|
|
||||||
```python
|
|
||||||
prompt = f"""基于以下参考资料回答问题...
|
|
||||||
参考资料: {context}
|
|
||||||
问题: {query}
|
|
||||||
请提供详细、准确的回答:"""
|
|
||||||
```
|
|
||||||
|
|
||||||
**修改后**:
|
|
||||||
```python
|
|
||||||
prompt = f"""你是一个基因家族分析专家...
|
|
||||||
参考资料: {context}
|
|
||||||
用户问题: {query}
|
|
||||||
请提供简洁、专业的回答。不要提及任何系统内部信息、代码、CLI命令或文件路径。"""
|
|
||||||
|
|
||||||
messages = [
|
|
||||||
LLMMessage(role="system", content="你是BotanicalBaseAgent基因家族分析系统的知识助手。只回答用户问题,不暴露任何系统内部信息。"),
|
|
||||||
LLMMessage(role="user", content=prompt)
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 验证结果(2026-04-02 10:00)
|
|
||||||
|
|
||||||
### 服务状态
|
|
||||||
| 组件 | 状态 |
|
|
||||||
|------|------|
|
|
||||||
| Redis | ✅ 运行中 (PID: 1460189) |
|
|
||||||
| Celery Worker | ✅ 运行中 (PID: 2216242) |
|
|
||||||
| FastAPI | ✅ 运行中 (前台模式稳定) |
|
|
||||||
|
|
||||||
### 意图识别验证 ✅
|
|
||||||
|
|
||||||
**测试用例**: "帮我执行motif分析"
|
|
||||||
|
|
||||||
**结果**:
|
|
||||||
```
|
|
||||||
[DEBUG] Intent analysis: type=motif, family=, species=
|
|
||||||
stage: motif ✅ 正确识别
|
|
||||||
agent: analysis ✅ 走到analysis节点
|
|
||||||
```
|
|
||||||
|
|
||||||
**修复内容**:
|
|
||||||
1. `_analysis_node` 中添加LLM意图识别
|
|
||||||
2. `_route_query` 添加motif/phylogeny/domain等关键词支持
|
|
||||||
3. 优先使用`_router_node`设置的mode进行路由
|
|
||||||
|
|
||||||
### RAG回复验证 ✅
|
|
||||||
|
|
||||||
**测试用例**: "什么是基因家族鉴定"
|
|
||||||
|
|
||||||
**结果**:
|
|
||||||
- 回复内容专业、面向用户
|
|
||||||
- `contains_cli: False` — 无CLI命令泄露
|
|
||||||
- 无文件路径、无代码、无系统内部信息
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 待办事项(全部完成 ✅)
|
|
||||||
|
|
||||||
1. ✅ 重新启动Celery Worker
|
|
||||||
2. ✅ 前端测试修复后的功能
|
|
||||||
3. ✅ 验证意图识别准确性
|
|
||||||
4. ✅ 验证RAG回复不再泄露系统信息
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 启动命令
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 启动Celery Worker
|
|
||||||
cd /home/xia-lab/wutx/00test/bbav20311
|
|
||||||
celery -A celery_app worker --loglevel=info --concurrency=1
|
|
||||||
|
|
||||||
# 启动FastAPI服务
|
|
||||||
cd /home/xia-lab/wutx/00test/bbav20311/apps/web/vue3
|
|
||||||
python3 main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 关键修复代码
|
|
||||||
|
|
||||||
### 1. `_route_query` 路由优化
|
|
||||||
```python
|
|
||||||
def _route_query(self, state: GraphState) -> str:
|
|
||||||
query = state["query"].lower()
|
|
||||||
mode = state.get("mode", "faq")
|
|
||||||
|
|
||||||
# 优先使用router节点设置的mode
|
|
||||||
if mode == "analysis":
|
|
||||||
return "analysis"
|
|
||||||
if mode == "faq":
|
|
||||||
return "knowledge"
|
|
||||||
|
|
||||||
analysis_keywords = [
|
|
||||||
"鉴定", "分析", "帮我", "执行", "运行",
|
|
||||||
"identify", "analyze", "analysis", "run", "execute",
|
|
||||||
"motif", "phylogeny", "进化树", "结构域", "domain",
|
|
||||||
"启动子", "promoter", "共线性", "synteny",
|
|
||||||
]
|
|
||||||
|
|
||||||
for keyword in analysis_keywords:
|
|
||||||
if keyword in query:
|
|
||||||
return "analysis"
|
|
||||||
|
|
||||||
return "knowledge"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. `_analysis_node` 意图识别
|
|
||||||
```python
|
|
||||||
# 使用LLM进行意图识别
|
|
||||||
intent_prompt = """分析用户的请求,提取以下信息:
|
|
||||||
1. 分析类型(identify/phylogeny/motif/domain/structure/promoter/synteny)
|
|
||||||
2. 基因家族名称
|
|
||||||
3. 物种
|
|
||||||
请以JSON格式回复..."""
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 意图识别扩展(2026-04-02 17:00)
|
|
||||||
|
|
||||||
**问题**: RNA-Seq子阶段、Combo、PEP等分析无法识别
|
|
||||||
|
|
||||||
**修复文件**: `/gfa_core/mas/core/orchestrator.py`
|
|
||||||
|
|
||||||
**修改内容**:
|
|
||||||
- 扩展分析类型到19种:identify, phylogeny, motif, domain, structure, promoter, synteny, interspecies_synteny, karyotype, ppi, kaks, expression, enrichment, pep, combo, rna_seq_qc, rna_seq_mapping, rna_seq_quantification, trend, wgcna
|
|
||||||
- 添加50+生物信息学常用术语关键词
|
|
||||||
- 完善意图识别prompt,覆盖所有分析类型别名
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. PPI分析修复(2026-04-02 21:00 - 23:00)
|
|
||||||
|
|
||||||
**问题**: PPI分析失败,缺少ortholog_pairs.txt映射文件
|
|
||||||
|
|
||||||
**修复文件**:
|
|
||||||
- `/apps/web/pipeline_backend.py`
|
|
||||||
- `/apps/cli/run_ortholog.py`(新增)
|
|
||||||
|
|
||||||
**修改内容**:
|
|
||||||
1. **自动查找种间共线性结果**: 优先使用`interspecies_synteny/analysis/Ft.Ath.anchors`
|
|
||||||
2. **自动格式转换**: 将anchors格式转换为ortholog_pairs格式
|
|
||||||
3. **精准筛选**: 只筛选当前基因家族成员的映射关系
|
|
||||||
4. **格式修正**: 修正输出格式为`苦荞ID 拟南芥ID`(符合PPI脚本期望)
|
|
||||||
|
|
||||||
**关键代码**:
|
|
||||||
```python
|
|
||||||
# 从种间共线性结果中筛选当前基因家族的映射
|
|
||||||
if mapping_file and mapping_file.name.endswith(".anchors"):
|
|
||||||
gene_id_file = final_dir / f"{prefix}.final_members.id"
|
|
||||||
gene_family_ids = set()
|
|
||||||
# 读取基因家族成员...
|
|
||||||
|
|
||||||
# 只保留当前基因家族成员的映射
|
|
||||||
if ft_gene in gene_family_ids:
|
|
||||||
f_out.write(f"{ft_gene}\t{ath_gene}\n") # 苦荞ID 拟南芥ID
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 种间共线性分析修复(2026-04-02 21:00)
|
|
||||||
|
|
||||||
**问题**: 种间共线性分析被错误识别为种内共线性
|
|
||||||
|
|
||||||
**修复文件**: `/gfa_core/mas/core/orchestrator.py`
|
|
||||||
|
|
||||||
**修改内容**:
|
|
||||||
- 将`interspecies`改为`interspecies_synteny`以匹配pipeline stage名称
|
|
||||||
- 添加stage名称映射:`interspecies` -> `interspecies_synteny`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 语法错误修复(2026-04-02 21:12)
|
|
||||||
|
|
||||||
**问题**: IndentationError: unexpected indent
|
|
||||||
|
|
||||||
**修复文件**: `/gfa_core/mas/core/orchestrator.py`
|
|
||||||
|
|
||||||
**修改内容**:
|
|
||||||
- 删除重复的guidance_map定义
|
|
||||||
- 修复缩进错误
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 修复文件清单
|
|
||||||
|
|
||||||
| 文件路径 | 修改类型 | 说明 |
|
|
||||||
|---------|---------|------|
|
|
||||||
| `/gfa_core/mas/core/orchestrator.py` | 修改 | 意图识别、路由、RAG回复优化 |
|
|
||||||
| `/apps/web/pipeline_backend.py` | 修改 | PPI分析自动查找/生成ortholog映射 |
|
|
||||||
| `/apps/cli/run_ortholog.py` | 新增 | 同源基因识别脚本(BLASTP) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*记录时间: 2026-04-02 01:08*
|
|
||||||
*更新时间: 2026-04-03 01:03*
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
# 2026-04-03 项目记录 - Celery集成修订
|
|
||||||
|
|
||||||
## 今日工作摘要
|
|
||||||
|
|
||||||
完成bbav20311多Agent系统的Celery异步任务队列集成修订,解决API层与Celery层任务状态不一致问题。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 问题诊断
|
|
||||||
|
|
||||||
### 发现的问题
|
|
||||||
- `/api/agent/execute` 接口使用 `asyncio.create_task()` 直接执行,未调用Celery
|
|
||||||
- 任务状态存储在内存字典 `agent_tasks` 中,与Celery任务状态不同步
|
|
||||||
- API返回 completed,但Celery任务状态为 PENDING
|
|
||||||
|
|
||||||
### 根因
|
|
||||||
- 代码实现了Celery任务定义 (`tasks.py`),但API层未实际调用
|
|
||||||
- 异步执行使用了纯asyncio方式,绕过了Celery队列
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 修订内容
|
|
||||||
|
|
||||||
### 2.1 main.py (apps/web/vue3/main.py)
|
|
||||||
|
|
||||||
#### `/api/agent/execute` 接口修改
|
|
||||||
```python
|
|
||||||
# 修改前:使用 asyncio.create_task() 直接执行
|
|
||||||
asyncio.create_task(execute_agent_task_async())
|
|
||||||
|
|
||||||
# 修改后:提交到Celery队列
|
|
||||||
if CELERY_AVAILABLE and execute_agent_task is not None:
|
|
||||||
celery_task = execute_agent_task.delay(
|
|
||||||
task_id=task_id,
|
|
||||||
message=request.message,
|
|
||||||
session_id=request.session_id or task_id,
|
|
||||||
history=request.history if request.history else []
|
|
||||||
)
|
|
||||||
agent_tasks[task_id]["celery_task_id"] = celery_task.id
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `/api/agent/status/{task_id}` 接口修改
|
|
||||||
```python
|
|
||||||
# 添加Celery状态查询
|
|
||||||
if CELERY_AVAILABLE and "celery_task_id" in task:
|
|
||||||
from celery_app import app as celery_app
|
|
||||||
celery_task = celery_app.AsyncResult(task["celery_task_id"])
|
|
||||||
# 根据celery_task.state更新状态
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 tasks.py
|
|
||||||
|
|
||||||
#### 修复 `update_state()` 调用
|
|
||||||
```python
|
|
||||||
# 修改前:self.update_state(state='RUNNING', ...) # task_id为空
|
|
||||||
# 修改后:
|
|
||||||
celery_task_id = self.request.id if self.request else task_id
|
|
||||||
self.update_state(
|
|
||||||
task_id=celery_task_id,
|
|
||||||
state='RUNNING',
|
|
||||||
meta={...}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 验证结果
|
|
||||||
|
|
||||||
### 3.1 服务状态
|
|
||||||
| 组件 | 状态 | 端口/PID |
|
|
||||||
|------|------|----------|
|
|
||||||
| FastAPI | 运行中 | :7865 (PID: 170514) |
|
|
||||||
| Celery Worker | 运行中 | 3进程 |
|
|
||||||
| Redis | 运行中 | :6379 |
|
|
||||||
| RAG API | 运行中 | :8001 |
|
|
||||||
|
|
||||||
### 3.2 任务提交流程验证
|
|
||||||
```bash
|
|
||||||
# 提交任务
|
|
||||||
curl -X POST http://localhost:7865/api/agent/execute \
|
|
||||||
-d '{"message": "测试任务", "session_id": "test_001"}'
|
|
||||||
|
|
||||||
# 响应
|
|
||||||
{"success": true, "task_id": "agent_task_...", "status": "pending"}
|
|
||||||
|
|
||||||
# 查询状态
|
|
||||||
curl http://localhost:7865/api/agent/status/{task_id}
|
|
||||||
|
|
||||||
# 状态流转: pending → running → completed
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 测试任务执行结果
|
|
||||||
- 任务ID: `agent_task_1775226937_cd1bce7d`
|
|
||||||
- Celery任务ID: `d99ef56b-25ab-4f42-b3c5-aa05bbdadcbb`
|
|
||||||
- 执行时间: ~8秒
|
|
||||||
- 结果: 成功完成意图识别和分析方案生成
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 已知问题
|
|
||||||
|
|
||||||
### 4.1 RNAseq QC执行问题
|
|
||||||
- 任务提交成功,但执行阶段返回 `分析失败: []`
|
|
||||||
- 需要进一步诊断Orchestrator执行逻辑
|
|
||||||
- 可能与输入数据路径或工具配置有关
|
|
||||||
|
|
||||||
### 4.2 服务启动方式
|
|
||||||
- 需要使用 `nohup` 或 `systemd` 保持服务后台运行
|
|
||||||
- 直接 `&` 后台运行会导致进程退出
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 文件变更
|
|
||||||
|
|
||||||
| 文件 | 变更类型 | 说明 |
|
|
||||||
|------|----------|------|
|
|
||||||
| `apps/web/vue3/main.py` | 修改 | Agent执行接口改用Celery |
|
|
||||||
| `tasks.py` | 修改 | 修复update_state task_id问题 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 后续计划
|
|
||||||
|
|
||||||
1. **修复RNAseq QC执行问题** - 诊断Orchestrator执行失败原因
|
|
||||||
2. **添加任务重试机制** - 失败任务自动重试
|
|
||||||
3. **完善日志记录** - 添加更详细的执行日志
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 参考
|
|
||||||
|
|
||||||
- 项目路径: `/home/xia-lab/wutx/00test/bbav20311/`
|
|
||||||
- Celery Worker启动: `celery -A celery_app worker --loglevel=info --concurrency=2`
|
|
||||||
- FastAPI启动: `nohup python3 apps/web/vue3/main.py > backend.log 2>&1 &`
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# 2026-04-04 项目记录 - bbav20311
|
|
||||||
|
|
||||||
## 项目信息
|
|
||||||
- **项目**: bbav20311 (BotanicalBaseAgent)
|
|
||||||
- **路径**: `/home/xia-lab/wutx/00test/bbav20311`
|
|
||||||
- **记录时间**: 2026-04-04
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 今日修订内容
|
|
||||||
|
|
||||||
### 1. 前端停止执行按钮 ✅
|
|
||||||
|
|
||||||
**需求**: 添加停止执行按钮,用户可随时取消正在运行的分析
|
|
||||||
|
|
||||||
**修改文件**: `/home/xia-lab/wutx/00test/bbav20311/apps/web/vue3/index.html`
|
|
||||||
|
|
||||||
**修改内容**:
|
|
||||||
- 左侧控制面板:执行按钮下方添加红色"⏹ 停止执行"按钮(执行时显示)
|
|
||||||
- 右侧结果面板:执行状态卡片添加"⏹ 停止"按钮
|
|
||||||
- 新增数据变量:`isExecuting`, `isStopping`, `currentTaskId`
|
|
||||||
- 新增方法:`stopExecution()` - 调用 `/api/agent/cancel/{task_id}` 停止任务
|
|
||||||
|
|
||||||
**按钮显示逻辑**:
|
|
||||||
```html
|
|
||||||
<!-- 执行按钮 -->
|
|
||||||
<el-button :disabled="!selectedStage || isExecuting" :loading="isExecuting">
|
|
||||||
{{ isExecuting ? '⏳ 执行中...' : '🚀 执行分析' }}
|
|
||||||
</el-button>
|
|
||||||
|
|
||||||
<!-- 停止按钮 -->
|
|
||||||
<el-button v-if="isExecuting" type="danger" @click="stopExecution">
|
|
||||||
{{ isStopping ? '⏹ 正在停止...' : '⏹ 停止执行' }}
|
|
||||||
</el-button>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. RNA-seq 文件检测修复 ✅
|
|
||||||
|
|
||||||
**问题**: Agent执行时(对话框输入)找不到 `samples.yaml`,但手动执行可以
|
|
||||||
|
|
||||||
**原因**:
|
|
||||||
- 对话框执行时 `session_id` 是 "session_xxx" 格式,不是 "job_xxx" 格式
|
|
||||||
- 原代码只查找最新的 job_ 目录,没有考虑配置文件位置
|
|
||||||
|
|
||||||
**修改文件**: `/home/xia-lab/wutx/00test/bbav20311/gfa_core/mas/core/orchestrator.py`
|
|
||||||
|
|
||||||
**修复内容**:
|
|
||||||
1. 新增辅助方法 `_get_input_dir()`:
|
|
||||||
```python
|
|
||||||
def _get_input_dir(self, session_id: str, stage_name: str = "") -> tuple[Path, str]:
|
|
||||||
"""获取输入目录,优先查找包含samples.yaml的job目录"""
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 应用到以下方法:
|
|
||||||
- `_execute_rnaseq_qc` (RNA-QC)
|
|
||||||
- `_execute_rnaseq_align` (RNA-ALN)
|
|
||||||
- `_execute_rnaseq_quant` (RNA-QUAN)
|
|
||||||
- `_execute_rnaseq_de` (RNA-DE)
|
|
||||||
- `_execute_rnaseq_enrich` (RNA-ENRICH)
|
|
||||||
- `_execute_kaks` (KaKs)
|
|
||||||
|
|
||||||
**查找逻辑**:
|
|
||||||
- 如果 `session_id` 不是 job_ 开头,查找所有 job_ 目录
|
|
||||||
- 优先选择包含 `samples.yaml` 或 `group_sample.yaml` 的目录
|
|
||||||
- 如果没有配置文件,使用最新的 job 目录
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. RNA-seq 缓存结果问题修复 ✅
|
|
||||||
|
|
||||||
**问题**: 对话框执行15秒就返回"完成",但实际执行需要15分钟
|
|
||||||
|
|
||||||
**原因**:
|
|
||||||
- 代码逻辑 `if has_results` 只要结果文件存在就返回成功
|
|
||||||
- 之前执行的结果文件还在,系统直接返回缓存结果
|
|
||||||
|
|
||||||
**修改文件**: `/home/xia-lab/wutx/00test/bbav20311/gfa_core/mas/core/orchestrator.py`
|
|
||||||
|
|
||||||
**修复内容**:
|
|
||||||
|
|
||||||
1. **严格的成功判断**:
|
|
||||||
```python
|
|
||||||
# 修改前:只要结果文件存在就算成功
|
|
||||||
if has_results:
|
|
||||||
|
|
||||||
# 修改后:必须执行成功且结果文件存在
|
|
||||||
execution_success = (result.returncode == 0) and has_results
|
|
||||||
if execution_success:
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **结果文件时效检查**:
|
|
||||||
```python
|
|
||||||
# 如果结果文件超过24小时,删除旧结果重新执行
|
|
||||||
if age_hours > 24:
|
|
||||||
shutil.rmtree(qc_dir)
|
|
||||||
qc_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 修改文件清单
|
|
||||||
|
|
||||||
| 文件路径 | 修改类型 | 说明 |
|
|
||||||
|---------|---------|------|
|
|
||||||
| `apps/web/vue3/index.html` | 修改 | 添加停止执行按钮、状态管理 |
|
|
||||||
| `gfa_core/mas/core/orchestrator.py` | 修改 | 添加`_get_input_dir()`方法、修复RNA-seq执行逻辑、修复缓存问题 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 待验证
|
|
||||||
|
|
||||||
- [ ] 停止按钮在前端正确显示
|
|
||||||
- [ ] 点击停止按钮能正确取消任务
|
|
||||||
- [ ] 对话框执行RNA-seq能正确找到samples.yaml
|
|
||||||
- [ ] RNA-seq执行时间符合预期(约15分钟)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 相关记录
|
|
||||||
|
|
||||||
- 昨日记录: `memory/2026-04-03.md` - Celery集成修订
|
|
||||||
- 项目索引: `MEMORY.md`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*记录时间: 2026-04-04 22:57*
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
# 2026-04-05 项目记录 - bbav20311
|
|
||||||
|
|
||||||
## 项目信息
|
|
||||||
- **项目**: bbav20311 (BotanicalBaseAgent)
|
|
||||||
- **路径**: `/home/xia-lab/wutx/00test/bbav20311`
|
|
||||||
- **记录时间**: 2026-04-05
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 今日修订内容
|
|
||||||
|
|
||||||
### 1. RNA-seq 分析优化
|
|
||||||
|
|
||||||
#### 1.1 Agent意图识别优化 ✅
|
|
||||||
**问题**: 对话框执行RNA-seq分析需要16分钟才弹出参数确认
|
|
||||||
|
|
||||||
**原因**: Orchestrator 意图识别流程耗时过长(LLM多次调用)
|
|
||||||
|
|
||||||
**解决方案**: 添加 `fast_intent.py` 关键词匹配,置信度>0.8时跳过LLM调用
|
|
||||||
|
|
||||||
**修改文件**:
|
|
||||||
- `gfa_core/mas/core/fast_intent.py` (新增)
|
|
||||||
- `gfa_core/mas/core/orchestrator.py`
|
|
||||||
|
|
||||||
**效果**: 16分钟 → <5秒
|
|
||||||
|
|
||||||
#### 1.2 RNA-seq 改为异步执行 ✅
|
|
||||||
**问题**: 点击确认后同步执行,前端卡死14.5分钟
|
|
||||||
|
|
||||||
**解决方案**:
|
|
||||||
- Agent返回意图,不直接执行
|
|
||||||
- 用户确认后通过 `/api/execute` 异步执行
|
|
||||||
- 前端轮询显示进度
|
|
||||||
|
|
||||||
**修改文件**:
|
|
||||||
- `gfa_core/mas/core/orchestrator.py`
|
|
||||||
- `apps/web/vue3/main.py` (添加 ASYNC_STAGES)
|
|
||||||
|
|
||||||
#### 1.3 取消超时限制 ✅
|
|
||||||
**问题**: RNA-seq分析可能运行数小时,默认7200秒超时
|
|
||||||
|
|
||||||
**解决方案**: `timeout=None` (无超时限制)
|
|
||||||
|
|
||||||
**修改文件**:
|
|
||||||
- `gfa_core/mas/core/orchestrator.py`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. RNA-seq 目录结构标准化
|
|
||||||
|
|
||||||
#### 2.1 统一目录结构
|
|
||||||
**问题**: RNA-seq各阶段目录结构不一致
|
|
||||||
- `rnaseq_align/rnaseq_align/` (嵌套)
|
|
||||||
- `rnaseq_de/rnaseq_de/` (嵌套)
|
|
||||||
- `rnaseq_enrich/rnaseq_enrich/analysis/` (三重嵌套)
|
|
||||||
|
|
||||||
**标准结构**: `stage/analysis/`
|
|
||||||
|
|
||||||
**修改文件**:
|
|
||||||
- `apps/cli/run_rnaseq_align.py`: `rnaseq_align/` → `analysis/`
|
|
||||||
- `apps/cli/run_rnaseq_de.py`: `rnaseq_de/` → `analysis/`
|
|
||||||
- `apps/cli/run_rnaseq_enrich.py`: `rnaseq_enrich/analysis/` → `analysis/`
|
|
||||||
|
|
||||||
#### 2.2 RNA-QC 目录结构优化
|
|
||||||
**问题**: 直接输出到 final,无中间目录
|
|
||||||
|
|
||||||
**解决方案**:
|
|
||||||
- 中间文件: `rnaseq_qc/analysis/`
|
|
||||||
- 最终结果: `final/` (标准命名)
|
|
||||||
|
|
||||||
**文件存放策略**:
|
|
||||||
| 文件类型 | 存放位置 | 说明 |
|
|
||||||
|---------|---------|------|
|
|
||||||
| `*.clean.fastq.gz` | `final/` | 下游分析需要 |
|
|
||||||
| `*_fastqc.html` | `final/` + `analysis/` | 前端展示 |
|
|
||||||
| `*_cutadapt.log` | `final/` | 统计信息 |
|
|
||||||
| `*_fastqc.zip` | `analysis/` | 大文件备用 |
|
|
||||||
|
|
||||||
**修改文件**:
|
|
||||||
- `apps/cli/run_rnaseq_qc.py`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. RNA-seq 文件命名标准化
|
|
||||||
|
|
||||||
#### 3.1 统一命名格式
|
|
||||||
**格式**: `{prefix}.rnaseq_{stage}.{filename}`
|
|
||||||
|
|
||||||
**示例**:
|
|
||||||
- `Ft.SPL.rnaseq_qc.SRR6847695_1_fastqc.html`
|
|
||||||
- `Ft.SPL.rnaseq_de.treat_vs_control.DE_results.txt`
|
|
||||||
- `Ft.SPL.rnaseq_align.SRR6847695.sorted.bam`
|
|
||||||
|
|
||||||
**修改文件**:
|
|
||||||
- `apps/cli/run_rnaseq_qc.py`
|
|
||||||
- `apps/cli/run_rnaseq_de.py`
|
|
||||||
- `apps/cli/run_rnaseq_align.py`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. 基因家族特异性分析
|
|
||||||
|
|
||||||
#### 4.1 添加家族特异性绘图
|
|
||||||
**功能**: 为基因家族生成特异性 MA图和火山图
|
|
||||||
|
|
||||||
**输出文件**:
|
|
||||||
- `{prefix}.rnaseq_de.{family_name}_{comp_name}_family_MA_plot.pdf/png`
|
|
||||||
- `{prefix}.rnaseq_de.{family_name}_{comp_name}_family_volcano_plot.pdf/png`
|
|
||||||
|
|
||||||
**修改文件**:
|
|
||||||
- `apps/cli/run_rnaseq_de.py`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. Bug修复
|
|
||||||
|
|
||||||
#### 5.1 修复 `re` 模块未定义错误
|
|
||||||
**文件**: `gfa_core/mas/core/orchestrator.py`
|
|
||||||
|
|
||||||
#### 5.2 修复 `prefix` 变量未定义错误
|
|
||||||
**文件**: `apps/cli/run_rnaseq_de.py`
|
|
||||||
|
|
||||||
#### 5.3 修复 `--final-dir` 参数传递
|
|
||||||
**文件**: `apps/cli/run_rnaseq_de.py` (添加调试信息)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 修改文件清单
|
|
||||||
|
|
||||||
| 文件路径 | 修改类型 | 说明 |
|
|
||||||
|---------|---------|------|
|
|
||||||
| `gfa_core/mas/core/fast_intent.py` | 新增 | 快速意图识别 |
|
|
||||||
| `gfa_core/mas/core/orchestrator.py` | 修改 | 集成快速意图、取消超时、RNA-seq返回意图 |
|
|
||||||
| `apps/cli/run_rnaseq_qc.py` | 修改 | 添加analysis目录、标准化命名、文件存放策略 |
|
|
||||||
| `apps/cli/run_rnaseq_align.py` | 修改 | 目录结构标准化 |
|
|
||||||
| `apps/cli/run_rnaseq_de.py` | 修改 | 目录结构标准化、家族特异性绘图 |
|
|
||||||
| `apps/cli/run_rnaseq_enrich.py` | 修改 | 目录结构标准化 |
|
|
||||||
| `apps/web/vue3/main.py` | 修改 | 添加ASYNC_STAGES、进度提示 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 测试结果
|
|
||||||
|
|
||||||
### RNA-seq DE 测试
|
|
||||||
- **意图识别**: 3秒内返回 `rnaseq_de`
|
|
||||||
- **异步执行**: 任务提交后立即返回
|
|
||||||
- **目录结构**: `rnaseq_de/analysis/` ✅
|
|
||||||
- **文件命名**: `Ft.SPL.rnaseq_de.*` ✅
|
|
||||||
- **家族绘图**: 正常生成 ✅
|
|
||||||
|
|
||||||
### RNA-seq QC 测试
|
|
||||||
- **目录结构**: `rnaseq_qc/analysis/` ✅
|
|
||||||
- **文件存放**: clean reads + fastqc.html 在 final ✅
|
|
||||||
- **文件命名**: `Ft.SPL.rnaseq_qc.*` ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 待办事项
|
|
||||||
- [ ] RNA-QC 执行完成验证
|
|
||||||
- [ ] 前端结果展示测试
|
|
||||||
- [ ] 完整RNA-seq流程测试(QC→Align→Quant→DE→Enrich)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*记录时间: 2026-04-05 22:14*
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
# 2026-04-06 项目记录 - bbav20311
|
|
||||||
|
|
||||||
## 项目信息
|
|
||||||
- **项目**: bbav20311 (BotanicalBaseAgent)
|
|
||||||
- **路径**: `/home/xia-lab/wutx/00test/bbav20311`
|
|
||||||
- **记录时间**: 2026-04-06
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 今日完成内容
|
|
||||||
|
|
||||||
### 1. PEP + PPI 技能化完成 ✅
|
|
||||||
|
|
||||||
#### 1.1 新建技能目录
|
|
||||||
```
|
|
||||||
skills/pep/
|
|
||||||
├── SKILL.md # 蛋白特征分析文档
|
|
||||||
├── config.yaml # 技能配置
|
|
||||||
└── run.py # 主程序 (理化性质+可视化)
|
|
||||||
|
|
||||||
skills/ppi/
|
|
||||||
├── SKILL.md # PPI网络分析文档
|
|
||||||
├── config.yaml # 技能配置
|
|
||||||
└── run.py # 主程序 (STRING API+网络构建)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 1.2 技能功能
|
|
||||||
**PEP Skill**:
|
|
||||||
- 理化性质分析: 分子量、等电点、疏水性(GRAVY)、脂肪族指数
|
|
||||||
- 跨膜结构预测: DeepTMHMM
|
|
||||||
- 亚细胞定位预测: Plant-mPLoc + WoLF PSORT
|
|
||||||
- 可视化输出: PDF/PNG图表 + Markdown报告 + Manifest
|
|
||||||
|
|
||||||
**PPI Skill**:
|
|
||||||
- STRING数据库查询
|
|
||||||
- 互作网络构建
|
|
||||||
- 网络统计: 节点数、边数、置信度分布
|
|
||||||
- 可视化输出: TSV数据表 + PDF网络图 + Markdown报告 + Manifest
|
|
||||||
|
|
||||||
#### 1.3 Pipeline集成
|
|
||||||
- **PEP路由**: 更新 `apps/web/pipeline_backend.py`,支持技能化版本
|
|
||||||
- **PPI路由**: 更新 `apps/web/pipeline_backend.py`,支持技能化版本
|
|
||||||
- **产物复制**: 技能执行后自动复制产物到 `final/` 目录
|
|
||||||
|
|
||||||
#### 1.4 Demo测试通过
|
|
||||||
```bash
|
|
||||||
# PEP测试
|
|
||||||
python skills/pep/run.py --demo --prefix test --output /tmp/pep_test
|
|
||||||
# 输出: 3 proteins analyzed, manifest generated
|
|
||||||
|
|
||||||
# PPI测试
|
|
||||||
python skills/ppi/run.py --demo --prefix test --output /tmp/ppi_test
|
|
||||||
# 输出: 2/3 genes in network, 1 interaction found
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. PPI技能增强 ✅
|
|
||||||
|
|
||||||
#### 2.1 同源映射支持
|
|
||||||
- 添加 `--mapping` 参数
|
|
||||||
- 支持非模式植物(如苦荞)基因ID映射到拟南芥基因ID
|
|
||||||
- 自动基因ID转换: 苦荞ID → 拟南芥ID
|
|
||||||
|
|
||||||
#### 2.2 STRING URL生成
|
|
||||||
- 生成STRING网页链接
|
|
||||||
- 保存到 `{prefix}_url.txt` 文件
|
|
||||||
- Report和Manifest包含STRING链接
|
|
||||||
|
|
||||||
#### 2.3 测试验证
|
|
||||||
```bash
|
|
||||||
# 带映射的PPI分析
|
|
||||||
python skills/ppi/run.py \
|
|
||||||
--gene-ids ft_genes.txt \
|
|
||||||
--species ath \
|
|
||||||
--mapping ortholog_pairs.txt \
|
|
||||||
--prefix test --output ./ppi
|
|
||||||
|
|
||||||
# 结果: 3/3 genes mapped, 2/3 in network, 1 interaction
|
|
||||||
# STRING URL: https://string-db.org/cgi/network?identifiers=...
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Ka/Ks分析修复 ✅
|
|
||||||
|
|
||||||
#### 3.1 工具路径修复
|
|
||||||
- **MUSCLE**: 使用 `conda run -n test4py311 muscle`
|
|
||||||
- **pal2nal.pl**: 使用 `conda run -n test4py311 pal2nal.pl`
|
|
||||||
- **yn00**: 使用 `conda run -n test4py311 yn00`
|
|
||||||
|
|
||||||
#### 3.2 Pipeline参数修复
|
|
||||||
- 修复 `-p` 参数为 `--prefix-name`
|
|
||||||
- 文件: `apps/web/pipeline_backend.py`
|
|
||||||
|
|
||||||
#### 3.3 shutil错误修复
|
|
||||||
- **问题**: 局部 `import shutil` 导致变量作用域问题
|
|
||||||
- **修复**: 删除PEP和PPI阶段的局部 `import shutil`
|
|
||||||
- **文件**: `apps/web/pipeline_backend.py`
|
|
||||||
|
|
||||||
#### 3.4 测试验证
|
|
||||||
- Ka/Ks分析成功执行 (7/8 pairs)
|
|
||||||
- 结果文件正确生成并复制到 `final/`
|
|
||||||
- 执行时间: ~20秒(正常)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. 前端参数确认强制化 ✅
|
|
||||||
|
|
||||||
#### 4.1 问题
|
|
||||||
- Agent模式下可能跳过参数确认对话框
|
|
||||||
- 用户要求所有执行都必须经过参数确认
|
|
||||||
|
|
||||||
#### 4.2 修复
|
|
||||||
- 删除Agent自动执行分支
|
|
||||||
- 强制所有执行都显示参数确认对话框
|
|
||||||
- **文件**: `apps/web/vue3/index.html`
|
|
||||||
|
|
||||||
#### 4.3 修改代码
|
|
||||||
```javascript
|
|
||||||
// 旧代码
|
|
||||||
if (autoExecute.value) {
|
|
||||||
doExecute(); // 直接执行
|
|
||||||
} else {
|
|
||||||
confirmDialogVisible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新代码
|
|
||||||
confirmDialogVisible.value = true; // 强制确认
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. 契约文档添加 ✅
|
|
||||||
|
|
||||||
#### 5.1 新建契约文档
|
|
||||||
- `gfa_core/schemas/stages/pep_contract_v1.md`
|
|
||||||
- `gfa_core/schemas/stages/ppi_contract_v1.md`
|
|
||||||
|
|
||||||
#### 5.2 契约内容
|
|
||||||
- 阶段信息 (名称、描述、技能目录)
|
|
||||||
- 输入契约 (必需/可选输入)
|
|
||||||
- 输出契约 (final/ + analysis/ 产物)
|
|
||||||
- Manifest结构示例
|
|
||||||
- 依赖环境 (Conda + 工具)
|
|
||||||
- 执行命令示例
|
|
||||||
- 上下游依赖关系
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 修改文件清单
|
|
||||||
|
|
||||||
| 文件路径 | 修改类型 | 说明 |
|
|
||||||
|---------|---------|------|
|
|
||||||
| `skills/pep/SKILL.md` | 新增 | 蛋白特征分析技能文档 |
|
|
||||||
| `skills/pep/config.yaml` | 新增 | PEP技能配置 |
|
|
||||||
| `skills/pep/run.py` | 新增 | PEP技能主程序 |
|
|
||||||
| `skills/ppi/SKILL.md` | 新增 | PPI网络分析技能文档 |
|
|
||||||
| `skills/ppi/config.yaml` | 新增 | PPI技能配置 |
|
|
||||||
| `skills/ppi/run.py` | 新增 | PPI技能主程序 |
|
|
||||||
| `gfa_core/schemas/stages/pep_contract_v1.md` | 新增 | PEP阶段契约 |
|
|
||||||
| `gfa_core/schemas/stages/ppi_contract_v1.md` | 新增 | PPI阶段契约 |
|
|
||||||
| `apps/web/pipeline_backend.py` | 修改 | PEP/PPI路由、Ka/Ks参数、shutil修复 |
|
|
||||||
| `apps/web/vue3/index.html` | 修改 | 强制参数确认对话框 |
|
|
||||||
| `apps/cli/run_kaks_v2.py` | 修改 | 工具路径修复 (muscle/pal2nal/yn00) |
|
|
||||||
| `MEMORY.md` | 修改 | 更新技能化状态 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 技能化状态更新
|
|
||||||
|
|
||||||
| 阶段 | 技能目录 | 状态 |
|
|
||||||
|------|----------|------|
|
|
||||||
| pep | skills/pep/ | ✅ 已技能化 (2026-04-06) |
|
|
||||||
| ppi | skills/ppi/ | ✅ 已技能化 (2026-04-06) |
|
|
||||||
|
|
||||||
**总计**: 18/18 阶段技能化完成 (100%)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 已解决问题
|
|
||||||
|
|
||||||
### 1. Ka/Ks `json` 未定义错误 ✅ (2026-04-07 00:26)
|
|
||||||
|
|
||||||
#### 问题现象
|
|
||||||
- 前端显示: `❌ Ka/Ks分析出错: name 'json' is not defined`
|
|
||||||
- 实际: Ka/Ks技能执行成功,结果文件已生成
|
|
||||||
- 用户反馈: 参数确认对话框没有弹出
|
|
||||||
|
|
||||||
#### 根本原因
|
|
||||||
**文件**: `gfa_core/mas/core/orchestrator.py`
|
|
||||||
**问题**: 缺少 `import json`,但代码中使用了 `json.load()`
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 第2017行使用了json
|
|
||||||
with open(kaks_manifest) as f:
|
|
||||||
manifest = json.load(f) # ❌ NameError: name 'json' is not defined
|
|
||||||
```
|
|
||||||
|
|
||||||
**为什么之前没找到?**
|
|
||||||
1. 错误被 try-except 捕获,返回了包装后的错误信息
|
|
||||||
2. 日志显示 stdout 成功,但异常在结果解析阶段抛出
|
|
||||||
3. 错误信息被误解为前端问题
|
|
||||||
|
|
||||||
#### 修复方案
|
|
||||||
```python
|
|
||||||
# 文件开头添加
|
|
||||||
import json
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 调试过程
|
|
||||||
1. 添加前端调试日志 (showConfirm/doExecute)
|
|
||||||
2. 添加后端调试日志 (/api/execute)
|
|
||||||
3. 分析日志发现错误来自 orchestrator.py
|
|
||||||
4. 检查 orchestrator.py 发现缺少 json 导入
|
|
||||||
5. 修复并验证
|
|
||||||
|
|
||||||
#### 相关修改
|
|
||||||
- `gfa_core/mas/core/orchestrator.py`: 添加 `import json`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 记录时间
|
|
||||||
*2026-04-06 22:14*
|
|
||||||
*更新: 2026-04-07 00:26 (解决json错误)*
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
# 2026-04-07 bbav20311 Agent提示词集成完成报告
|
|
||||||
|
|
||||||
## 执行摘要
|
|
||||||
|
|
||||||
**任务**: 将8个Agent的详细提示词集成到bbav20311系统
|
|
||||||
**状态**: ✅ 已完成并验证
|
|
||||||
**耗时**: ~3小时
|
|
||||||
**成果**: 8个提示词文件(59.8KB)成功集成,Agent行为显著改善
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 执行过程
|
|
||||||
|
|
||||||
### 1. 提示词文件创建 (已完成)
|
|
||||||
|
|
||||||
| Agent | 文件名 | 大小 | 核心内容 |
|
|
||||||
|:---|:---|:---:|:---|
|
|
||||||
| RouterAgent | router_agent.md | 6,016 B | 4种意图模式、参数提取规则 |
|
|
||||||
| PlanningAgent | planning_agent.md | 9,196 B | 20阶段规划、依赖关系 |
|
|
||||||
| ExecutionAgent | execution_agent.md | 7,585 B | 20阶段执行、CLI工具映射 |
|
|
||||||
| DiagnosisAgent | diagnosis_agent.md | 6,665 B | 5类错误诊断、修复策略 |
|
|
||||||
| KnowledgeAgent | knowledge_agent.md | 6,628 B | RAG检索、知识引用格式 |
|
|
||||||
| ResultParserAgent | result_parser_agent.md | 8,443 B | 20阶段结果解析规范 |
|
|
||||||
| ValidatorAgent | validator_agent.md | 7,988 B | 5维度验证、评分体系 |
|
|
||||||
| LearningAgent | learning_agent.md | 8,758 B | 参数优化、学习机制 |
|
|
||||||
|
|
||||||
**总计**: 8个文件,61,279 bytes (59.8 KB)
|
|
||||||
|
|
||||||
### 2. 代码修改 (已完成)
|
|
||||||
|
|
||||||
#### 修改1: BaseAgent._build_system_prompt()
|
|
||||||
- **文件**: `gfa_core/mas/agents/base.py`
|
|
||||||
- **功能**: 从 `/prompts/{name}_agent.md` 加载提示词
|
|
||||||
- **回退机制**: 文件不存在时使用默认提示词
|
|
||||||
|
|
||||||
#### 修改2: BaseAgent._call_llm() 和 _acall_llm()
|
|
||||||
- **功能**: 自动附加系统提示词到用户请求
|
|
||||||
- **组合格式**:
|
|
||||||
```
|
|
||||||
{系统提示词}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
用户请求: {user_input}
|
|
||||||
|
|
||||||
请根据上述系统提示词的指导处理此请求。
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 服务重启 (已完成)
|
|
||||||
|
|
||||||
- **原PID**: 617807 (已停止)
|
|
||||||
- **新PID**: 622439 → 620755 → 622439 (最终稳定)
|
|
||||||
- **API状态**: ✅ 正常响应
|
|
||||||
- **端口**: 7865
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 验证结果
|
|
||||||
|
|
||||||
### 验证1: 提示词加载机制
|
|
||||||
|
|
||||||
```
|
|
||||||
PROMPTS_DIR: /home/xia-lab/wutx/00test/bbav20311/prompts
|
|
||||||
目录存在: True
|
|
||||||
|
|
||||||
提示词文件:
|
|
||||||
✓ diagnosis_agent 6,665 bytes
|
|
||||||
✓ execution_agent 7,585 bytes
|
|
||||||
✓ knowledge_agent 6,628 bytes
|
|
||||||
✓ learning_agent 8,758 bytes
|
|
||||||
✓ planning_agent 9,196 bytes
|
|
||||||
✓ result_parser_agent 8,443 bytes
|
|
||||||
✓ router_agent 6,016 bytes
|
|
||||||
✓ validator_agent 7,988 bytes
|
|
||||||
─────────────────────────────────────
|
|
||||||
总计: 8个文件, 61,279 bytes (59.8 KB)
|
|
||||||
|
|
||||||
RouterAgent测试:
|
|
||||||
Agent名称: router
|
|
||||||
提示词长度: 3,652 字符
|
|
||||||
提示词来源: 文件 ✅
|
|
||||||
```
|
|
||||||
|
|
||||||
### 验证2: 提示词组合效果
|
|
||||||
|
|
||||||
**输入**: `"帮我分析拟南芥的SPL基因家族"` (15字符)
|
|
||||||
|
|
||||||
**输出**:
|
|
||||||
```
|
|
||||||
组合后总长度: 3,701 字符
|
|
||||||
增长倍数: 246.7x
|
|
||||||
|
|
||||||
结构分析:
|
|
||||||
├─ 系统提示词: 3,652 字符 (98.7%)
|
|
||||||
├─ 分隔符: 45 字符
|
|
||||||
├─ 用户请求: 15 字符 (0.4%)
|
|
||||||
└─ 指令后缀: 30 字符
|
|
||||||
──────────────────────────────────
|
|
||||||
总计: 3,701 字符
|
|
||||||
```
|
|
||||||
|
|
||||||
### 验证3: 实际API调用验证
|
|
||||||
|
|
||||||
#### 测试1: RouterAgent - 分析请求
|
|
||||||
```
|
|
||||||
输入: "帮我分析拟南芥的SPL基因家族"
|
|
||||||
|
|
||||||
处理链:
|
|
||||||
[RouterNode] Using RouterAgent to classify query
|
|
||||||
[SLOW INTENT] Falling back to LLM intent recognition
|
|
||||||
[DEBUG] Intent analysis: type=unknown, family=SPL, species=At
|
|
||||||
[RouterNode] RouterAgent classified as: analysis ✅
|
|
||||||
|
|
||||||
结果:
|
|
||||||
- 识别模式: analysis
|
|
||||||
- 基因家族: SPL
|
|
||||||
- 物种: At (拟南芥)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 测试2: RouterAgent - FAQ请求
|
|
||||||
```
|
|
||||||
输入: "什么是SPL基因家族?"
|
|
||||||
|
|
||||||
处理链:
|
|
||||||
[RouterNode] Using RouterAgent to classify query
|
|
||||||
[RouterNode] RouterAgent classified as: faq ✅
|
|
||||||
|
|
||||||
结果:
|
|
||||||
- 识别模式: faq
|
|
||||||
- 路由至: KnowledgeAgent
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 测试3: PlanningAgent - 任务规划
|
|
||||||
```
|
|
||||||
输入: "分析水稻SPL基因家族,做完整分析"
|
|
||||||
|
|
||||||
结果:
|
|
||||||
- API状态: 200 OK
|
|
||||||
- 规划阶段: 多个阶段
|
|
||||||
- 包含: gene-identify, phylogeny等
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 关键改进
|
|
||||||
|
|
||||||
### 修改前 vs 修改后
|
|
||||||
|
|
||||||
| 维度 | 修改前 | 修改后 | 提升 |
|
|
||||||
|:---|:---|:---|:---:|
|
|
||||||
| 提示词来源 | 硬编码字符串 | 文件加载 | - |
|
|
||||||
| RouterAgent提示词 | ~200字符 | 3,652字符 | 18x |
|
|
||||||
| 意图模式定义 | 简单描述 | 4种详细模式 | 4x |
|
|
||||||
| 参数提取 | 无 | 自动提取family/species | ✓ |
|
|
||||||
| 阶段覆盖 | 11阶段 | 20阶段 | 1.8x |
|
|
||||||
| 错误分类 | 无 | 5类详细分类 | ✓ |
|
|
||||||
| 知识引用 | 无 | 带来源引用 | ✓ |
|
|
||||||
|
|
||||||
### 实际效果
|
|
||||||
|
|
||||||
1. **意图识别更准确**
|
|
||||||
- 能区分analysis/faq/consultation/interpretation
|
|
||||||
- 置信度提升到0.85+
|
|
||||||
|
|
||||||
2. **参数提取自动化**
|
|
||||||
- 自动识别基因家族名称(SPL, WRKY等)
|
|
||||||
- 自动识别物种(拟南芥→At, 水稻→rice)
|
|
||||||
|
|
||||||
3. **任务规划更完整**
|
|
||||||
- 覆盖20个分析阶段
|
|
||||||
- 包含RNA-seq分析流程
|
|
||||||
|
|
||||||
4. **知识回答更专业**
|
|
||||||
- 带引用格式
|
|
||||||
- 可追溯来源
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 文件位置
|
|
||||||
|
|
||||||
### 提示词文件
|
|
||||||
```
|
|
||||||
/home/xia-lab/wutx/00test/bbav20311/prompts/
|
|
||||||
├── router_agent.md
|
|
||||||
├── planning_agent.md
|
|
||||||
├── execution_agent.md
|
|
||||||
├── diagnosis_agent.md
|
|
||||||
├── knowledge_agent.md
|
|
||||||
├── result_parser_agent.md
|
|
||||||
├── validator_agent.md
|
|
||||||
└── learning_agent.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### 代码修改
|
|
||||||
```
|
|
||||||
/home/xia-lab/wutx/00test/bbav20311/gfa_core/mas/agents/base.py
|
|
||||||
- _build_system_prompt() 方法
|
|
||||||
- _call_llm() 方法
|
|
||||||
- _acall_llm() 方法
|
|
||||||
```
|
|
||||||
|
|
||||||
### 测试脚本
|
|
||||||
```
|
|
||||||
/home/xia-lab/wutx/00test/bbav20311/test_simple.py
|
|
||||||
/home/xia-lab/wutx/00test/bbav20311/test_e2e.py
|
|
||||||
/home/xia-lab/wutx/00test/bbav20311/test_api_detailed.py
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 后续建议
|
|
||||||
|
|
||||||
1. **监控效果**
|
|
||||||
- 收集实际使用中的意图识别准确率
|
|
||||||
- 监控参数提取成功率
|
|
||||||
|
|
||||||
2. **持续优化**
|
|
||||||
- 根据用户反馈调整提示词
|
|
||||||
- 补充更多示例和边界情况
|
|
||||||
|
|
||||||
3. **扩展覆盖**
|
|
||||||
- 考虑为其他Agent添加更详细的示例
|
|
||||||
- 补充多语言支持
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 结论
|
|
||||||
|
|
||||||
✅ **bbav20311 Agent系统的提示词集成已完全完成并验证生效**
|
|
||||||
|
|
||||||
- 8个Agent都使用各自的详细提示词文件
|
|
||||||
- 意图识别、参数提取、任务规划能力显著提升
|
|
||||||
- 系统运行稳定,API响应正常
|
|
||||||
|
|
||||||
**完成时间**: 2026-04-07 21:00
|
|
||||||
**执行者**: xialab的幸运星
|
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
# 基因家族结果解读框架(鉴定到 PEP)
|
||||||
|
|
||||||
|
本文件是报告结构和语言规范,不是结果事实。所有数字、成员、分组和结论必须来自任务证据包。
|
||||||
|
|
||||||
|
## 1. 首页和一页式摘要
|
||||||
|
|
||||||
|
- 写明任务编号、家族、目标物种、参考物种、分析范围和报告时间。
|
||||||
|
- 用 5—8 条短句回答:鉴定到多少成员、分类是否清楚、保守特征是什么、分布和复制有什么证据、调控预测有什么线索、蛋白有什么特点、最大风险是什么。
|
||||||
|
- 明确“计算结果用于筛选线索,不能替代实验验证”。
|
||||||
|
|
||||||
|
## 2. 分析流程与完成情况
|
||||||
|
|
||||||
|
- 按真实执行顺序列出阶段、状态、关键产物和缺失项。
|
||||||
|
- 区分成功、部分成功、失败和未运行。
|
||||||
|
- 缺失阶段不能由相邻阶段补写。
|
||||||
|
|
||||||
|
## 3. 统一图解读方法
|
||||||
|
|
||||||
|
每个逻辑图必须有独立小节,依次回答:
|
||||||
|
|
||||||
|
1. 图用来回答什么问题。
|
||||||
|
2. 本任务证据能确认什么。
|
||||||
|
3. 用普通语言解释它可能意味着什么。
|
||||||
|
4. 哪些细节仍需要打开原图或原始表复核。
|
||||||
|
5. 这张图不能证明什么。
|
||||||
|
|
||||||
|
不要只介绍横轴、纵轴和颜色。首次出现专业词时立即解释,例如“motif(蛋白中较短、经常重复出现的保守片段)”。
|
||||||
|
|
||||||
|
## 4. 基因家族鉴定
|
||||||
|
|
||||||
|
- 输入蛋白总数、HMM 命中、BLAST 命中、候选数、最终成员数。
|
||||||
|
- 对照成员 ID 文件和 PEP 序列数是否一致。
|
||||||
|
- 说明成员筛选依据和被排除对象的原因。
|
||||||
|
- 不能把全基因组蛋白数写成家族成员数。
|
||||||
|
- 鉴定成功只说明找到了符合规则的成员,不能直接证明每个成员功能相同。
|
||||||
|
|
||||||
|
## 5. 系统发育和参考分类
|
||||||
|
|
||||||
|
- 解读普通树、环形树、多序列比对图、参考分类树和分类置信度表。
|
||||||
|
- 说明树上靠近表示序列更相似,不等于功能完全相同。
|
||||||
|
- 有分支支持率时说明分支稳定性;没有时明确写缺口。
|
||||||
|
- 参考物种用于辅助命名和分类,不能替代目标物种实验。
|
||||||
|
- subfamily confirm 要写清自动分类、人工确认和最终分类之间的关系。
|
||||||
|
|
||||||
|
## 6. Motif
|
||||||
|
|
||||||
|
- 解读 SeqLogo、motif 数量统计、排列图、树与 motif 组合图。
|
||||||
|
- 说明同组成员是否共享相似片段,是否有成员出现缺失、增加或顺序变化。
|
||||||
|
- motif 只提供结构和潜在功能线索,不能单独确定生物学功能。
|
||||||
|
- 没有成员级位点表时,不得编造成员间具体差异。
|
||||||
|
|
||||||
|
## 7. Domain
|
||||||
|
|
||||||
|
- 解读主要功能区域统计、树与 domain 结构图、多区域或重复区域。
|
||||||
|
- 重点核对显著命中数、覆盖成员数和主要类型。
|
||||||
|
- manifest 与原始结果冲突时,以风险提示为准,不写成确定结论。
|
||||||
|
- domain 完整性支持蛋白具备某类功能基础,但不等于功能已经验证。
|
||||||
|
|
||||||
|
## 8. 基因结构
|
||||||
|
|
||||||
|
- 解读外显子/内含子结构图、树与结构组合图。
|
||||||
|
- 比较同组成员的结构是否大体相似,标记超长或异常成员。
|
||||||
|
- 结构差异可能提示分化,也可能来自注释问题,必须保留两种可能。
|
||||||
|
|
||||||
|
## 9. 综合组合图
|
||||||
|
|
||||||
|
- 对树、motif、domain、基因结构同图展示进行联合解释。
|
||||||
|
- 判断分类与结构证据是否一致。
|
||||||
|
- 指出证据冲突成员和需要复核的成员。
|
||||||
|
- 组合图整合的是多类计算证据,不是功能实验结果。
|
||||||
|
|
||||||
|
## 10. 启动子元件
|
||||||
|
|
||||||
|
- 解读元件总量、类别分布、基因×元件热图、位置分布、富集、共现和复杂度图。
|
||||||
|
- TATA、CAAT 等核心元件高频出现不能单独写成胁迫或激素响应。
|
||||||
|
- ABRE、MYB/MYC、LTR 等只能写成潜在调控线索。
|
||||||
|
- 没有表达数据时,不能写成基因已经响应某种处理。
|
||||||
|
|
||||||
|
## 11. TFBS
|
||||||
|
|
||||||
|
- 解读高频 motif 柱状图、基因×motif 热图、全部 motif 热图、位置分布、位置密度、启动子结构、TF—基因网络和 motif 来源图。
|
||||||
|
- 说明预测到的是可能结合的位置,不是实际结合实验。
|
||||||
|
- 网络中的连线表示预测关系,不代表直接调控已经发生。
|
||||||
|
- 每张图都应写明数据是否齐全以及成员级解释边界。
|
||||||
|
|
||||||
|
## 12. 种内共线性
|
||||||
|
|
||||||
|
- 解读 Circos 图、种内共线性图和复制类型统计。
|
||||||
|
- 只有检测到涉及家族成员的基因对时,才能讨论家族复制。
|
||||||
|
- 若家族成员基因对为 0,必须明确写“本任务未获得支持片段复制驱动扩张的证据”。
|
||||||
|
- 全基因组共线性数量不能当作家族共线性数量。
|
||||||
|
|
||||||
|
## 13. 染色体分布
|
||||||
|
|
||||||
|
- 解读染色体定位图、聚集分布和染色体偏好。
|
||||||
|
- 必须先确认实际定位到多少家族成员。
|
||||||
|
- 若定位数为 0 或存在高风险,明确说明该图当前不能用于染色体分布结论,并建议检查基因 ID 与注释版本。
|
||||||
|
|
||||||
|
## 14. 种间共线性和 Ortholog
|
||||||
|
|
||||||
|
- 解读多物种关系树、环形树、多物种共线性图、标记版图和两两物种图。
|
||||||
|
- 说明目标物种与参考物种之间是否找到可信对应关系。
|
||||||
|
- ortholog 表示跨物种较可能对应的基因,不等于功能完全相同。
|
||||||
|
- 未获得 Ka/Ks 时,不得推断选择压力。
|
||||||
|
|
||||||
|
## 15. PEP 蛋白特征
|
||||||
|
|
||||||
|
- 解读蛋白长度、分子量、等电点、亲疏水性、跨膜条带图、跨膜汇总图和亚细胞定位图。
|
||||||
|
- 标记长度异常、跨膜区域多、定位特殊或序列提取失败的成员。
|
||||||
|
- 预测结果用来筛选特殊成员,不能替代蛋白定位或功能实验。
|
||||||
|
- 说明 PEP 序列数是否与最终成员数一致。
|
||||||
|
|
||||||
|
## 16. 多证据综合和候选成员
|
||||||
|
|
||||||
|
至少使用以下证据建立候选成员表:
|
||||||
|
|
||||||
|
- 鉴定证据是否完整。
|
||||||
|
- 分类是否稳定。
|
||||||
|
- motif/domain/结构是否与同组成员一致或具有特殊性。
|
||||||
|
- 启动子和 TFBS 是否提供合理线索。
|
||||||
|
- 种内或种间保守关系是否有本地证据。
|
||||||
|
- 蛋白特征是否完整、是否存在异常。
|
||||||
|
- 是否有表达或实验数据支持;没有时明确写“本任务未提供”。
|
||||||
|
|
||||||
|
候选成员分为高、中、低优先级。评分只用于整理证据,不能解释为功能概率。
|
||||||
|
|
||||||
|
## 17. 主要限制
|
||||||
|
|
||||||
|
- 数据库和注释版本限制。
|
||||||
|
- 参考物种推断限制。
|
||||||
|
- 树分支支持、motif/domain 统计或成员级表缺失。
|
||||||
|
- 染色体定位、共线性或 ID 映射失败。
|
||||||
|
- 启动子、TFBS、定位和跨膜均属于预测。
|
||||||
|
- 缺少表达、遗传和功能实验时,不能形成因果结论。
|
||||||
|
|
||||||
|
## 18. 下一步验证建议
|
||||||
|
|
||||||
|
按优先级给出可执行建议,例如:
|
||||||
|
|
||||||
|
1. 修复高风险的数据或 ID 映射问题。
|
||||||
|
2. 核对候选成员序列和基因注释。
|
||||||
|
3. 用表达数据检查候选成员在相关组织或处理下是否变化。
|
||||||
|
4. 进行亚细胞定位、蛋白互作或启动子验证。
|
||||||
|
5. 对最高优先级成员开展过表达、敲除或互补实验。
|
||||||
|
|
||||||
|
## 19. 报告语言红线
|
||||||
|
|
||||||
|
- “证明”改为“支持”或“提供线索”。
|
||||||
|
- “调控”改为“可能参与调控”,除非有直接实验。
|
||||||
|
- “导致”改为“与……同时出现”或“可能有关”。
|
||||||
|
- “该家族成员全部具有某功能”改为“部分成员具有相关结构或预测线索”。
|
||||||
|
- 无结果不等于没有生物学现象,只能写“本任务在当前数据和方法下未检测到”。
|
||||||
|
|
||||||
|
## 20. 报告结尾
|
||||||
|
|
||||||
|
- 给出综合证据矩阵。
|
||||||
|
- 列出 3—10 个优先候选成员;证据不足时可以不强行列候选。
|
||||||
|
- 给出“读者最需要记住的五句话”。
|
||||||
|
- 最后一段再次说明:候选结论需要后续实验验证。
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from gfa_core.mas.agents.deepseek_summary import (
|
||||||
|
_validate_report,
|
||||||
|
collect_figure_inventory,
|
||||||
|
select_primary_scope,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_select_primary_scope_prefers_target_plus_reference() -> None:
|
||||||
|
summaries = [
|
||||||
|
{"stage": "identify", "scope_id": "YABBY.target-BNA.target_only"},
|
||||||
|
{"stage": "phylogeny", "scope_id": "YABBY.target-BNA.ref-At.target_plus_reference"},
|
||||||
|
{"stage": "motif", "scope_id": "YABBY.target-BNA.ref-At.target_plus_reference"},
|
||||||
|
]
|
||||||
|
|
||||||
|
assert select_primary_scope(summaries) == "YABBY.target-BNA.ref-At.target_plus_reference"
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_figure_inventory_deduplicates_identical_aliases(tmp_path: Path) -> None:
|
||||||
|
final_dir = tmp_path / "final"
|
||||||
|
final_dir.mkdir()
|
||||||
|
(final_dir / "A.tfbs_motif_source_summary.png").write_bytes(b"same-image")
|
||||||
|
(final_dir / "A.tfbs.motif_source_summary.png").write_bytes(b"same-image")
|
||||||
|
(final_dir / "A.pep_properties.png").write_bytes(b"different-image")
|
||||||
|
|
||||||
|
figures = collect_figure_inventory(final_dir, [], scope_id="scope")
|
||||||
|
|
||||||
|
assert len(figures) == 2
|
||||||
|
assert {item["stage"] for item in figures} == {"tfbs", "pep"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_validation_requires_every_figure_marker() -> None:
|
||||||
|
figures = [
|
||||||
|
{"marker": "[[FIG:a.png]]"},
|
||||||
|
{"marker": "[[FIG:b.png]]"},
|
||||||
|
]
|
||||||
|
report = (
|
||||||
|
"# 报告\n\n任务 job_1\n\n"
|
||||||
|
+ "\n\n".join(f"## 章节{i}\n内容" for i in range(1, 10))
|
||||||
|
+ "\n\n[[FIG:a.png]]\n"
|
||||||
|
+ ("通俗解读。" * 800)
|
||||||
|
)
|
||||||
|
|
||||||
|
validation = _validate_report(report, job_id="job_1", figures=figures)
|
||||||
|
|
||||||
|
assert validation["passed"] is False
|
||||||
|
assert validation["missing_figure_markers"] == ["[[FIG:b.png]]"]
|
||||||
@@ -0,0 +1,517 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def _source_block(source: str, start: str, end: str) -> str:
|
||||||
|
start_index = source.index(start)
|
||||||
|
return source[start_index:source.index(end, start_index)]
|
||||||
|
|
||||||
|
|
||||||
|
def _run_node(source: str) -> dict:
|
||||||
|
completed = subprocess.run(
|
||||||
|
["/usr/bin/node", "-e", source],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
return json.loads(completed.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def _index_html() -> str:
|
||||||
|
return Path("apps/web/vue3/index.html").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def test_joint_owner_preflight_continues_after_one_owner_pauses() -> None:
|
||||||
|
helper = _source_block(
|
||||||
|
_index_html(),
|
||||||
|
"const preflightPpiKaksDependencyOwners = async",
|
||||||
|
"const ensureOrthologProducerForPpiKaks = async",
|
||||||
|
)
|
||||||
|
result = _run_node(
|
||||||
|
helper
|
||||||
|
+ """
|
||||||
|
(async () => {
|
||||||
|
const calls = [];
|
||||||
|
const result = await preflightPpiKaksDependencyOwners(
|
||||||
|
['kaks', 'ppi'],
|
||||||
|
async owner => {
|
||||||
|
calls.push(owner);
|
||||||
|
return owner === 'ppi';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log(JSON.stringify({calls, result}));
|
||||||
|
})();
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == {
|
||||||
|
"calls": ["kaks", "ppi"],
|
||||||
|
"result": [
|
||||||
|
{"ownerStageId": "kaks", "status": "paused"},
|
||||||
|
{"ownerStageId": "ppi", "status": "ready"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_ppi_kaks_pause_only_claims_owned_ortholog() -> None:
|
||||||
|
helper = _source_block(
|
||||||
|
_index_html(),
|
||||||
|
"const ppiKaksOwnedOrthologOwners =",
|
||||||
|
"const ppiKaksPauseStageIds =",
|
||||||
|
)
|
||||||
|
result = _run_node(
|
||||||
|
helper
|
||||||
|
+ """
|
||||||
|
const explicit = ppiKaksOwnedOrthologOwners(
|
||||||
|
{id: 'ortholog', params: {autoInsertedFor: ['kaks']}},
|
||||||
|
{intentStages: ['ortholog', 'kaks']}
|
||||||
|
);
|
||||||
|
const plannerInserted = ppiKaksOwnedOrthologOwners(
|
||||||
|
{id: 'ortholog', params: {}},
|
||||||
|
{intentStages: ['kaks']}
|
||||||
|
);
|
||||||
|
const ppiOwned = ppiKaksOwnedOrthologOwners(
|
||||||
|
{id: 'ortholog', params: {dependencyOwner: 'ppi'}},
|
||||||
|
{intentStages: ['ppi']}
|
||||||
|
);
|
||||||
|
const unknown = ppiKaksOwnedOrthologOwners(
|
||||||
|
{id: 'ortholog', params: {}},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
console.log(JSON.stringify({explicit, plannerInserted, ppiOwned, unknown}));
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == {
|
||||||
|
"explicit": [],
|
||||||
|
"plannerInserted": ["kaks"],
|
||||||
|
"ppiOwned": ["ppi"],
|
||||||
|
"unknown": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_joint_owner_pauses_are_stored_without_overwriting_each_other() -> None:
|
||||||
|
helper = _source_block(
|
||||||
|
_index_html(),
|
||||||
|
"const ppiKaksOwnedOrthologOwners =",
|
||||||
|
"const mergePpiReferenceSpeciesIntoBatch =",
|
||||||
|
)
|
||||||
|
result = _run_node(
|
||||||
|
"""
|
||||||
|
const batchExecuteParams = {value: {intentStages: ['kaks', 'ppi']}};
|
||||||
|
const batchExecuteStages = {value: [
|
||||||
|
{id: 'ortholog', params: {autoInsertedFor: ['kaks', 'ppi']}},
|
||||||
|
{id: 'kaks'}, {id: 'ppi'}
|
||||||
|
]};
|
||||||
|
const stageExecutionStatus = {value: {ortholog: 'pending', kaks: 'pending', ppi: 'pending'}};
|
||||||
|
const stagePauseReasons = {};
|
||||||
|
const getStageId = stage => typeof stage === 'object' ? stage.id : stage;
|
||||||
|
const recordStagePause = (stageId, pause) => { stagePauseReasons[stageId] = pause.reason; };
|
||||||
|
"""
|
||||||
|
+ helper
|
||||||
|
+ """
|
||||||
|
storePpiKaksReferencePause(['kaks'], {issue_code: 'missing_cds'}, 'KaKs paused');
|
||||||
|
applyStoredPpiKaksPauseStatuses();
|
||||||
|
const afterKaks = {...stageExecutionStatus.value};
|
||||||
|
storePpiKaksReferencePause(['ppi'], {issue_code: 'missing_owner'}, 'PPI paused');
|
||||||
|
applyStoredPpiKaksPauseStatuses();
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
pauseOwners: Object.keys(batchExecuteParams.value.ppiKaksDependencyPauses).sort(),
|
||||||
|
afterKaks,
|
||||||
|
afterBoth: stageExecutionStatus.value,
|
||||||
|
reasons: stagePauseReasons
|
||||||
|
}));
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["pauseOwners"] == ["kaks", "ppi"]
|
||||||
|
assert result["afterKaks"] == {"ortholog": "pending", "kaks": "paused", "ppi": "pending"}
|
||||||
|
assert result["afterBoth"] == {"ortholog": "paused", "kaks": "paused", "ppi": "paused"}
|
||||||
|
assert result["reasons"] == {
|
||||||
|
"kaks": "missing_cds",
|
||||||
|
"ppi": "missing_owner",
|
||||||
|
"ortholog": "missing_cds",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_batch_start_uses_temporary_circuit_breaker_without_owner_preflight() -> None:
|
||||||
|
start_block = _source_block(
|
||||||
|
_index_html(),
|
||||||
|
"const startBatchExecute = async",
|
||||||
|
"const executeStagesWithDependencies = async",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "applyPpiKaksTemporaryCircuitBreaker();" in start_block
|
||||||
|
assert "if (ppiKaksTemporaryCircuitBreakerEnabled)" in start_block
|
||||||
|
assert "else {\n await ensureOrthologProducerForPpiKaks();\n }" in start_block
|
||||||
|
assert "await executeStagesWithDependencies(stages, plannedStageIds);" in start_block
|
||||||
|
|
||||||
|
|
||||||
|
def test_temporary_circuit_breaker_pauses_only_owned_ortholog() -> None:
|
||||||
|
source = _index_html()
|
||||||
|
owner_helper = _source_block(
|
||||||
|
source,
|
||||||
|
"const ppiKaksOwnedOrthologOwners =",
|
||||||
|
"const ppiKaksPauseStageIds =",
|
||||||
|
)
|
||||||
|
policy_helper = _source_block(
|
||||||
|
source,
|
||||||
|
"const ppiKaksTemporaryCircuitBreakerEnabled =",
|
||||||
|
"const batchIncludesPpiKaks =",
|
||||||
|
)
|
||||||
|
result = _run_node(
|
||||||
|
"const ppiKaksDependencyStages = new Set(['ppi', 'kaks']);\n"
|
||||||
|
"const getStageId = stage => typeof stage === 'object' ? stage.id : stage;\n"
|
||||||
|
+ owner_helper
|
||||||
|
+ policy_helper
|
||||||
|
+ """
|
||||||
|
const owned = ppiKaksTemporaryPauseStageIds(
|
||||||
|
[{id: 'ortholog', params: {autoInsertedFor: ['ppi', 'kaks']}}, {id: 'ppi'}, {id: 'kaks'}],
|
||||||
|
{intentStages: ['ppi', 'kaks']}
|
||||||
|
);
|
||||||
|
const explicit = ppiKaksTemporaryPauseStageIds(
|
||||||
|
[{id: 'ortholog', params: {}}, {id: 'ppi'}],
|
||||||
|
{intentStages: ['ortholog', 'ppi']}
|
||||||
|
);
|
||||||
|
const unrelated = ppiKaksTemporaryPauseStageIds(
|
||||||
|
[{id: 'ortholog', params: {}}, {id: 'motif'}],
|
||||||
|
{intentStages: ['ortholog', 'motif']}
|
||||||
|
);
|
||||||
|
const onePlannedOwner = ppiKaksTemporaryPauseStageIds(
|
||||||
|
[{id: 'ortholog', params: {autoInsertedFor: ['ppi', 'kaks']}}, {id: 'ppi'}],
|
||||||
|
{intentStages: ['ppi']}
|
||||||
|
);
|
||||||
|
console.log(JSON.stringify({owned, explicit, unrelated, onePlannedOwner}));
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == {
|
||||||
|
"owned": ["ortholog", "ppi", "kaks"],
|
||||||
|
"explicit": ["ppi"],
|
||||||
|
"unrelated": [],
|
||||||
|
"onePlannedOwner": ["ortholog", "ppi"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_full_gene_family_plan_keeps_explicit_ortholog_and_other_stages_runnable() -> None:
|
||||||
|
source = _index_html()
|
||||||
|
owner_helper = _source_block(
|
||||||
|
source,
|
||||||
|
"const ppiKaksOwnedOrthologOwners =",
|
||||||
|
"const ppiKaksPauseStageIds =",
|
||||||
|
)
|
||||||
|
policy_helper = _source_block(
|
||||||
|
source,
|
||||||
|
"const ppiKaksTemporaryCircuitBreakerEnabled =",
|
||||||
|
"const batchIncludesPpiKaks =",
|
||||||
|
)
|
||||||
|
result = _run_node(
|
||||||
|
"const ppiKaksDependencyStages = new Set(['ppi', 'kaks']);\n"
|
||||||
|
"const getStageId = stage => typeof stage === 'object' ? stage.id : stage;\n"
|
||||||
|
+ owner_helper
|
||||||
|
+ policy_helper
|
||||||
|
+ """
|
||||||
|
const stageIds = [
|
||||||
|
'phylogeny', 'motif', 'domain', 'structure', 'combo', 'promoter', 'tfbs',
|
||||||
|
'synteny', 'interspecies_synteny', 'karyotype', 'pep', 'ortholog', 'kaks', 'ppi'
|
||||||
|
];
|
||||||
|
const stages = stageIds.map(id => ({id, params: {}}));
|
||||||
|
const paused = ppiKaksTemporaryPauseStageIds(stages, {intentStages: stageIds});
|
||||||
|
const runnable = stageIds.filter(stageId => !paused.includes(stageId));
|
||||||
|
console.log(JSON.stringify({paused, runnable}));
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["paused"] == ["kaks", "ppi"]
|
||||||
|
assert result["runnable"] == [
|
||||||
|
"phylogeny",
|
||||||
|
"motif",
|
||||||
|
"domain",
|
||||||
|
"structure",
|
||||||
|
"combo",
|
||||||
|
"promoter",
|
||||||
|
"tfbs",
|
||||||
|
"synteny",
|
||||||
|
"interspecies_synteny",
|
||||||
|
"karyotype",
|
||||||
|
"pep",
|
||||||
|
"ortholog",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_dependency_scheduler_executes_later_stages_while_ppi_kaks_are_paused() -> None:
|
||||||
|
source = _index_html()
|
||||||
|
owner_helper = _source_block(
|
||||||
|
source,
|
||||||
|
"const ppiKaksOwnedOrthologOwners =",
|
||||||
|
"const ppiKaksPauseStageIds =",
|
||||||
|
)
|
||||||
|
policy_helper = _source_block(
|
||||||
|
source,
|
||||||
|
"const ppiKaksTemporaryCircuitBreakerEnabled =",
|
||||||
|
"const batchIncludesPpiKaks =",
|
||||||
|
)
|
||||||
|
scheduler = _source_block(
|
||||||
|
source,
|
||||||
|
"const executeStagesWithDependencies = async",
|
||||||
|
"const stopBatchAfterSkippedSubfamilyConfirm =",
|
||||||
|
)
|
||||||
|
result = _run_node(
|
||||||
|
"""
|
||||||
|
const console = {log: () => {}, error: () => {}};
|
||||||
|
const ppiKaksDependencyStages = new Set(['ppi', 'kaks']);
|
||||||
|
const getStageId = stage => typeof stage === 'object' ? stage.id : stage;
|
||||||
|
const stageDependencies = {phylogeny: [], motif: ['phylogeny'], ppi: [], kaks: []};
|
||||||
|
const shouldStopBatchExecute = {value: false};
|
||||||
|
const stageExecutionStatus = {value: {}};
|
||||||
|
const runningStages = {value: new Set()};
|
||||||
|
const maxParallelStages = 4;
|
||||||
|
const limitGwasModelHeavyWave = candidates => candidates;
|
||||||
|
const isTerminalBatchStatus = status => ['completed', 'failed', 'paused'].includes(status);
|
||||||
|
const getStageAction = stage => getStageId(stage);
|
||||||
|
const getStageLabel = stageId => stageId;
|
||||||
|
const recordStagePause = () => {};
|
||||||
|
const addAgentMessage = () => {};
|
||||||
|
const calls = [];
|
||||||
|
const executeStageWithConfirmation = async stage => {
|
||||||
|
const stageId = getStageId(stage);
|
||||||
|
calls.push(stageId);
|
||||||
|
stageExecutionStatus.value[stageId] = 'completed';
|
||||||
|
};
|
||||||
|
"""
|
||||||
|
+ owner_helper
|
||||||
|
+ policy_helper
|
||||||
|
+ scheduler
|
||||||
|
+ """
|
||||||
|
const stages = [
|
||||||
|
{id: 'ppi', dependencies: []},
|
||||||
|
{id: 'phylogeny', dependencies: []},
|
||||||
|
{id: 'motif', dependencies: ['phylogeny']},
|
||||||
|
{id: 'kaks', dependencies: []}
|
||||||
|
];
|
||||||
|
const paused = ppiKaksTemporaryPauseStageIds(stages, {intentStages: stages.map(getStageId)});
|
||||||
|
stages.forEach(stage => {
|
||||||
|
stageExecutionStatus.value[stage.id] = paused.includes(stage.id) ? 'paused' : 'pending';
|
||||||
|
});
|
||||||
|
executeStagesWithDependencies(stages, new Set(stages.map(getStageId))).then(() => {
|
||||||
|
process.stdout.write(JSON.stringify({calls, statuses: stageExecutionStatus.value}));
|
||||||
|
});
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == {
|
||||||
|
"calls": ["phylogeny", "motif"],
|
||||||
|
"statuses": {
|
||||||
|
"ppi": "paused",
|
||||||
|
"phylogeny": "completed",
|
||||||
|
"motif": "completed",
|
||||||
|
"kaks": "paused",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_submit_guard_precedes_species_preflight_and_execute_fetch() -> None:
|
||||||
|
submit_block = _source_block(
|
||||||
|
_index_html(),
|
||||||
|
"const submitExecuteRequestWithAutoAnnotation = async",
|
||||||
|
"const waitForDialogTransition = async",
|
||||||
|
)
|
||||||
|
|
||||||
|
guard = submit_block.index("isPpiKaksTemporarySubmissionBlocked")
|
||||||
|
species_preflight = submit_block.index("runPpiSpeciesResolutionPreflight")
|
||||||
|
execute_fetch = submit_block.index("fetch('/api/execute'")
|
||||||
|
assert guard < species_preflight
|
||||||
|
assert guard < execute_fetch
|
||||||
|
assert "status: 'temporarily_unavailable'" in submit_block
|
||||||
|
|
||||||
|
|
||||||
|
def test_submit_guard_returns_paused_without_any_fetch() -> None:
|
||||||
|
submit_block = _source_block(
|
||||||
|
_index_html(),
|
||||||
|
"const submitExecuteRequestWithAutoAnnotation = async",
|
||||||
|
"const waitForDialogTransition = async",
|
||||||
|
)
|
||||||
|
result = _run_node(
|
||||||
|
"""
|
||||||
|
let fetchCalls = 0;
|
||||||
|
const fetch = async () => { fetchCalls += 1; throw new Error('fetch must not run'); };
|
||||||
|
const isPpiKaksTemporarySubmissionBlocked = () => true;
|
||||||
|
const getStageLabel = stageId => stageId === 'ppi' ? 'PPI' : stageId;
|
||||||
|
"""
|
||||||
|
+ submit_block
|
||||||
|
+ """
|
||||||
|
submitExecuteRequestWithAutoAnnotation({stage: 'ppi'}, 'ppi')
|
||||||
|
.then(result => process.stdout.write(JSON.stringify({result, fetchCalls})));
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["fetchCalls"] == 0
|
||||||
|
assert result["result"] == {
|
||||||
|
"success": False,
|
||||||
|
"paused": True,
|
||||||
|
"status": "temporarily_unavailable",
|
||||||
|
"message": "PPI 功能修订中,当前暂不可用。",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_submit_guard_blocks_owned_ortholog_but_not_independent_manual_ortholog() -> None:
|
||||||
|
source = _index_html()
|
||||||
|
owner_helper = _source_block(
|
||||||
|
source,
|
||||||
|
"const ppiKaksOwnedOrthologOwners =",
|
||||||
|
"const ppiKaksPauseStageIds =",
|
||||||
|
)
|
||||||
|
policy_helper = _source_block(
|
||||||
|
source,
|
||||||
|
"const ppiKaksTemporaryCircuitBreakerEnabled =",
|
||||||
|
"const batchIncludesPpiKaks =",
|
||||||
|
)
|
||||||
|
result = _run_node(
|
||||||
|
"const ppiKaksDependencyStages = new Set(['ppi', 'kaks']);\n"
|
||||||
|
"const getStageId = stage => typeof stage === 'object' ? stage.id : stage;\n"
|
||||||
|
"const firstNonEmptyList = (...values) => values.find(value => Array.isArray(value) && value.length) || [];\n"
|
||||||
|
"const batchExecuteParams = {value: {intentStages: ['kaks']}};\n"
|
||||||
|
+ owner_helper
|
||||||
|
+ policy_helper
|
||||||
|
+ """
|
||||||
|
const cases = {
|
||||||
|
ppi: isPpiKaksTemporarySubmissionBlocked('ppi', {}),
|
||||||
|
ownedOrtholog: isPpiKaksTemporarySubmissionBlocked(
|
||||||
|
'ortholog', {autoInsertedFor: ['kaks']}
|
||||||
|
),
|
||||||
|
independentManualOrtholog: isPpiKaksTemporarySubmissionBlocked('ortholog', {}),
|
||||||
|
};
|
||||||
|
batchExecuteParams.value.intentStages = ['ortholog', 'kaks'];
|
||||||
|
cases.explicitBatchOrtholog = isPpiKaksTemporarySubmissionBlocked(
|
||||||
|
'ortholog', {autoInsertedFor: ['kaks']}
|
||||||
|
);
|
||||||
|
console.log(JSON.stringify(cases));
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == {
|
||||||
|
"ppi": True,
|
||||||
|
"ownedOrtholog": True,
|
||||||
|
"independentManualOrtholog": False,
|
||||||
|
"explicitBatchOrtholog": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_manual_execute_returns_before_running_or_submitting_ppi_kaks() -> None:
|
||||||
|
manual_block = _source_block(
|
||||||
|
_index_html(),
|
||||||
|
"const doExecute = async",
|
||||||
|
"const queueAutofillFromPayload = async",
|
||||||
|
)
|
||||||
|
|
||||||
|
guard = manual_block.index("isPpiKaksTemporarilyUnavailable(confirmParams.stage)")
|
||||||
|
running = manual_block.index("executing.value = true")
|
||||||
|
submit = manual_block.index("submitExecuteRequestWithAutoAnnotation")
|
||||||
|
assert guard < running
|
||||||
|
assert guard < submit
|
||||||
|
assert "功能修订中,当前暂不可用" in manual_block
|
||||||
|
|
||||||
|
|
||||||
|
def test_manual_execute_shows_non_blocking_message_without_starting() -> None:
|
||||||
|
manual_block = _source_block(
|
||||||
|
_index_html(),
|
||||||
|
"const doExecute = async",
|
||||||
|
"const queueAutofillFromPayload = async",
|
||||||
|
)
|
||||||
|
result = _run_node(
|
||||||
|
"""
|
||||||
|
const console = {log: () => {}, error: () => {}};
|
||||||
|
const confirmParams = {stage: 'ppi'};
|
||||||
|
const confirmDialogVisible = {value: true};
|
||||||
|
const executing = {value: false};
|
||||||
|
const isExecuting = {value: false};
|
||||||
|
let timerClears = 0;
|
||||||
|
const warnings = [];
|
||||||
|
const isPpiKaksTemporarilyUnavailable = stageId => stageId === 'ppi' || stageId === 'kaks';
|
||||||
|
const clearAutoExecuteTimer = () => { timerClears += 1; };
|
||||||
|
const getStageLabel = stageId => stageId === 'ppi' ? 'PPI' : stageId;
|
||||||
|
const ElMessage = {warning: message => warnings.push(message)};
|
||||||
|
"""
|
||||||
|
+ manual_block
|
||||||
|
+ """
|
||||||
|
doExecute().then(() => process.stdout.write(JSON.stringify({
|
||||||
|
timerClears,
|
||||||
|
warnings,
|
||||||
|
dialogVisible: confirmDialogVisible.value,
|
||||||
|
executing: executing.value,
|
||||||
|
isExecuting: isExecuting.value
|
||||||
|
})));
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == {
|
||||||
|
"timerClears": 1,
|
||||||
|
"warnings": ["PPI 功能修订中,当前暂不可用"],
|
||||||
|
"dialogVisible": False,
|
||||||
|
"executing": False,
|
||||||
|
"isExecuting": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_background_batch_applies_temporary_pauses_before_execution_loop() -> None:
|
||||||
|
background = _source_block(
|
||||||
|
_index_html(),
|
||||||
|
"const startBackgroundBatchExecution = async",
|
||||||
|
"const extractDataPathsFromText =",
|
||||||
|
)
|
||||||
|
|
||||||
|
pause_policy = background.index("ppiKaksTemporaryPauseStageIds")
|
||||||
|
execution_loop = background.index("while (true)")
|
||||||
|
direct_execute = background.index("executeStageDirect")
|
||||||
|
assert pause_policy < execution_loop < direct_execute
|
||||||
|
assert "localStatus[stageId] = 'paused'" in background
|
||||||
|
|
||||||
|
|
||||||
|
def test_background_batch_classifies_only_owner_preflight_blocks_as_paused() -> None:
|
||||||
|
helper = _source_block(
|
||||||
|
_index_html(),
|
||||||
|
"const isPpiKaksBackgroundPause =",
|
||||||
|
"const startBackgroundBatchExecution = async",
|
||||||
|
)
|
||||||
|
result = _run_node(
|
||||||
|
helper
|
||||||
|
+ """
|
||||||
|
const cases = {
|
||||||
|
kaks: isPpiKaksBackgroundPause('kaks', {}, {status: 'needs_reference_data'}, ['kaks']),
|
||||||
|
ppi: isPpiKaksBackgroundPause('ppi', {}, {ppiSpeciesResolution: {status: 'confirmation_required'}}, ['ppi']),
|
||||||
|
ownedOrtholog: isPpiKaksBackgroundPause(
|
||||||
|
'ortholog', {autoInsertedFor: ['ppi']}, {status: 'needs_ortholog'}, ['ppi']
|
||||||
|
),
|
||||||
|
explicitOrtholog: isPpiKaksBackgroundPause(
|
||||||
|
'ortholog', {}, {status: 'needs_ortholog'}, ['ortholog', 'ppi']
|
||||||
|
),
|
||||||
|
ordinary: isPpiKaksBackgroundPause('motif', {}, {status: 'needs_reference_data'}, ['motif'])
|
||||||
|
,temporaryPpi: isPpiKaksBackgroundPause('ppi', {}, {status: 'temporarily_unavailable'}, ['ppi'])
|
||||||
|
};
|
||||||
|
console.log(JSON.stringify(cases));
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == {
|
||||||
|
"kaks": True,
|
||||||
|
"ppi": True,
|
||||||
|
"ownedOrtholog": True,
|
||||||
|
"explicitOrtholog": False,
|
||||||
|
"ordinary": False,
|
||||||
|
"temporaryPpi": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_background_batch_reports_paused_separately_from_failed() -> None:
|
||||||
|
background = _source_block(
|
||||||
|
_index_html(),
|
||||||
|
"const startBackgroundBatchExecution = async",
|
||||||
|
"const extractDataPathsFromText =",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "localStatus[stageId] = 'paused'" in background
|
||||||
|
assert "let paused = 0;" in background
|
||||||
|
assert "暂停 ${paused}/${stageIds.length}" in background
|
||||||
Reference in New Issue
Block a user