fix-review-report-critical
main
依据 Profeto-代码审查报告.md 完成修复。全程 TDD(先测后改、红绿循环),并对关键守卫做了变异测试验证其确实能捕获回归。
Profeto-代码审查报告.md
C1 定时任务永不触发 —— 实际 4 层缺陷,报告只列了 2 层
scheduler.py:42
croniter(self.cron, datetime.now)
croniter
TypeError
next_run
_calc_next
ValueError
_run_loop
sleep(min(wait,60))
SLEEP_TICK
create_task
_ensure_loop
register()
cron
C2 bzzoiro events 采集管线整体缺失
6a49940
fetch_bzzoiro_events
class BzzoiroSource
sources.py
except Exception: pass
_SOURCES
get_source("bzzoiro")
f05dc1a
C3 EvalPage.tsx 引用未导入的 EmptyState → 改 EmptyText;tsc --noEmit 由 1 error 归零
EmptyState
EmptyText
tsc --noEmit
_km
key_ring._mask
ingest_bzzoiro_standings
%d
len(ok_reports)
mode="multi"
model
timestamptz
_parse_date_bound
asyncio.gather
orchestrator.py
C1 定时任务静默失效(4 层缺陷): - scheduler.py: croniter(expr, datetime.now) 未调用 now(), 导致 TypeError 被吞、next_run 恒为 None(最深一层) - scheduler.py: _calc_next 非法 cron 静默吞异常 -> 改为构造期抛 ValueError - scheduler.py: _run_loop 一次异常即永久停摆 -> 增加异常隔离 - scheduler.py: sleep(min(wait,60)) 使运行期 cron 变更最长 60s 才生效 -> 改为固定 SLEEP_TICK 轮询 - app.py / schedules.py: 3 处 register() 第 2 参误传 task 类型名 C3 EvalPage.tsx: 引用未导入的 EmptyState -> 改为已导入的 EmptyText (tsc --noEmit 由 1 error 变为 0 error) 新增 tests/test_scheduler_registration.py: 9 项行为回归测试
C2 数据丢失修复。重构提交 6a49940 删除了 src/data/bzzoiro.py 中的 fetch_bzzoiro_events 与 BzzoiroSource 后未做替换,同时 sources.py 用 `try/except Exception: pass` 吞掉了 ImportError,导致: - _SOURCES 注册表恒为空 - get_source("bzzoiro") 恒抛 ValueError("未知数据源: bzzoiro") - src/api/routes/ingest.py 与 schedules.py 运行时全线失效, 且日志中无任何导入错误痕迹 修复内容: 1. 从 f05dc1a 恢复并移植 events 管线: - fetch_bzzoiro_events():分页抓取 /events/(纯异步,复用现有 _fetch_json_async) - @register class BzzoiroSource + ingest():保留原签名 (db, *, leagues, date_from, date_to, status),保持「不 commit, 事务由调用方 UnitOfWork 控制」的契约,并保留单联赛抓取失败的 错误隔离(记录 error 后 continue) - 复用现有 _to_date / _to_int_or_none / _match_key,未重复定义 - 血缘字段继续使用与 nm 配对的 raw(避免 P0-3 回归) 2. sources.py:导入失败改为 logger.exception 记录,新增 _loaded 标记 保证 _load_sources() 至多执行一次;失败时不置位以便后续重试。 get_source() 对真正未知的名字仍抛 ValueError。 验证: - 新增 tests/test_bzzoiro_source_registry.py(8 项):注册表非空、 get_source/list_sources 可用、ingest 签名契约、不再静默吞异常 - test_regressions.py:3 failed -> 2 failed - 全量:14 failed -> 13 failed,通过数 190 -> 199 注意事项:未改动任何 caller(routes/ingest.py、routes/schedules.py 原样 可用,证明签名保持正确)。
test_bzzoiro_source_registry.py 增至 11 项: - 新增 TestImportOrderSelfHeal:用独立子进程验证两种真实导入次序 (sources 先 / bzzoiro 先)下注册表均可用。该缺陷只在真实模块初始化 时序下成立,mock 照不出来,故不 mock。 - 新增未知名字仍抛 ValueError 的契约守卫。 src/data/sources.py:补全 _load_sources 文档,说明首次装载失败属正常的 环状导入(bzzoiro 仍在初始化),失败时保持 _loaded=False 使后续 get_source()/list_sources() 调用可重试装载(自愈)。 验证: - tests/test_bzzoiro_source_registry.py:11 passed - 两种导入次序实测均输出 bzzoiro ['bzzoiro']
R1 429 key 轮换路径调用不存在的 _km → NameError(且位于凭证脱敏日志行): 改用 src/data/key_ring._mask,全树不再有 _km 调用。 [注: 该改动已随另一工作流提交 9ccda4b 一并入库] R2 ingest_bzzoiro_standings 被截断(return 前无 upsert 逻辑,standings 表 永不写入、total_upserted 恒为 0):从 f05dc1a 移植完整实现,按 (league_id, season, team_id) upsert,保留逐联赛错误隔离,不加 db.commit()。 [注: 同上,已随 9ccda4b 入库] R3 src/llm/agents/orchestrator.py 完成日志把 list 喂给 %d → logging TypeError: 改为 len(ok_reports),与同文件降级日志行写法一致。 R4 mode=multi 静默丢弃调用方传入的 model:predict_match_multi 新增 model 关键字参数,经 _ACTIVE_MODEL_OVERRIDE 下传至 _agent_provider, 显式 model 优先级最高;override 生效时跳过 provider 缓存读写以免串味, 并在 predict.py 派发点透传。 R5 回测把字符串日期直接与 timestamptz 列比较:新增 _parse_date_bound 助手, 支持 YYYY-MM-DD / 完整 ISO / datetime / None,裸日期按 UTC 锚定, 结束日取当天末刻(闭区间,避免最后一天被静默排除),非法输入抛 ValueError。 新增 tests/test_review_required_fixes.py 覆盖 R1-R5(R2/R4 为行为测试), 20 项全通过。
9ccda4b
按「测试腐化(a)/ 源码缺陷(b)/ 测试污染(c)」逐项定性,仅改 tests/: - 外机绝对路径(4): 迁移文件路径改为相对仓库根解析(沿用 test_regressions._read 约定),断言内容保持不变。 - cutoff 用例(4+1): mock 未生效的真因是 _agent_provider 被换成同步 lambda,await 抛 TypeError 被生产代码吞掉后 IndexError;改为 async mock 并按 run_specialists/load_match_header/_agent_provider 的真实契约 打补丁。degraded 用例再加 _upsert_prediction 顶层 kwargs(model)采集。 - 陈旧断言(3): agent 键按现契约断言中文映射;h2h mock 改 async; Match.stats 按设计为 lazy="select",从 MATCH_RELATIONS 移出并单独 固化该设计决定。 - P0-3 守卫(1): seg 越界扫到下游 stats 管线导致误报,改为按缩进收口; 合法形状含经 raw 派生变量中转的写法,并补元测试确保守卫仍能抓到回归。 - 交叉污染(6): test_multi_agent_cutoff 用 patch.object 精确还原,消除 裸赋值泄漏的同步 mock;现已验证顺序无关。
原断言对「关系声明到下一注解」的整段(含注释)做 lazy="selectin" 子串匹配;而 Match.league 声明正下方的注释里恰好也写着该字样, 导致移除真实 kwarg 后测试依旧通过(变异测试确认)。 改为只匹配 relationship(...) 括号内内容,并加注释说明该假阳性。 验证: 干净代码通过 → 移除 league 的 lazy 后失败 → 还原后又通过。 全套 238 passed / 1 skipped。
R4 首版实现用模块级 _ACTIVE_MODEL_OVERRIDE 中转 model override, 理由是不想改动 run_specialists 的签名(既有测试会 mock 它)。 但 backtest.py:225 会 asyncio.gather 并发 8 场预测(Semaphore(8)), 每场都调用 predict_match_multi —— 模块级变量会被并发调用互相覆盖, 导致 A 场的预测用上 B 场的模型。这是静默的正确性缺陷。 改为: run_specialists 新增 model_override 形参,一路显式下传; 删除模块级变量。同步更新 6 处 mock 签名。 新增 3 个守卫并做变异验证: - test_r4_no_module_level_model_override_global (源码级,可判别) - test_r4_run_specialists_accepts_model_override_parameter - test_r4_dispatch_passes_override_to_specialists 变异测试: 重新引入全局变量方案 → 两个守卫变红;还原 → 全绿。 注: 曾尝试写并发行为测试,但真实 run_specialists 会访问数据库, 测试环境下不稳定(ConnectionRefusedError),会是 flaky 的假证据, 故改用清晰的源码级判别守卫,并在注释中说明原因。 240 passed / 1 skipped; tsc 0 error; vite build 成功。
No dependencies set.
The note is not visible to the blocked user.
依据
Profeto-代码审查报告.md完成修复。全程 TDD(先测后改、红绿循环),并对关键守卫做了变异测试验证其确实能捕获回归。Critical(3)
C1 定时任务永不触发 —— 实际 4 层缺陷,报告只列了 2 层
scheduler.py:42写的是croniter(self.cron, datetime.now)—— 漏了调用括号,传的是方法对象本身,croniter抛TypeError被吞 →next_run恒为 None。即使 3 处调用点全传对 cron,任务依然永不触发_calc_next非法 cron 静默吞异常 → 改为构造期抛ValueError_run_loop单任务异常会终止整个调度循环 → 增加异常隔离sleep(min(wait,60))使运行期改 cron 最长 60s 才生效 → 改固定SLEEP_TICK轮询create_task→ 新增_ensure_loopregister()第 2 参误传 task 类型名 → 改传cronC2 bzzoiro events 采集管线整体缺失
6a49940同时删掉fetch_bzzoiro_events与class BzzoiroSource却未替代;sources.py的except Exception: pass吞掉 ImportError →_SOURCES恒空 →get_source("bzzoiro")恒抛错f05dc1a恢复两符号;静默吞异常改为记日志C3 EvalPage.tsx 引用未导入的
EmptyState→ 改EmptyText;tsc --noEmit由 1 error 归零Required(5)
_km未定义 → 复用key_ring._maskingest_bzzoiro_standings是截断函数(零 upsert)→ 从f05dc1a移植完整 upsert%d传了 list → 改len(ok_reports)mode="multi"丢弃model→ 透传timestamptz→ 新增_parse_date_bound报告的两处误判(已核实)
收尾复核新增发现
asyncio.gather并发 8 场预测会互相覆盖 → A 场用上 B 场的模型。已改为形参显式下传验证
tsc --noEmit:0 error;vite build 成功遗留
orchestrator.py等超大文件)未处理C1 定时任务静默失效(4 层缺陷): - scheduler.py: croniter(expr, datetime.now) 未调用 now(), 导致 TypeError 被吞、next_run 恒为 None(最深一层) - scheduler.py: _calc_next 非法 cron 静默吞异常 -> 改为构造期抛 ValueError - scheduler.py: _run_loop 一次异常即永久停摆 -> 增加异常隔离 - scheduler.py: sleep(min(wait,60)) 使运行期 cron 变更最长 60s 才生效 -> 改为固定 SLEEP_TICK 轮询 - app.py / schedules.py: 3 处 register() 第 2 参误传 task 类型名 C3 EvalPage.tsx: 引用未导入的 EmptyState -> 改为已导入的 EmptyText (tsc --noEmit 由 1 error 变为 0 error) 新增 tests/test_scheduler_registration.py: 9 项行为回归测试