## 摩擦全景

    flowchart TB
      subgraph BE["后端"]
        IJ["intelligence_jobs.py
    两套手写结算序列"] --> AR["AsyncJobRepository
    claim/recovery 已深"]
        R1["routers/intelligent_evals.py
    直 import ORM 表"] -.泄漏.-> VIS["逻辑删除可见性
    三处独立实现"]
        DL["decision_logs._require_eval"] -.泄漏.-> VIS
        REPO["storage/repository.py
    1180 行 · 12 类 · 5 域"]
        APP["web/app.py
    手工编排 5 个 registry 关停顺序"]
      end
      subgraph FE["前端"]
        READ["src/read 深模块
    仅 1 个消费者"] -.绕过.-> TABS["drawer 标签页群
    各自手写 fetch/polling"]
        API["api.ts 909 行 · 12 域"]
      end
      classDef warn fill:#fef3c7,stroke:#f59e0b,stroke-width:2px;
      class IJ,VIS,REPO,APP,TABS,API warn;

## ① 耐久智能作业：结算序列收进单一执行器

intelligence_jobs.py · AsyncJobRepository · campaigns 路由 · app.py

Strong

### Files

- `evaluation/intelligence_jobs.py` — 两个 executor（53–150、153–263）
- `storage/repository.py` — `AsyncJobRepository`（782–863）
- `evaluation/campaign_runner.py:242-247`、`routers/campaigns.py:128-134`
- `evaluation/comparison.py:245-280`（死代码 + 投影越界）

### Problem

v2 计划已建出 `AsyncJobRepository`（claim / recovery / orphan 结算都深），但编排序列仍按 adapter 手抄：分析与周期对比两个 executor 各自复制「缺则入队 → claim_queued → upsert(generating) → try/except → failed/completed」。错误截断 `str(exc)[:500]` 出现两次（123、250）；正式线跳过守卫两处（campaign_runner.py:242 与 intelligence_jobs.py:136）；分析触发校验在路由与 executor 各一份（周期对比已有共享入口，分析没有）；`build_comparison_payload` 是零调用死代码且与 `_build_auto_baseline_payload` 逐字节相同。

### Solution

在执行器层提供单一深接缝 `execute(job_kind, work_fn)`：入队幂等、认领、generating、失败归一（截断）、成功结算、重复触发幂等观察——全部收进 implementation。分析与周期对比退化为纯领域工作 adapter；顺带删除死代码、把 `load_comparison_view` 投影移回读模型、分析校验对齐周期对比的共享入口。

### Benefits

Locality：结算顺序知识（先认领再 generating、失败先归一再落库）只住一处。Leverage：第三个作业类型只需一个 work adapter。测试面从"两套序列各测一遍"收敛为执行器接口一组契约测试 + 每 adapter 的纯领域测试。两个 adapter 已存在——这是真接缝，不是假想接缝。

### Before / After

Before · 序列手抄 ×2

长接口：调用方须知认领顺序 / 截断 / 幂等
分析 executor：~100 行编排
对比 executor：~110 行同形编排

After · 单一执行器

小接口：execute(kind, work_fn)
深 implementation
认领·结算·归一·幂等
分析 adapter
对比 adapter

    sequenceDiagram
      participant C as 触发方(路由/自动)
      participant E as execute() 深模块
      participant W as work adapter
      C->>E: execute(kind, work_fn)
      E->>E: 幂等入队 + claim_queued
      E->>E: upsert(generating)
      E->>W: 领域工作（无 Session 编排）
      alt 成功
        E->>E: upsert(completed + 结果)
      else 异常
        E->>E: 归一 + 截断 + upsert(failed)
      end

## ② 智能评估可见性接缝：逻辑删除语义三处收敛为一

intelligent_eval/repository.py · decision_logs.py · routers/intelligent_evals.py · task_queue.py

Strong

### Files

- `intelligent_eval/repository.py` — 可见性谓词重复 6 处（103、117、130、137、147、171）
- `decision_logs.py:30-33` — `_require_eval` 绕过 repo 直查 ORM
- `routers/intelligent_evals.py:73-80` — 第三份实现，且路由直接 import `IntelligentEvalDB`
- `task_queue.list_tasks:330` — 删除不感知，监控面板泄漏已删评估

### Problem

逻辑删除（2d2c5a2，一周前）落地后，「已删即 404」由三个独立实现分别裁决：repo 的 `get()` 隐藏 + `get_including_deleted()`、`decision_logs._require_eval` 直查 ORM、路由 `_require_eval_exists` 再直查 ORM——路由跨过 repository 接缝直接握表，等于把删除语义焊死在 HTTP 层。附带浅点：配置快照 11 字段序列化 dict 在路由里逐字写了两遍（399–416、430–442）；expired 会话的 Markdown 标注住在路由（242–252）；`count_decisions` 全量载入后 `len()`（112–121）。

### Solution

可见性收进 repository 单一谓词（内部 `_visible()`），对外只留 `require_live_eval(session, id)` 服务接缝——三处调用方改走它，路由删掉 ORM import。快照序列化与 expired 标注下沉到读模型投影；`count_decisions` 改 `func.count`。

### Benefits

Locality：删除语义改动（如未来回收站、级联策略）只改一处。正确性：消除三处分歧风险——当前任务监控已实际泄漏已删评估。测试面：逻辑删除契约从分散的集成断言收敛为 repo 接口一组测试。这是新功能知识正在扩散的窗口期，越早收敛成本越低。

### Before / After

Before · 三处裁决"已删即 404"

repo.get() 隐藏删除
decision_logs 直查 ORM
路由直查 ORM（import 表）
task_queue 完全不知情

After · 单一可见性接缝

require_live_eval(id)
repository：_visible 谓词 + get/get_including_deleted
decision_logs
路由
task_queue

    flowchart LR
      subgraph NOW["现状：三份语义"]
        A["router 直查
    IntelligentEvalDB"] --> D["DB"]
        B["decision_logs
    session.get()"] --> D
        C["repo.get()
    隐藏 deleted"] --> D
      end
      classDef bad fill:#fee2e2,stroke:#ef4444;
      class A,B bad;

## ③ 存储层 monolith：repository.py / db.py 按域拆分

storage/repository.py（1180 行）· storage/db.py（709 行）

Worth exploring

### Files

- `storage/repository.py` — 1180 行、12 个 repository 类、跨约 5 个域
- `storage/db.py` — 709 行、20 张表一个模块
- 对比项：`intelligent_eval/repository.py`（510 行、单域）是良好范本

### Problem

文件级 locality 欠佳：目录（Target/Scenario）、执行（Run/Result）、评估活动（Campaign + 两个智能作业）、探索式评测（ExplorationSession/Message）同居一文件；探索对（1057–1163）与异步作业仓库（782–996）完全自包含，却被迫与无关域共享导航成本。AI 与新成员定位"活动的 CAS 写入"必须扫过全部 12 个类。CAS 条件写模式也在此三度独立实现（680–739、802–828 与 intelligent_eval 的 190–248），同文件却不共享原语。

### Solution

纯移动不改行为：拆出 `storage/exploration_repository.py`、`storage/async_job_repository.py`（或随候选①并入执行器模块）；db.py 可暂不动（表定义集中有 create_all 的便利）。可选第二步：把三份 CAS 收敛为一个共享条件写原语——但需先过删除测试：三处的条件字段与冲突语义确有差异，只有当共享原语接口小到不泄漏这些差异时才值得。

### Benefits

每域一个文件 = 改动、bug、知识同址；导航成本从"扫 1180 行"降到"开对应文件"。本身不产生 leverage，故标记为 Worth exploring——它是其他候选（①②）的天然搭车项，单独做价值有限。

### Before / After

Before · 一文件 12 类 5 域

storage/repository.py — 1180 行

Target
Scenario
Run
Result
Campaign
AnalysisJob
ComparisonJob
ExplSession
ExplMessage

After · 每域一文件

catalog_repository
run_repository
campaign_repository
exploration_repository

## ④ 前端读取模块：src/read 从单消费者扩展为统一资源接缝

src/read/* · components/intelligent_eval/* · hooks/useCampaignReport.ts · hooks/useResource.ts

Strong

### Files

- `read/intelligentEval.ts`（118 行，有测试）· `read/useIntelligentEvalRead.ts`（83 行，有测试）
- `components/intelligent_eval/` — ExecutionProcess（480 行）、DecisionProcess、ConfigSnapshots、TaskQueueMonitor
- `hooks/useCampaignReport.ts:16-21`、`hooks/useResource.ts`

### Problem

`src/read` 是一个真正的深模块（ReadSlot 相位机、竞态守卫、静默刷新、可注入 adapter，且有测试）——但只有 IntelligentEvals.tsx 一个消费者，连它自己都绕过读模块做 mutation。五个 drawer 标签页各自手写同形 fetch 样板（DecisionProcess:25-35、ConfigSnapshots:32-42、TaskQueueMonitor:39-50）；`ACTIVE_STATUSES` 定义两份；`ReadPhase/ReadSlot` 在 useCampaignReport.ts 里平行重造；Blob 导出仪式重复三遍；同一份决策日志有三种新鲜度策略（父级 5s 轮询详情 + ExecutionProcess 独立 5s 轮询 + DecisionProcess 只挂载取一次）。组件群零测试（EvalReport / EvalOverview / DecisionProcess / ConfigSnapshots / TaskQueueMonitor）。

### Solution

把 ReadSlot 泛化为通用资源接缝（或强制 drawer 标签页走既有 `useResource` + 轮询门控），决策日志的新鲜度策略由父级读模块统一供给一次；常量与导出仪式各自归位单一出口。组件退化为纯渲染，状态经 props/reducer 注入。

### Benefits

Leverage：竞态守卫、静默刷新、轮询暂停这些已写好且已测的行为，一次学习全组件受益。可测性：接口即测试面——五个零覆盖组件的测试从"mock 整个 api + 时序"变成对纯 reducer/props 的断言，这正是 sessionReducer 已验证的形状。深模块已存在、adapter 需求已出现（campaign 报告平行重造）——符合"两个 adapter 才是真接缝"。

### Before / After

Before · 深模块闲置，样板四散

src/read
深·有测试
1 个消费者
ExecutionProcess
手写 5 个 fetch 槽
DecisionProcess
手写样板
ConfigSnapshots
手写样板
TaskQueueMonitor
手写样板

After · 资源接缝统一供给

useReadResource(slot, adapter) — 竞态/轮询/静默刷新内置
列表页
5 个 drawer 标签
活动报告

## ⑤ api.ts 单文件按域拆分

frontend/web/src/api.ts（909 行 · 12 域）

Speculative

### Files

`src/api.ts` — auth、targets、scenarios、modelConfigs、runs、reports、stats、exploration、campaigns、intelligentEvals（16 个端点）、files，全部类型内联。

### Problem

v1.2 已点名的残留浅点。单文件本身不深不浅——axios 拦截器统一错误是真实深度——但 12 域同居迫使每次改动载入全部类型；报告 Markdown 下载的 DOM 副作用（835–843）住在"接口定义单一出口"里属越界。注意：它是locality 问题而非 depth 问题，单独拆不产生 leverage。

### Solution

保留 `api/index.ts` 作为拦截器与 axios 实例的唯一住处（现有唯一出口语义不丢），各域拆为 `api/intelligentEvals.ts` 等；Blob 导出仪式与候选④合并到同一个下载工具。

### Benefits

导航与 diff 噪声下降；域类型就近。无行为变化、无测试变化——因此只做搭车项，不单独立项。

### Before / After

api.ts — 909 行

auth targets scenarios modelConfigs runs reports stats exploration campaigns intelligentEvals +DOM 副作用 files
↓
api/index.ts
实例 + 拦截器
api/<domain>.ts ×11

## Top recommendation — 先做 ②

候选②（智能评估可见性接缝）应当第一个做。 理由有三：其一，逻辑删除是一周前刚落地的新功能，其领域知识正在扩散——三处独立实现 + 路由直 import ORM 表 + 任务监控已实际泄漏已删评估，每晚一天收敛成本更高；其二，它是五个候选里爆炸半径最小的——纯收敛、零行为变化、已有 +100 行集成测试做行为锁；其三，它顺带把快照序列化与 expired 标注移出路由，巩固 v1.2 已确立的"路由只做 HTTP 翻译"纪律。

紧随其后做 候选①（结算序列——真有两个 adapter，接缝是真的），③⑤ 作为搭车项，④ 作为前端独立战役（涉及组件改造与补测试，体量最大）。

词汇：module / interface / implementation / depth / seam / adapter / leverage / locality（codebase-design）；领域名词取自 CONTEXT.md。