前后端同时改,部署前开着的旧页面要刷新一次(题号列会是空的、提交筛选会失效)。 - 挂在非题目对象上的展示题号,原来叫 problem(字符串)或 displayId,统一成 problemDisplayId:提交列表、流程图列表、统计的 lastFailure / items / 今日最热题、站内信里的提交、后台题单的题目列表。别处的 problem 都是对象 或数字 id,这几处是字符串,最容易看错 - 列表筛选的 query 参数 ?problemId= 实际传的是展示题号,改成 ?problemDisplayId=(提交列表、流程图列表、三个统计接口) - 契约里 problemsetId / problemSetId、problemset / problemSet 两种拼法并存, 统一成 problemSetId / problemSet,和 ProblemSet* 类型名、problem-sets 路径、前端路由参数一致。drizzle 的列键跟表名走,没动 - aiDetail.user(用户名字符串,而契约里别的 user 都是对象)→ username。 ai_analysis.data 里存的历史快照仍是 user,没有代码读回它 - 后台题单题目表的列是按字符串键 'displayId' 取的,类型检查抓不到,一并改 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
routes/ai.ts 1149 行里约六成是 buildSolved / buildDetail / buildDuration 这些查询 构造器,它们不碰 Context,只是查询。挪出去之后 route 文件剩 585 行,只管 「鉴权 → 取参 → 调统计 → 回包」和 AI 调用本身;targetUser / throttleAi 依赖 Context,留在 route 里。 纯搬移,函数体一行没改。实跑核过:同一个用户的 /ai/detail、/ai/duration(按月、 按周)、/ai/solved、/ai/heatmap 五个响应,改前改后逐字节一致。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>