docs(plan): mark W5-C subscription enhancement status in 05-subscription-gap

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-27 23:34:13 +08:00
1 parent 80780add66
commit fc49c58359
1 file changed
+54 -25
+54 -25
View File
@@ -93,37 +93,66 @@
### P0(错误结果/主链路,建议 W5 首批) ### P0(错误结果/主链路,建议 W5 首批)
1. **总集数来源与账本**:`refreshMetadata` 把 TMDB `seasons[].episodeCount` 写入季单元并预建 provisional 集单元,插件/人工值只升、可锁 — `apps/server/src/modules/sources-identity/service/{metadata,entities}.ts`、`packages/adapters/src/tmdb/client.ts`(已有字段)。 1. ✅(W5-C)**总集数来源与账本**:`refreshMetadata` 把 TMDB `seasons[].episodeCount` 写入季单元并预建 provisional 集单元,插件/人工值只升、可锁 — `apps/server/src/modules/sources-identity/service/{metadata,entities}.ts`、`packages/adapters/src/tmdb/client.ts`(已有字段)。
2. **总集数纯函数**:`resolveEpisodeTotal(old, fresh, injected, coveredMax, locked)`(只升;回退不低于已覆盖最高集)— `packages/domain/src/slot/`(新 `episode-total.ts`)+ 单测(账本 §13 样本 1、9)。 2. ✅(W5-C)**总集数纯函数**:`resolveEpisodeTotal(old, fresh, injected, coveredMax, locked)`(只升;回退不低于已覆盖最高集)— `packages/domain/src/slot/`(新 `episode-total.ts`)+ 单测(账本 §13 样本 1、9)。
3. **完成判定修正**:`slotComplete` 用季单元 total 与 `totalKnown`,未知或未齐 → 继续追踪;`once` 订阅仅 `totalKnown && complete` 完成 — `apps/server/src/modules/acquisition-organize/services/organize.ts`、`packages/domain/src/slot/completion.ts`。 3. ✅(W5-C)**完成判定修正**:`slotComplete` 用季单元 total 与 `totalKnown`,未知或未齐 → 继续追踪;`once` 订阅仅 `totalKnown && complete` 完成 — `apps/server/src/modules/acquisition-organize/services/organize.ts`、`packages/domain/src/slot/completion.ts`。
4. **缺集纯函数**:`computeUnitGaps(required, covered, inflight, range)`,评估与完成共用 — `packages/domain/src/slot/`(新 `gaps.ts`)、`services/acquisition.ts` satisfiedUnits。 4. ✅(W5-C)**缺集纯函数**:`computeUnitGaps(required, covered, inflight, range)`,评估与完成共用 — `packages/domain/src/slot/`(新 `gaps.ts`)、`services/acquisition.ts` satisfiedUnits。
5. **Slot×单元预留**:同 Slot 允许多个单元不相交的在途计划,`BUSY` 从整 Slot 改为按单元冲突;调整部分唯一索引 — `services/acquisition.ts` reserve、`packages/db/migrations`(新迁移)、`domain/slot/state.ts`(剧集态语义)。 5. ✅(W5-C)**Slot×单元预留**:同 Slot 允许多个单元不相交的在途计划,`BUSY` 从整 Slot 改为按单元冲突;调整部分唯一索引 — `services/acquisition.ts` reserve、`packages/db/migrations`(新迁移)、`domain/slot/state.ts`(剧集态语义)。
6. **忙后重评与定时重评**:Slot 离开忙态入队 `evaluateAcquisition{acquisitionSlotId}`;巡检消费 `next_evaluate_at` — `services/organize.ts`、`services/downloads.ts`、`acquisition-organize/index.ts` 循环。 6. ✅(W5-C)**忙后重评与定时重评**:Slot 离开忙态入队 `evaluateAcquisition{acquisitionSlotId}`;巡检消费 `next_evaluate_at` — `services/organize.ts`、`services/downloads.ts`、`acquisition-organize/index.ts` 循环。
7. **订阅主动搜索任务**:`searchSubscription`(新订阅立即 + 缺口 Slot 每 24h + 手动),按实体标题/别名调 `snapshotSite({query})` 后走原识别评估链 — `apps/server/src/modules/sources-identity/{tasks,scheduler}.ts`、`acquisition-organize/routes/rules.ts`(手动端点)、`packages/contracts/src/api.ts`。 7. ✅(W5-C)**订阅主动搜索任务**:`searchSubscription`(新订阅立即 + 缺口 Slot 每 24h + 手动),按实体标题/别名调 `snapshotSite({query})` 后走原识别评估链 — `apps/server/src/modules/sources-identity/{tasks,scheduler}.ts`、`acquisition-organize/routes/rules.ts`(手动端点)、`packages/contracts/src/api.ts`。
8. **追踪剧集元数据刷新周期**:active 剧集订阅每 6h `refreshMetadata`,完成判定前强制一次 — `sources-identity/scheduler.ts`。 8. ◐(W5-C)**追踪剧集元数据刷新周期**:active 剧集订阅每 6h `refreshMetadata`,完成判定前强制一次 — `sources-identity/scheduler.ts`。
### P1(常用过滤与季包) ### P1(常用过滤与季包)
9. **关键词包含/排除**:`SlotHardConditions.include/exclude`(正则白名单校验)— `packages/domain/src/slot/{types,evaluate}.ts`、`packages/contracts/src/policy.ts`、`packages/mapping/src/slot.ts`。 9. ✅(W5-C)**关键词包含/排除**:`SlotHardConditions.include/exclude`(正则白名单校验)— `packages/domain/src/slot/{types,evaluate}.ts`、`packages/contracts/src/policy.ts`、`packages/mapping/src/slot.ts`。
10. **订阅站点范围**:`params.siteIds`,评估跳过范围外站点并记理由 — `services/acquisition.ts`、`packages/contracts/src/policy.ts`。 10. ✅(W5-C)**订阅站点范围**:`params.siteIds`,评估跳过范围外站点并记理由 — `services/acquisition.ts`、`packages/contracts/src/policy.ts`。
11. **每集体积口径**:`perUnitSize` 按覆盖单元数折算 — `domain/slot/evaluate.ts`。 11. ✅(W5-C)**每集体积口径**:`perUnitSize` 按覆盖单元数折算 — `domain/slot/evaluate.ts`。
12. **集范围**:`params.episodeRange{start,end?,locked}` 裁剪评估与完成 — `services/acquisition.ts`、`services/organize.ts`、contracts。 12. ✅(W5-C)**集范围**:`params.episodeRange{start,end?,locked}` 裁剪评估与完成 — `services/acquisition.ts`、`services/organize.ts`、contracts。
13. **季包按需选集**:提交后读文件列表,`setFilePriority` 只下缺失单元文件,计划记 `selectedUnits` — `services/downloads.ts`、`packages/adapters/src/qbittorrent/client.ts`。 13. ✅(W5-C)**季包按需选集**:提交后读文件列表,`setFilePriority` 只下缺失单元文件,计划记 `selectedUnits` — `services/downloads.ts`、`packages/adapters/src/qbittorrent/client.ts`。
14. **按单元质量账本(分集升级)**:(Slot, unit) → 当前资产 + 质量档;升级按单元比较,候选只取能提升的单元 — 新迁移、`services/{acquisition,organize}.ts`、`domain/slot/evaluate.ts`。 14. ✅(W5-C)**按单元质量账本(分集升级)**:(Slot, unit) → 当前资产 + 质量档;升级按单元比较,候选只取能提升的单元 — 新迁移、`services/{acquisition,organize}.ts`、`domain/slot/evaluate.ts`。
15. **titleQuery 订阅落地**:创建时解析身份,需确认进待处理中心,确认后回填实体并重评 — `routes/rules.ts`、`sources-identity/service/resolve.ts`。 15. ◐(W5-C 仅状态)**titleQuery 订阅落地**:创建时解析身份,需确认进待处理中心,确认后回填实体并重评 — `routes/rules.ts`、`sources-identity/service/resolve.ts`。
16. **季范围语义**:`current_season` 无 `season_unit_id` 时固定到创建时最新季;origin=agent 默认 current_season — `routes/rules.ts`、`services/acquisition.ts`。 16. ✗ **季范围语义**:`current_season` 无 `season_unit_id` 时固定到创建时最新季;origin=agent 默认 current_season — `routes/rules.ts`、`services/acquisition.ts`。
17. **偏好标签接入评估**:active 标签编译进 SlotSpec/新作品过滤,理由记标签 id — `services/acquisition.ts`、`packages/mapping/src/slot.ts`。 17. ✗ **偏好标签接入评估**:active 标签编译进 SlotSpec/新作品过滤,理由记标签 id — `services/acquisition.ts`、`packages/mapping/src/slot.ts`。
### P2(增强) ### P2(增强)
18. 订阅查重(同实体同季 active 幂等)— `routes/rules.ts`。 18. ✗ 订阅查重(同实体同季 active 幂等)— `routes/rules.ts`。
19. 全集/整包优先升级模式 `packPolicy` — `domain/slot/{types,evaluate}.ts`、`services/acquisition.ts`。 19. ◐(W5-C:`upgrade.mode=full_pack` 只收整季包;prefer_pack 未做)全集/整包优先升级模式 `packPolicy` — `domain/slot/{types,evaluate}.ts`、`services/acquisition.ts`。
20. 做种数/促销硬条件 — `domain/slot/evaluate.ts`。 20. ✅(W5-C)做种数/促销硬条件 — `domain/slot/evaluate.ts`。
21. `require_distinct_asset` 接入评估与入库 — `services/{acquisition,organize}.ts`、`domain/slot/completion.ts`。 21. ✗ `require_distinct_asset` 接入评估与入库 — `services/{acquisition,organize}.ts`、`domain/slot/completion.ts`。
22. Agent 选片理由/证据字段与独立预算 — `acquisition-organize/capabilities.ts`、`domain/acquisition/budget.ts`、contracts。 22. ✗ Agent 选片理由/证据字段与独立预算 — `acquisition-organize/capabilities.ts`、`domain/acquisition/budget.ts`、contracts。
23. 回退等待期 `fallback.after` — `packages/mapping/src/slot.ts`、`domain/slot/evaluate.ts`。 23. ✗ 回退等待期 `fallback.after` — `packages/mapping/src/slot.ts`、`domain/slot/evaluate.ts`。
24. 订阅重置端点 — `routes/rules.ts`。 24. ◐(W5-C:完成保留行 + PATCH status=active 重新订阅;无清账重置)订阅重置端点 — `routes/rules.ts`。
25. 队列按 `sortByPriority` 出队 — `services/downloads.ts`。 25. ✗ 队列按 `sortByPriority` 出队 — `services/downloads.ts`。
26. 实体级识别词/集偏移(替代 MP custom_words)— `sources-identity`。 26. 实体级识别词/集偏移(替代 MP custom_words)— `sources-identity`。
不迁移:订阅分享/Fork/Follow/热门、Seerr 入口、完成否决插件事件、搜索随机休眠(由站点限流器替代)。 不迁移:订阅分享/Fork/Follow/热门、Seerr 入口、完成否决插件事件、搜索随机休眠(由站点限流器替代)。
## 4. W5-C 实施记录(2026-09-27)
对应 §3 的状态已在条目前标注(✅ 完成 / ◐ 部分 / ✗ 未做)。实现要点与落点:
| 能力 | 实现 | 落点 |
| --- | --- | --- |
| 总集数账本 | 季单元 `content_unit.episode_total/_source/_locked/_at`(迁移 0025);`resolveEpisodeTotal`:变大采用、注入只升、TMDB 回退取 max(新值, 已覆盖最高集)、锁定不改、未公布保持未知;`refreshMetadata` 写入(只建季单元,不预建集单元);手动 `POST /subscriptions/{id}/episode-total`(不锁只升,锁定可调低) | `domain/slot/episode-total.ts`、`sources-identity/service/metadata-totals.ts`、`acquisition-organize/services/subscriptions.ts` |
| 完成判定 | `slotComplete`:按季 `seasonProgress`(总集数已知且范围内全覆盖才完成;未知 → 继续追踪 + "疑似缺集"待处理 info);pending 订阅不完成;once 订阅完成、continuous+仅本季剧集季齐也完成;完成写 `subscription_history`(行保留,改回 active 即重新订阅) | `services/subscriptions.ts`(经 `applySlotEvent(verify_passed)` 钩入,覆盖 organize 传入的 complete) |
| 缺集/在途 | `computeUnitGaps(units, covered, inflight, range)`;在途 = 该 Slot reserved/submitted 计划的 `unit_keys`(提交即计入,失败/移除释放);季包总集数已知按缺集展开,未知用整季通配 `s{季}*` | `domain/slot/gaps.ts`、`services/acquisition.ts` |
| Slot×单元多在途 | 去掉 `acquisition_plan_slot_active_key` 唯一索引;剧集 Slot 在行锁内校验单元不相交后可再预留;Slot 状态 = 在途计划最靠后阶段(`transitionWithInflight`),最后一个结束才落静止态;电影 Slot 仍单在途 | `domain/slot/inflight.ts`、`services/downloads.ts applySlotEvent`、迁移 0025 |
| 延后与重评 | Slot 忙/单元在途冲突 → `slot_deferral` + 候选评估 `decision=deferred`;计划结束(释放/失败/核验/下载被移除)→ `next_evaluate_at=now`;编排循环 `sweepDueSlots` 入队 `evaluateAcquisition{acquisitionSlotId}`(先按集号评 deferred 再评最近发布);未满足 Slot 按 `slotReevaluateIntervalMs`(缺省 6h)定时重评 | `services/subscriptions.ts`、`index.ts planTick` |
| 主动搜索 | `searchSubscription`(sources-identity 注册):标题/原名/别名 ≤3 个检索词 × 订阅站点范围内已开启且非 failing 的站点,经 `liveSearch`(适配器 search + 共享限流 + 合并/退避 + `site_snapshot kind=live_search`);新结果走 release.observed → resolved → evaluate,旧结果由任务末尾的订阅重评覆盖;用户新建订阅立即一次,之后 `next_search_at`(`params.searchIntervalHours`,缺省 24h,0 关)由编排循环 `sweepSubscriptionSearch` 到期入队;手动 `POST /subscriptions/{id}/search` | `sources-identity/tasks.ts`、`services/subscriptions.ts`、`routes/rules.ts` |
| 元数据周期刷新 | 追踪中剧集(active/pending 且有 TMDB 身份)`last_metadata_refresh_at` 超过 `metadataRefreshIntervalMs`(缺省 6h)→ 入队 `refreshMetadata`;**完成前强制刷新未做**(完成用当前账本) | `services/subscriptions.ts sweepMetadataRefresh` |
| 季包选文件 | 提交后 `files()`(重试 3 次),视频文件按文件名解析集号:需要 = 计划单元(通配 = 未覆盖且不在其它在途计划)→ 其余视频与有集号的附属文件 `setFilePriority(0)`;拿不到清单/有视频认不出集号/包里没有需要的集 → 整包并在 `acquisition_plan.file_selection` 记理由;`unit_keys` 收敛为实际选中集;接管的已有任务不改选择 | `services/downloads.ts selectPackFiles` |
| 分集质量账本 | `slot_unit`(Slot × 内容单元 → 当前资产 + 质量摘要 + 阶梯 pri);核验通过时登记;升级计划只替换计划内单元;全部已覆盖时按 `improvableUnits` 只取能提升的单元建 `purpose=upgrade` 计划 | `services/subscriptions.ts afterSlotVerified`、`services/acquisition.ts` |
| 硬条件扩展 | `excludeSources`、`hdr{anyOf,noneOf,requireSdr}`、`codecs/excludeCodecs`、`min/maxUnitSizeBytes`(每集体积,季包按账本总集数折算)、`include/exclude` 正则(白名单校验:长度、反向引用、后行断言、嵌套量词)、`sites/excludeSites`、`minSeeders`、`freeOnly`;站点事实经 `EvaluationContext` 传入,缺失按 unknownPolicy | `domain/slot/{types,evaluate}.ts` |
| 优先级阶梯 | `soft.tiers[]`(`>` 分层,层内条件同时满足),pri = 100 − 层号,未命中 0(`tiersRequired` 时拒绝);排序先比 pri;`upgrade.basis=priority`(配置阶梯时缺省)要求 pri 严格提升,`targetPri` 顶档;`upgrade.mode` movie / per_unit / full_pack | `domain/slot/evaluate.ts priorityLadder/currentPri/improvableUnits` |
| trace | 所有评估结果带 `trace`(逐条违规中文说明 + 逐层阶梯),`describeViolation` 统一文案;候选评估记录新增 `pri/trace/unitKeys` | `domain/slot/evaluate.ts`、`mapping/slot.ts`、`contracts/policy.ts CandidateEvaluation` |
| 状态 | `subscription.status` 新增 `pending`(未建档,或剧集无已接受 TMDB 身份;可搜索/评估,不可完成;`refreshMetadata` 成功后转 active);暂停不评估不搜索 | 迁移 0025、`routes/rules.ts`、`metadata-totals.ts` |
| API/UI | `GET /subscriptions/{id}/progress`(季总集数 + 每集状态 + Slot 在途/延后)、`POST /subscriptions/{id}/search`、`POST /subscriptions/{id}/episode-total`、`GET /subscription-history`;订阅详情加"立即搜索"、主动搜索时间、追更进度(每集色块 + 设置/锁定总集数) | `contracts/api.ts`、`apps/web/app/routes/console/subscriptions.tsx` |
测试:`packages/domain/test/subscription.test.ts`(账本/缺集/多在途迁移/硬条件/阶梯/洗版,覆盖账本 §13 样本 1、3、6、9、11、12);
`acquisition-organize/__tests__/subscription.test.ts`(多在途 + 延后重评 + 移除释放、季包选文件与整包兜底、总集数未知不结束 + 疑似缺集 + once 完成写历史、pending 不完成/暂停不评估、分集洗版);
`test/sources-identity/subscription-search.test.ts`(mock NexusPHP 主动搜索、站点范围、手动/暂停、季总集数写入与 pending→active);
`test/e2e/e9-continuous-series.test.ts`(连续更新 3 集在 Slot 忙时到达全部入库,另一版本延后不重复下载,once 完成写历史);E7 保持通过。
遗留:#8 完成前强制刷新;#15 titleQuery 订阅建档回填;#16 季范围语义;#17 偏好标签接入评估;P2 #18/#21–#23/#25/#26;
`organize.ts verifyAndImport` 仍保留其本地 `slotComplete` 与"once 订阅完成"更新(W4-E4 改执行侧期间不动),实际完成与历史由 `applySlotEvent` 钩子按总集数账本判定;执行侧稳定后应删掉本地判定,改调 `services/subscriptions.ts`。