Some checks failed
CI / test (push) Failing after 12s
将 settlement.py 的 settle_campaign_sessions 函数吸收为 ExplorationSessionRepository.expire_running_sessions 方法。删除浅模块 settlement.py(30 行,接口宽如实现),会话生命周期操作集中在 repository。 - 新增 ExplorationSessionRepository.expire_running_sessions(campaign_id) - 更新 campaigns.py 和 campaign_runner.py 两个调用点 - 删除 backend/agenteval/exploration/settlement.py - 所有测试通过,行为不变
416 lines
27 KiB
HTML
416 lines
27 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>架构深化审查 — AgentEvalTool</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<script type="module">
|
||
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
||
mermaid.initialize({ startOnLoad: true, theme: "neutral", securityLevel: "loose" });
|
||
</script>
|
||
<style>
|
||
.seam { stroke-dasharray: 4 4; }
|
||
.leak { stroke: #dc2626; }
|
||
.deep { background: linear-gradient(135deg, #0f172a, #1e293b); }
|
||
body { font-feature-settings: "liga" 1; }
|
||
</style>
|
||
</head>
|
||
<body class="bg-stone-50 text-slate-900 font-sans">
|
||
<main class="max-w-5xl mx-auto px-6 py-12 space-y-12">
|
||
|
||
<!-- Header -->
|
||
<header class="border-b border-stone-200 pb-6">
|
||
<div class="flex items-baseline justify-between">
|
||
<h1 class="text-3xl font-serif font-bold tracking-tight">AgentEvalTool 架构深化审查</h1>
|
||
<span class="text-sm text-stone-500">2026-08-04 · 第三轮</span>
|
||
</div>
|
||
<div class="flex gap-6 mt-4 text-xs text-stone-500">
|
||
<span class="flex items-center gap-1.5"><span class="inline-block w-3 h-3 rounded border-2 border-slate-400"></span> module</span>
|
||
<span class="flex items-center gap-1.5"><span class="inline-block w-6 border-t-2 border-dashed border-slate-400"></span> seam</span>
|
||
<span class="flex items-center gap-1.5"><span class="inline-block w-6 border-t-2 border-red-500"></span> leakage</span>
|
||
<span class="flex items-center gap-1.5"><span class="inline-block w-3 h-3 rounded deep"></span> deep module</span>
|
||
</div>
|
||
<p class="mt-4 text-sm text-stone-600 max-w-2xl">
|
||
扫描范围:最近 30 次提交的热点区域(探索式评测、周期对比、活动报告、前端活动页)。
|
||
识别 6 个深化机会,按推荐强度排序。
|
||
</p>
|
||
</header>
|
||
|
||
<!-- Candidates -->
|
||
<section id="candidates" class="space-y-10">
|
||
|
||
<!-- Candidate A -->
|
||
<article class="bg-white rounded-xl border border-stone-200 shadow-sm overflow-hidden">
|
||
<div class="p-6">
|
||
<div class="flex items-start justify-between mb-4">
|
||
<h2 class="text-xl font-bold">A. 用例判定证据构建收敛到 case_verdict</h2>
|
||
<span class="inline-block px-2.5 py-0.5 rounded-full text-xs font-bold bg-emerald-100 text-emerald-800">强推荐</span>
|
||
</div>
|
||
<div class="flex flex-wrap gap-1.5 mb-4">
|
||
<code class="text-xs bg-stone-100 px-2 py-0.5 rounded">evaluation/report.py</code>
|
||
<code class="text-xs bg-stone-100 px-2 py-0.5 rounded">web/routers/runs.py</code>
|
||
<code class="text-xs bg-stone-100 px-2 py-0.5 rounded">evaluation/case_verdict.py</code>
|
||
</div>
|
||
|
||
<!-- Before/After -->
|
||
<div class="grid md:grid-cols-2 gap-6 mb-6">
|
||
<div>
|
||
<p class="text-xs uppercase tracking-wider text-stone-500 font-bold mb-2">Before — 重复的证据构建</p>
|
||
<div class="rounded-lg border border-stone-200 bg-white p-4">
|
||
<pre class="mermaid text-xs">
|
||
flowchart LR
|
||
subgraph report_py["report.py"]
|
||
R1[generate_report] --> R2[构建 CaseEvidence]
|
||
R2 --> R3[resolve_case_verdicts]
|
||
end
|
||
subgraph runs_py["runs.py"]
|
||
U1[get_run_logs] --> U2[构建 CaseEvidence]
|
||
U2 --> U3[resolve_case_verdicts]
|
||
end
|
||
style R2 fill:#fecaca,stroke:#dc2626
|
||
style U2 fill:#fecaca,stroke:#dc2626
|
||
</pre>
|
||
</div>
|
||
<p class="text-xs text-stone-500 mt-2">两处各 ~25 行,turns → evidence dict → CaseEvidence → verdicts</p>
|
||
</div>
|
||
<div>
|
||
<p class="text-xs uppercase tracking-wider text-stone-500 font-bold mb-2">After — 单一落点</p>
|
||
<div class="rounded-lg border border-stone-200 bg-white p-4">
|
||
<pre class="mermaid text-xs">
|
||
flowchart LR
|
||
subgraph case_verdict["case_verdict.py"]
|
||
CV[build_evidence_and_verdicts]
|
||
CV --> CV2[resolve_case_verdicts]
|
||
end
|
||
subgraph callers["调用方"]
|
||
R[report.py] --> CV
|
||
U[runs.py] --> CV
|
||
end
|
||
style CV fill:#0f172a,stroke:#0f172a,color:#fff
|
||
</pre>
|
||
</div>
|
||
<p class="text-xs text-stone-500 mt-2">一处构建,两处调用;locality 回归</p>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="text-sm text-stone-700 mb-2"><strong>问题:</strong>CaseEvidence 构建 + resolve_case_verdicts 调用链在 report.py 和 runs.py 各写一遍(各 ~25 行),turns/results → evidence dict 的映射逻辑漂移风险高。</p>
|
||
<p class="text-sm text-stone-700 mb-3"><strong>方案:</strong>在 case_verdict.py 新增 <code>build_evidence_and_verdicts(turns, results, summary)</code>,report.py 和 runs.py 各一行调用。</p>
|
||
<ul class="text-sm text-stone-600 space-y-1">
|
||
<li><strong>locality:</strong>证据构建逻辑改一处,全局生效</li>
|
||
<li><strong>depth:</strong>case_verdict 从纯函数变深——接口不变,实现吸收编排</li>
|
||
<li><strong>测试面:</strong>一处可测,不用在两处重复覆盖</li>
|
||
</ul>
|
||
</div>
|
||
</article>
|
||
|
||
<!-- Candidate B -->
|
||
<article class="bg-white rounded-xl border border-stone-200 shadow-sm overflow-hidden">
|
||
<div class="p-6">
|
||
<div class="flex items-start justify-between mb-4">
|
||
<h2 class="text-xl font-bold">B. 活动报告读模型补完:load_campaign_view 统一出口</h2>
|
||
<span class="inline-block px-2.5 py-0.5 rounded-full text-xs font-bold bg-emerald-100 text-emerald-800">强推荐</span>
|
||
</div>
|
||
<div class="flex flex-wrap gap-1.5 mb-4">
|
||
<code class="text-xs bg-stone-100 px-2 py-0.5 rounded">evaluation/report.py</code>
|
||
<code class="text-xs bg-stone-100 px-2 py-0.5 rounded">evaluation/comparison.py</code>
|
||
<code class="text-xs bg-stone-100 px-2 py-0.5 rounded">web/routers/campaigns.py</code>
|
||
<code class="text-xs bg-stone-100 px-2 py-0.5 rounded">exploration/summary.py</code>
|
||
</div>
|
||
|
||
<div class="grid md:grid-cols-2 gap-6 mb-6">
|
||
<div>
|
||
<p class="text-xs uppercase tracking-wider text-stone-500 font-bold mb-2">Before — 9 次重复生成</p>
|
||
<div class="rounded-lg border border-stone-200 bg-white p-4">
|
||
<pre class="mermaid text-xs">
|
||
sequenceDiagram
|
||
participant C as comparison.py
|
||
participant R as load_campaign_report
|
||
participant DB as SQLite
|
||
C->>R: baseline report
|
||
R->>DB: SELECT runs WHERE campaign_id
|
||
DB-->>R: 50 runs
|
||
R-->>C: report dict
|
||
C->>R: current report
|
||
R->>DB: SELECT runs WHERE campaign_id
|
||
DB-->>R: 50 runs
|
||
R-->>C: report dict
|
||
Note over C,DB: 同一对比生成中重复 3-4 轮
|
||
</pre>
|
||
</div>
|
||
<p class="text-xs text-stone-500 mt-2">comparison.py 内 9 次调用 load_campaign_report;markdown handler 7 步手动拼装</p>
|
||
</div>
|
||
<div>
|
||
<p class="text-xs uppercase tracking-wider text-stone-500 font-bold mb-2">After — 单一 view 投影</p>
|
||
<div class="rounded-lg border border-stone-200 bg-white p-4">
|
||
<pre class="mermaid text-xs">
|
||
sequenceDiagram
|
||
participant Caller
|
||
participant V as load_campaign_view
|
||
participant DB as SQLite
|
||
Caller->>V: session, campaign
|
||
V->>DB: 一次查询
|
||
DB-->>V: runs + sessions + analysis
|
||
V-->>Caller: {report, exploration, analysis, comparison}
|
||
Note over Caller,DB: 所有出口走同一 view
|
||
</pre>
|
||
</div>
|
||
<p class="text-xs text-stone-500 mt-2">一个接口,N 个投影(JSON / markdown / comparison)</p>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="text-sm text-stone-700 mb-2"><strong>问题:</strong>load_campaign_report 在 comparison.py 被调 9 次(同一对比生成中 baseline/current 各重复 3-4 轮);markdown handler 手动拼装 7 个数据源;report + exploration 总是要一起取但没有统一入口。</p>
|
||
<p class="text-sm text-stone-700 mb-3"><strong>方案:</strong>在 report.py 新增 <code>load_campaign_view(session, campaign)</code>,返回 <code>{report, exploration, analysis, comparison}</code>,内部做缓存避免重复查询。</p>
|
||
<ul class="text-sm text-stone-600 space-y-1">
|
||
<li><strong>leverage:</strong>一个接口,5 个调用点统一</li>
|
||
<li><strong>locality:</strong>新增导出格式只需投影 view,不新增取数逻辑</li>
|
||
<li><strong>性能:</strong>消除 comparison 生成中的重复报告构建</li>
|
||
</ul>
|
||
</div>
|
||
</article>
|
||
|
||
<!-- Candidate C -->
|
||
<article class="bg-white rounded-xl border border-stone-200 shadow-sm overflow-hidden">
|
||
<div class="p-6">
|
||
<div class="flex items-start justify-between mb-4">
|
||
<h2 class="text-xl font-bold">C. 仪表盘聚合逻辑下沉到 metrics 模块</h2>
|
||
<span class="inline-block px-2.5 py-0.5 rounded-full text-xs font-bold bg-amber-100 text-amber-800">值得探索</span>
|
||
</div>
|
||
<div class="flex flex-wrap gap-1.5 mb-4">
|
||
<code class="text-xs bg-stone-100 px-2 py-0.5 rounded">web/routers/stats.py</code>
|
||
<code class="text-xs bg-stone-100 px-2 py-0.5 rounded">evaluation/metrics.py</code>
|
||
</div>
|
||
|
||
<div class="grid md:grid-cols-2 gap-6 mb-6">
|
||
<div>
|
||
<p class="text-xs uppercase tracking-wider text-stone-500 font-bold mb-2">Before — 业务逻辑在 HTTP 层</p>
|
||
<div class="rounded-lg border border-stone-200 bg-stone-50 p-4 font-mono text-xs leading-relaxed">
|
||
<div class="text-stone-400"># stats.py router</div>
|
||
<div class="text-red-600">def _settled(runs): ...</div>
|
||
<div class="text-red-600">for r in runs:</div>
|
||
<div class="text-red-600"> if started.date() == today: ...</div>
|
||
<div class="text-red-600"> trigger_breakdown[...] += 1</div>
|
||
<div class="text-red-600">for sid, sruns in by_scenario.items():</div>
|
||
<div class="text-red-600"> agg = aggregate_runs(sruns)</div>
|
||
<div class="mt-2 text-stone-500">~40 行聚合逻辑 + _settled 过滤器</div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="text-xs uppercase tracking-wider text-stone-500 font-bold mb-2">After — 接口薄、实现深</p>
|
||
<div class="rounded-lg border border-stone-200 bg-stone-50 p-4 font-mono text-xs leading-relaxed">
|
||
<div class="text-stone-400"># metrics.py</div>
|
||
<div class="text-emerald-700">def compute_dashboard(runs) -> DashboardStats:</div>
|
||
<div class="text-emerald-700"> # settled, per-scenario, trigger, today</div>
|
||
<div class="mt-2 text-stone-400"># stats.py router</div>
|
||
<div class="text-slate-800">def dashboard(session):</div>
|
||
<div class="text-slate-800"> runs = RunRepository(session).list_all()</div>
|
||
<div class="text-slate-800"> return compute_dashboard(runs)</div>
|
||
<div class="mt-2 text-stone-500">handler 缩到 3 行</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="text-sm text-stone-700 mb-2"><strong>问题:</strong><code>_settled</code> 过滤器和 per-scenario 聚合在 router 文件里定义,是领域概念却锁在 HTTP 层——无法复用、无法测试。</p>
|
||
<p class="text-sm text-stone-700 mb-3"><strong>方案:</strong>抽 <code>compute_dashboard(runs) -> DashboardStats</code> 到 metrics.py,router 只做 session 生命周期和序列化。</p>
|
||
<ul class="text-sm text-stone-600 space-y-1">
|
||
<li><strong>locality:</strong>聚合规则改一处</li>
|
||
<li><strong>可测性:</strong>纯函数,输入 runs 输出 stats</li>
|
||
<li><strong>leverage:</strong>未来趋势端点复用同一函数</li>
|
||
</ul>
|
||
</div>
|
||
</article>
|
||
|
||
<!-- Candidate D -->
|
||
<article class="bg-white rounded-xl border border-stone-200 shadow-sm overflow-hidden">
|
||
<div class="p-6">
|
||
<div class="flex items-start justify-between mb-4">
|
||
<h2 class="text-xl font-bold">D. Campaigns.tsx 巨型组件拆解</h2>
|
||
<span class="inline-block px-2.5 py-0.5 rounded-full text-xs font-bold bg-amber-100 text-amber-800">值得探索</span>
|
||
</div>
|
||
<div class="flex flex-wrap gap-1.5 mb-4">
|
||
<code class="text-xs bg-stone-100 px-2 py-0.5 rounded">frontend/web/src/pages/Campaigns.tsx</code>
|
||
</div>
|
||
|
||
<div class="grid md:grid-cols-2 gap-6 mb-6">
|
||
<div>
|
||
<p class="text-xs uppercase tracking-wider text-stone-500 font-bold mb-2">Before — 1122 行单体</p>
|
||
<div class="rounded-lg border border-stone-200 bg-white p-4">
|
||
<div class="space-y-1.5">
|
||
<div class="h-6 bg-red-100 border border-red-300 rounded flex items-center px-2 text-xs font-mono">createOpen, submitting, form</div>
|
||
<div class="h-6 bg-red-100 border border-red-300 rounded flex items-center px-2 text-xs font-mono">reportOpen, reportLoading, report</div>
|
||
<div class="h-6 bg-red-100 border border-red-300 rounded flex items-center px-2 text-xs font-mono">reportRuns, reportTimeline</div>
|
||
<div class="h-6 bg-red-100 border border-red-300 rounded flex items-center px-2 text-xs font-mono">analysis, analysisBusy</div>
|
||
<div class="h-6 bg-red-100 border border-red-300 rounded flex items-center px-2 text-xs font-mono">comparison, expandedIds, timelines</div>
|
||
<div class="h-6 bg-red-100 border border-red-300 rounded flex items-center px-2 text-xs font-mono">4x usePolling</div>
|
||
<div class="h-6 bg-red-100 border border-red-300 rounded flex items-center px-2 text-xs font-mono">fetchReport: 5 并行 API 调用</div>
|
||
</div>
|
||
<p class="text-xs text-stone-500 mt-2">15+ useState,接口宽如实现</p>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="text-xs uppercase tracking-wider text-stone-500 font-bold mb-2">After — 深 hook + 薄页面</p>
|
||
<div class="rounded-lg border border-stone-200 bg-white p-4">
|
||
<div class="space-y-1.5">
|
||
<div class="h-8 deep rounded flex items-center px-2 text-xs font-mono text-white">useCampaignReport(id)</div>
|
||
<div class="h-5 bg-stone-100 border border-stone-300 rounded flex items-center px-2 text-xs font-mono">useCampaignList()</div>
|
||
<div class="h-5 bg-stone-100 border border-stone-300 rounded flex items-center px-2 text-xs font-mono">useCampaignCreate()</div>
|
||
<div class="mt-3 h-8 bg-emerald-100 border border-emerald-300 rounded flex items-center px-2 text-xs font-mono">Campaigns.tsx: ~200 行</div>
|
||
</div>
|
||
<p class="text-xs text-stone-500 mt-2">hook 深、页面薄;状态按职责分组</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="text-sm text-stone-700 mb-2"><strong>问题:</strong>1122 行组件、15+ 状态变量、fetchReport 一次发 5 个并行请求。添加报告抽屉新功能(如探索详情)需向已超载的组件再加状态。</p>
|
||
<p class="text-sm text-stone-700 mb-3"><strong>方案:</strong>抽 <code>useCampaignReport(campaignId)</code> hook 封装报告 + 分析 + 对比 + 时间线的数据获取;页面组件只负责渲染和交互。</p>
|
||
<ul class="text-sm text-stone-600 space-y-1">
|
||
<li><strong>depth:</strong>hook 接口小(一个 id 进,完整 view 出)</li>
|
||
<li><strong>locality:</strong>报告数据逻辑集中,不再散落</li>
|
||
<li><strong>可测性:</strong>hook 可独立测试,不依赖页面渲染</li>
|
||
</ul>
|
||
</div>
|
||
</article>
|
||
|
||
<!-- Candidate E -->
|
||
<article class="bg-white rounded-xl border border-stone-200 shadow-sm overflow-hidden">
|
||
<div class="p-6">
|
||
<div class="flex items-start justify-between mb-4">
|
||
<h2 class="text-xl font-bold">E. 分析 / 对比 Repository 双子合并</h2>
|
||
<span class="inline-block px-2.5 py-0.5 rounded-full text-xs font-bold bg-slate-100 text-slate-700">推测性</span>
|
||
</div>
|
||
<div class="flex flex-wrap gap-1.5 mb-4">
|
||
<code class="text-xs bg-stone-100 px-2 py-0.5 rounded">storage/repository.py</code>
|
||
</div>
|
||
|
||
<div class="grid md:grid-cols-2 gap-6 mb-6">
|
||
<div>
|
||
<p class="text-xs uppercase tracking-wider text-stone-500 font-bold mb-2">Before — 近乎相同的双子</p>
|
||
<div class="rounded-lg border border-stone-200 bg-white p-4">
|
||
<div class="grid grid-cols-2 gap-2 text-xs font-mono">
|
||
<div class="bg-blue-50 border border-blue-200 rounded p-2">
|
||
<div class="font-bold text-blue-800">AnalysisRepo</div>
|
||
<div class="text-blue-600 mt-1">get_by_campaign</div>
|
||
<div class="text-blue-600">upsert(...)</div>
|
||
<div class="text-blue-600">mark_orphans_failed</div>
|
||
</div>
|
||
<div class="bg-purple-50 border border-purple-200 rounded p-2">
|
||
<div class="font-bold text-purple-800">ComparisonRepo</div>
|
||
<div class="text-purple-600 mt-1">get_by_campaign</div>
|
||
<div class="text-purple-600">upsert(...)</div>
|
||
<div class="text-purple-600">mark_orphans_failed</div>
|
||
</div>
|
||
</div>
|
||
<p class="text-xs text-stone-500 mt-2">~60 行重复;mark_orphans_failed 在 RunRepo 还有一份</p>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="text-xs uppercase tracking-wider text-stone-500 font-bold mb-2">After — 泛型基类</p>
|
||
<div class="rounded-lg border border-stone-200 bg-white p-4">
|
||
<div class="bg-slate-800 rounded p-3 text-xs font-mono text-white">
|
||
<div class="text-slate-400"># 基类</div>
|
||
<div>class AsyncJobRepo(BaseRepo):</div>
|
||
<div> get_by_campaign()</div>
|
||
<div> upsert()</div>
|
||
<div> mark_orphans_failed()</div>
|
||
<div class="mt-2 text-slate-400"># 子类只提供表名和字段映射</div>
|
||
<div>class AnalysisRepo(AsyncJobRepo): ...</div>
|
||
<div>class ComparisonRepo(AsyncJobRepo): ...</div>
|
||
</div>
|
||
<p class="text-xs text-stone-500 mt-2">子类 ~5 行,差异在配置不在代码</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="text-sm text-stone-700 mb-2"><strong>问题:</strong>CampaignAnalysisRepository 和 CampaignPeriodComparisonRepository 结构近乎相同(get_by_campaign / upsert / mark_orphans_failed),加上 RunRepository.mark_orphans_failed 共三份拷贝。</p>
|
||
<p class="text-sm text-stone-700 mb-3"><strong>方案:</strong>抽 AsyncJobRepository 泛型基类,子类只提供表类型和字段映射。</p>
|
||
<ul class="text-sm text-stone-600 space-y-1">
|
||
<li><strong>locality:</strong>孤儿清理策略改一处</li>
|
||
<li><strong>leverage:</strong>新增异步任务类型只需 5 行子类</li>
|
||
<li>风险:引入继承层次,需权衡是否值得</li>
|
||
</ul>
|
||
</div>
|
||
</article>
|
||
|
||
<!-- Candidate F -->
|
||
<article class="bg-white rounded-xl border border-stone-200 shadow-sm overflow-hidden">
|
||
<div class="p-6">
|
||
<div class="flex items-start justify-between mb-4">
|
||
<h2 class="text-xl font-bold">F. settlement.py 吸收进 Repository</h2>
|
||
<span class="inline-block px-2.5 py-0.5 rounded-full text-xs font-bold bg-slate-100 text-slate-700">推测性</span>
|
||
</div>
|
||
<div class="flex flex-wrap gap-1.5 mb-4">
|
||
<code class="text-xs bg-stone-100 px-2 py-0.5 rounded">exploration/settlement.py</code>
|
||
<code class="text-xs bg-stone-100 px-2 py-0.5 rounded">storage/repository.py</code>
|
||
</div>
|
||
|
||
<div class="grid md:grid-cols-2 gap-6 mb-6">
|
||
<div>
|
||
<p class="text-xs uppercase tracking-wider text-stone-500 font-bold mb-2">Before — 浅模块独立成文件</p>
|
||
<div class="rounded-lg border border-stone-200 bg-stone-50 p-4 font-mono text-xs leading-relaxed">
|
||
<div class="text-stone-400"># settlement.py (30 行)</div>
|
||
<div>def settle_campaign_sessions(...):</div>
|
||
<div> for s in repo.list_by_campaign(...):</div>
|
||
<div> if s.status == RUNNING:</div>
|
||
<div> s.status = EXPIRED</div>
|
||
<div> repo.update(s)</div>
|
||
<div class="mt-2 text-stone-500">接口宽 = 实现,无深度</div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="text-xs uppercase tracking-wider text-stone-500 font-bold mb-2">After — Repository 方法</p>
|
||
<div class="rounded-lg border border-stone-200 bg-stone-50 p-4 font-mono text-xs leading-relaxed">
|
||
<div class="text-stone-400"># ExplorationSessionRepository</div>
|
||
<div class="text-emerald-700">def expire_running(self, campaign_id) -> int:</div>
|
||
<div class="text-emerald-700"> # 批量 UPDATE ... WHERE status=RUNNING</div>
|
||
<div class="mt-2 text-stone-400"># campaign_runner.py</div>
|
||
<div>repo.expire_running(campaign_id)</div>
|
||
<div class="mt-2 text-stone-500">删除 settlement.py,调用方一行</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="text-sm text-stone-700 mb-2"><strong>问题:</strong>settlement.py 是 30 行的浅模块——接口(一个函数)几乎和实现(一个循环)一样复杂。理解结算需要跨三个文件跳转。</p>
|
||
<p class="text-sm text-stone-700 mb-3"><strong>方案:</strong>将 <code>settle_campaign_sessions</code> 吸收为 <code>ExplorationSessionRepository.expire_running(campaign_id)</code>,删除 settlement.py。</p>
|
||
<ul class="text-sm text-stone-600 space-y-1">
|
||
<li><strong>删除测试通过:</strong>删掉它,复杂度回到 repository(本就在那里)</li>
|
||
<li><strong>locality:</strong>会话生命周期操作集中在 repository</li>
|
||
<li>减少一个文件跳转</li>
|
||
</ul>
|
||
</div>
|
||
</article>
|
||
|
||
</section>
|
||
|
||
<!-- Top Recommendation -->
|
||
<section id="top-recommendation" class="bg-gradient-to-br from-emerald-50 to-teal-50 border-2 border-emerald-200 rounded-xl p-8">
|
||
<h2 class="text-2xl font-serif font-bold text-emerald-900 mb-4">首选深化机会</h2>
|
||
<div class="grid md:grid-cols-3 gap-6">
|
||
<div class="md:col-span-2">
|
||
<h3 class="text-lg font-bold text-emerald-800 mb-2">A. 用例判定证据构建收敛到 case_verdict</h3>
|
||
<p class="text-sm text-stone-700 mb-3">
|
||
这是最干净的深化:两处 ~25 行的重复逻辑收敛到一个函数,接口不变、实现变深。
|
||
不涉及数据模型变更、不影响 API 契约、不触碰前端——纯粹的 locality 回归。
|
||
完成后,runs.py 的 handler 从 60 行缩到 10 行,report.py 的证据构建消失。
|
||
</p>
|
||
<p class="text-sm text-stone-600">
|
||
<strong>为什么先做它:</strong>成本最低(半天)、风险最小(纯重构)、收益最直接(删除 50 行重复)。
|
||
做完后 CaseEvidence 的构建逻辑有了唯一落点,后续任何判定相关的改动只改一处。
|
||
</p>
|
||
</div>
|
||
<div class="bg-white/60 rounded-lg p-4 border border-emerald-200">
|
||
<p class="text-xs uppercase tracking-wider text-emerald-700 font-bold mb-2">度量</p>
|
||
<ul class="text-sm text-stone-700 space-y-1">
|
||
<li>删除行数:~50</li>
|
||
<li>影响文件:3</li>
|
||
<li>测试新增:1 个</li>
|
||
<li>API 变更:无</li>
|
||
<li>前端变更:无</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
</main>
|
||
</body>
|
||
</html>
|