docs(runbook): subscriptions C1-A private rule groups, presets, reset and calendar
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
05350460f0
commit
62290d4477
1 file changed
+67
-10
@@ -1,7 +1,9 @@
|
||||
# 订阅运行手册(W9-E)
|
||||
# 订阅运行手册(W9-E、C1-A)
|
||||
|
||||
实现:`apps/server/src/modules/acquisition-organize/{routes/subscriptions.ts, services/coverage.ts, services/evaluate-autofill.ts, services/subscriptions-params.ts, services/subscriptions.ts}`、
|
||||
`packages/domain/src/{slot/overview.ts, acquisition/fill.ts}`、contracts `policy.ts`(`SubscriptionOverviewItem` `SubscriptionCoverage` 等)、迁移 `0035_subscriptions_v2`。
|
||||
实现:`apps/server/src/modules/acquisition-organize/{routes/subscriptions.ts, services/coverage.ts, services/evaluate-autofill.ts, services/subscriptions-params.ts, services/subscriptions.ts, services/subscription-reset.ts, services/subscription-calendar.ts}`、
|
||||
`packages/domain/src/{slot/overview.ts, acquisition/fill.ts}`、contracts `policy.ts`(`SubscriptionOverviewItem` `SubscriptionCoverage` `SubscriptionCalendar` `SubscriptionResetResult` 等)、迁移 `0035_subscriptions_v2`、`0036_subscription_private_rules`;
|
||||
集播出日期写入在 `sources-identity/service/metadata-totals.ts`(`applyEpisodeAirDates`),关键词检索在 `sources-identity/tasks.ts`(`searchSubscription`)。
|
||||
前端:`apps/web/app/routes/console/subscriptions.tsx`(`?view=calendar`)、`components/subscriptions/{calendar.tsx, actions.tsx, subscription-form.tsx, model.ts}`。
|
||||
设计:`docs/design/subscriptions.md` §1–§3、§5;规则组见 `docs/runbook/rules.md`;W5-C 账本/完成/搜索见 `docs/plan/05-subscription-gap.md` §4。
|
||||
|
||||
## 数据
|
||||
@@ -76,20 +78,72 @@
|
||||
| --- | --- | --- |
|
||||
| `siteIds` | `sites`(站点 id 或 key) | 空 = 全部启用站点;不存在 400 |
|
||||
| `downloaderInstanceId` | `downloader`(实例 id 或名称) | 不存在 400 |
|
||||
| `downloadSavePath` | `save_path` / `savePath` | 已存,下载提交暂未使用(P2) |
|
||||
| `downloadSavePath` | `save_path` / `savePath` | 下载提交用它做 qB savepath,见下"下载提交与整理去向" |
|
||||
| `episodeRange {start,end}` | `start_episode` / `end_episode`;旧 `episodeRange.locked` 丢弃 | end < start 400;电影丢弃 |
|
||||
| —(季总集数) | `total_episode` + `manual_total_episode` | 保存后写订阅季(无季单元按第 1 季)的总集数,`manual_total_episode` 为真时锁定 |
|
||||
| `include[]` / `exclude[]` | 字符串或数组;开头的 `(?i)` 去掉 | 正则白名单(`validateKeywordPattern`)+ 语法校验,不过 400;评估时并入每个版本目标的硬条件 |
|
||||
| `keywords[]` | `keyword` | 已存;主动搜索任务(sources-identity)尚未追加这些检索词 |
|
||||
| `include[]` / `exclude[]` | 字符串或数组;开头的 `(?i)` 去掉 | 正则白名单(`validateKeywordPattern`)+ 语法校验,不过 400;保存时同步为订阅私有规则组(见下),params 里保留原值供表单回显 |
|
||||
| `keywords[]` | `keyword` | 主动搜索时追加在标题/原名/别名之后(去重,不占标题检索词的 3 个上限),每个站点每个词搜一次 |
|
||||
| `resolution` | `resolution`(MP 标签 `4K`/`1080P` 或正则值 `4K\|2160p\|x2160`) | `4k / 1080p / 720p`;无法识别 400;空或"全部"= 不设 |
|
||||
| `source` | `quality` / `resource_type`(蓝光原盘 / Remux / BluRay / UHD / WEB-DL / HDTV / H265 / H264,标签或正则值) | `bluray_disc remux bluray uhd web hdtv h265 h264` |
|
||||
| `effect` | `effect`(杜比视界 / 杜比全景声 / HDR / SDR) | `dv atmos hdr sdr` |
|
||||
| `ruleGroupIds` | `filter_groups`(组名或 id) | 不存在/已删 400 |
|
||||
| —(版本目标升级) | `best_version` / `best_version_full` | 写到该订阅的版本目标 `upgrade_threshold`:关 → `enabled:false`;开 → `mode` = per_unit(剧集)/ full_pack(全集洗版)/ movie(电影),revision+1 |
|
||||
| `autoFill` | 布尔;`autoFillVersions{enabled}` | → on / off |
|
||||
| `fillBudget {unitsPerDay, bytesPerDay}` | `fillBudget{perDay, bytes}`、`autoFillVersions{budget{perDay, bytes}}` | 负数 400 |
|
||||
| `libraryRootId` | — | 库根须存在;整理去向暂未使用(organize 侧待接) |
|
||||
| `libraryRootId` | — | 库根须存在;作品还没有路径绑定时整理用它(首次建绑定) |
|
||||
| `searchIntervalHours` `stopAfter` | — | 同 W5-C;stopAfter 须是时间 |
|
||||
|
||||
新建:同作品同季(`season_unit_id` 同为空也算)已有 active / pending / paused 订阅 → **409**,消息里带已有订阅 id。PATCH 把状态改回追踪、重新订阅也做同样检查。
|
||||
|
||||
## 订阅私有规则组(include / exclude)
|
||||
|
||||
- `rule_group.hard`(迁移 0036):规则组的结构化硬条件,目前只用 `include[]` / `exclude[]`。评估时 `resolveRuleChain` 按链合并(`chainHard`):各组的包含都要满足(多组合成一条前瞻正则),排除取并集;再与版本目标自己的硬条件合并(`mergeHard`,两边的包含取 AND)。
|
||||
- 订阅保存(新建 / PATCH params / 改状态)后 `syncPrivateRuleGroup`:`scope = {subscriptionId}`、`kind = acquisition`、`ladder_required = false`、名称 `订阅私有 <id 前 13 位>`;内容 = 表单包含(至少命中一条)AND 质量预设里只能用正则表达的 UHD / Atmos,排除原样。有变化 revision+1;条件清空或订阅取消(删除)→ 软删(`deleted_at`);重新订阅按 params 重建。
|
||||
- 私有组不在规则页 `GET /rules` 列出;它排在引用链最前,不影响订阅默认链 / 显式规则组的选择。
|
||||
- 迁移为存量订阅(未取消、params 有 include / exclude)各建一个私有组。评估不再读 params 的 include / exclude(`withSubscriptionFilters` 已删)。
|
||||
|
||||
## 质量预设(MP resolution / quality / effect)
|
||||
|
||||
保存时写到**该订阅自己的**版本目标(`subscription_id = 订阅`;作品共享的无主版本目标不动):
|
||||
|
||||
| 预设 | 硬条件 | 软偏好 |
|
||||
| --- | --- | --- |
|
||||
| 4k / 1080p / 720p | `resolutions` = `[2160p]` / `[1080p, 1080i]` / `[720p]` | |
|
||||
| bluray_disc / remux | `sources` = `[BluRay-Disc]` / `[REMUX]` | |
|
||||
| bluray | `sources` = 蓝光族 `[BluRay-Disc, REMUX, BluRay]` | `sources` 同序 |
|
||||
| web / hdtv | `[WEB-DL, WEBRip]` / `[HDTV]` | web:`sources` 同序 |
|
||||
| h265 / h264 | `codecs` = `[H.265]` / `[H.264]` | |
|
||||
| uhd | 私有组包含 `UHD\|Ultra-?HD` | |
|
||||
| dv / hdr / sdr | `hdr.anyOf = [DV]` / `hdr.anyOf = [HDR10, HDR10+, HLG, HDR Vivid]` / `hdr.requireSdr` | dv / hdr:`hdr` 偏好顺序 |
|
||||
| atmos | 私有组包含 `Atmos` | |
|
||||
|
||||
改预设(`applySlotPreset`):新预设给出的字段覆盖;旧预设给过、新预设不再给的字段,只有版本目标上的值仍等于旧预设生成的值时才删(手改过的保留)。有变化 revision+1。
|
||||
新建订阅时作品还没有版本目标:收录策略有 `defaultSlots` 按模板建;没有模板但给了预设 → 建一个"默认版本"承载预设;都没有则不建(同以前)。
|
||||
|
||||
## 下载提交与整理去向
|
||||
|
||||
- 下载器:`params.downloaderInstanceId` 指定实例(须启用且有保存路径),否则取第一个可用实例;计划 `evaluation.instanceId` 记下,`submitDownload` 用它。
|
||||
- 保存路径:`params.downloadSavePath` 在评估闸门(`gates`)换成下载器侧路径(`subscriptionSavePath`):实例没配路径映射 → 原样;是本机路径且落在某条映射里 → 换成下载器路径;本身就是下载器侧路径(能反向映射回本机)→ 原样;都不成立 → 不建计划,候选记 `download_save_path_unmapped`。结果写进计划 `evaluation.savePath`,提交时作为 qB `savepath`(没有时用实例默认)。
|
||||
- 库根:整理 `chooseRoot` 的顺序是 请求指定 → 作品已有路径绑定 → **订阅 `libraryRootId`**(下载计划所属版本目标的订阅优先,否则该作品最近的未取消订阅;库根须启用)→ 按类型默认。已有绑定时订阅库根不生效(不搬家)。
|
||||
|
||||
## 重置 `POST /subscriptions/{id}/reset`
|
||||
|
||||
对照 MP "重置订阅"(清 note)。我们的 note 是在途计划:
|
||||
- 已预留未提交(`reserved`)的计划、已提交但下载任务不在(被移除、失败、丢失)的计划 → `cancelled`(`status_reason = subscription_reset`),版本目标走 `release` 迁移;
|
||||
- 下载器里仍在跑的计划保留(`keptPlans`),避免重复下载;
|
||||
- 清掉订阅关联版本目标的 `slot_deferral`,`next_evaluate_at = now`,追踪中的订阅立即入队重评;
|
||||
- 已入库资产、分集账本、总集数(含锁定)不动。写审计 `subscription.reset`。已完成 / 已取消 409(用重新订阅)。
|
||||
- 前端:卡片 / 详情"更多"菜单"重置",确认后提示释放和保留的计划数。
|
||||
|
||||
## 日历 `GET /subscriptions/calendar?from&to&mediaKind`
|
||||
|
||||
- 范围 `YYYY-MM-DD`,跨度 ≤ 62 天(月视图 6 周 = 42 天),`to < from` 400。只看追踪中(active / pending)订阅,最多 2000 条。
|
||||
- 剧集 / 综艺:与覆盖矩阵同一模型(`buildSubscriptionModels`),列的 `airDate` 在范围内的集按 日期 + 季 聚合成一条(`S01E03–E04`,不连续时 `S01E03、E05`);范围外(`out_of_range`)的集不出现。状态取主版本那一行(没有版本目标时取"库内任一版本"):全已入库 `have`、部分 `partial`、都未入库 `missing`、都在今天之后 `upcoming`。
|
||||
- 电影:日期 = 元数据 `releaseDate`,已入库 `have`,今天之后 `upcoming`,否则 `missing`;没有上映日期列进 `unknown`。
|
||||
- `unknown`:范围内没有播出日期、且还没入库的集(每季最多 50 个),以及没有任何季列的订阅。界面标"播出日期未知"。
|
||||
- 数据源:`refreshMetadata`(TMDB)对**追踪中订阅**范围内的季拉季详情(订阅季;接续后续季时含之后各季;没指定季取最新两季;最多 6 季),按(作品, 季, 集)建或补集单元 `air_date`(新建为 confirmed,挂季单元;已有单元只补日期、标题只补空)。没有追踪订阅的作品不额外请求 TMDB。`subscriptions.metadata` 定时任务照旧驱动刷新。
|
||||
- 前端:`/console/subscriptions?view=calendar&month=YYYY-MM`,类型页签同样筛选;周一开头的月网格,条目链到订阅详情;下方列"播出日期未知"。
|
||||
|
||||
## 状态动作
|
||||
|
||||
| 端点 | 说明 |
|
||||
@@ -99,6 +153,7 @@
|
||||
| `POST /subscriptions/{id}/resubscribe` | completed / cancelled → active / pending(`resubscribed`),历史保留,写审计,立即重评 + 主动搜索 |
|
||||
| `POST /subscriptions/batch {ids, action}` | `search / pause / resume / delete`;逐条返回 `{id, ok, error}`;delete = 取消(`cancelled`,`user_deleted`)并写审计 `subscription.deleted` |
|
||||
| `PUT /subscriptions/{id}/slot-order {slotIds}` | 必须恰好是该订阅的全部版本目标;第一个成为主版本 |
|
||||
| `POST /subscriptions/{id}/reset` | 见上"重置" |
|
||||
| `POST /subscriptions/{id}/search` | 同 W5-C |
|
||||
|
||||
PATCH `status` 仍可直接改(旧行为),完成写 `subscription_history` 的逻辑不变。
|
||||
@@ -107,12 +162,14 @@ PATCH `status` 仍可直接改(旧行为),完成写 `subscription_history` 的
|
||||
|
||||
- `apps/server/test/subscriptions/overview-domain.test.ts`:分组顺序、矩阵行格状态(通配在途、范围、未播出、suspected、可升级、延后)、自动补全取值、补全预算。
|
||||
- `apps/server/test/subscriptions/subscriptions-v2.test.ts`(真实 PG):矩阵(季包 + 单集 + 双版本 + 在途 + 整季通配 + 临时单元 + 未播出 + 缓存)、集范围 / 总集数未知 / 电影版本、总览分组计数排序分页、自动补全(补全计划优先级、主版本也缺照常追更、关闭记延后、手动补全)、规则组继承与补全预算、表单兼容与校验 / 409 / best_version、暂停恢复重新订阅批量排序。
|
||||
- `apps/server/test/subscriptions/subscriptions-c1a.test.ts`(纯函数 + 真实 PG + mock qB):MP 质量字段识别与映射、包含取 AND、保存路径映射;私有规则组(规则页不列、修订、软删、删除订阅与重新订阅)、评估链纳入私有组(包含没中 / 排除命中 / 与版本目标包含取 AND)、预设写版本目标与改预设保留手改、下载器与 qB savepath、映射不到不建计划、订阅库根用于首次整理、重置(释放 / 保留 / 清延后 / 审计 / 409)、日历(聚合、部分、未播出、未知、电影、跨度校验)。
|
||||
- `apps/server/test/subscriptions/subscriptions-c1a-sources.test.ts`(mock 站点 + mock TMDB):关键词追加检索、集播出日期写入与幂等、无追踪订阅不拉季详情。
|
||||
- 前端 `components/subscriptions/model.test.ts`:质量预设草稿 ↔ params、月网格与分桶。
|
||||
- E7 / E9 / 规则组 / 订阅增强测试保持通过。
|
||||
|
||||
## 遗留
|
||||
|
||||
- 日历(§4,P2)、`POST /subscriptions/{id}/reset`(差距 #24)未做。
|
||||
- `keywords` 未接主动搜索(`sources-identity/tasks.ts searchSubscription`);`libraryRootId` / `downloadSavePath` 已存未接整理与下载提交。
|
||||
- include/exclude 以订阅 params 存、评估时并入硬条件,没有建订阅私有规则组(设计 §3 的写法);分辨率/来源/特效(MP resolution/quality/effect)表单字段未映射。
|
||||
- 规则页不能编辑 `rule_group.hard`(只有订阅私有组用);多个组都带包含时合成的前瞻正则可能超过 200 字符上限,此时评估记 `invalid_pattern`。
|
||||
- 日历不做 MP 的"日历缓存预热"任务;季详情只在元数据刷新时拉。综艺按播出日期的分段单元仍只在矩阵列上标 `segment`。
|
||||
- 综艺分段列(同一播出日的上/中/下拆成独立列)等身份层按分段建单元后启用;现在只在列上标 `segment`。
|
||||
- 版本目标新建(`POST /acquisition-slots`)position 缺省 0,与主版本同值时按 created_at 排在后面;需要调整顺序用 slot-order。
|
||||
Reference in new issue
Block a user