docs(identity): ID-3 escalation chain, triggers and list IDs

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-01 00:15:20 +08:00
1 parent 3d5a50b30a
commit bca9ecb6cf
3 files changed
+74 -3

No files matched your search

+61 -1
View File
@@ -26,6 +26,9 @@
3. 查了 TMDB 的,召回 TMDB 候选(`recallTmdbCandidates`:中文名 → 英文名,按类型单语言搜索,标题对不上的按年份相近度拉前几个详情比对别名 / 译名),
交给 `domain.resolveIdentity` 打分:auto 建档 / 关联并发 `release.resolved`;否则进池。打分规则与 MP 的对照见
`docs/porting/moviepilot-identity-match.md`(ID-2)。
站点列表 / 详情页给出的 IMDb / TMDB ID(`raw._site` / `raw._detail`)作为 `trustedIds` 参与打分(ID-3):候选按这个 ID 取来时,
标题对不上也至少 `multi_evidence`(可自动确认);年份冲突降为 `needs_confirm`;续作序号 / 类型冲突不放宽。
标题认不准时走「识别升级链」(见下节)。
4. 自动绑定时把站点标题登记为作品别名,只在标题精确命中(或别名命中且另一种文字的名称也命中)时登记,分类词 / 单字 / 停用词不登记。
FIX-5 守卫(`service/alias-guard.ts`):逐个名称再查一遍,以下任一成立就不登记,原因记在 `resolution_attempt.best.aliases.skipped`
(`added` 是登记了的):证据等级不是 `rule_confirmed`(`low_evidence`);资源年份与作品年份差超过 1 年且不是季年份(`year_conflict`);
@@ -39,6 +42,62 @@
搜索模块想让搜索结果直接识别,入队 `resolveObservation` 时传 `trigger: "search"`(目前搜索结果经 `release.observed` 进来,
按 observed 处理;命中订阅别名的照样会查)。
## 识别升级链:用站点详情页的 ID(ID-3)
用户反馈(2026-09-30):"既然能抓 PT 站详情页,详情有 TMDB、IMDb 或豆瓣,为啥还判断不了是什么资源?" 以前详情页只在用户打开时才抓,
标题识别不出就直接进待识别 / 待处理。现在标题识别结果是 `ambiguous` / `no_match` 时按下面的链再认一次
(`service/id-escalation.ts`、`resolve.ts escalateIdentity`):
1. **抓详情**(只在资源还没抓过详情时,`detail_fetched_at` 为空):走 `refreshReleaseDetail`——站点详情缓存、共享限流器、请求计数、
凭据失效处理都照旧;失败不阻塞识别,记在 `raw._escalation {at, status, error}`,6 小时内不再为识别重抓。
2. **新拿到的 IMDb / TMDB ID** → TMDB 详情 / `find` → 按 `trustedIds` 打分(列表上已有的 ID 在第一轮就用过)。
3. 还不能确认且有**豆瓣 ID** → `kv_cache` region `douban.subject` 里缓存的豆瓣条目(标题 / 别名 / 年份)→ 按条目名称 + 年份召回 TMDB →
ID-2 打分(对照 MP `chain/media/projection.py` 豆瓣 → TMDB:原名 / 中文名 / 英文名 + 年份)。**不请求豆瓣**;条目来源只有两个:
站点详情页的豆瓣信息块(◎片名 / ◎译名 / ◎年代,详情页同时给了豆瓣 ID 时写入,`source = site_detail`)和元数据补全(META-2)
网页搜索结果里 `douban.com/subject/<id>` 的搜索标题与摘要(`source = web_snippet`,不覆盖 site_detail 的)。缓存 30 天。
4. 确定(auto)→ 照常绑定:回填资源、建 / 关联作品、关闭同资源的池条目并从合并事项里摘除(组空即关)。站点标题登记为别名时
按资源**自己的标题**重新判断(靠 ID / 豆瓣名确认、标题对不上的不登记),FIX-5 守卫照旧。
证据记在 `source_release.resolution_evidence.escalation` 与池条目 `query.escalation`:`detail`(`fetched` / `skipped:<原因>` /
`deferred:cap_reached` / `failed:fetch_failed` / `not_now:batch_ingest`)、`via`(`site_id` / `douban` / null)、`ids`、`pending`、`douban`。
### 什么时候抓详情
| 触发 | 抓详情? |
|---|---|
| 搜索结果(`trigger: "search"`,搜索完成后逐条入队识别) | 是 |
| 手动「重新识别」、Agent `identity.resolve`(`manual`) | 是 |
| 池条目「元数据补全」之前(`POST /identity-pool/{id}/enrich`) | 是:资源没抓过详情或有豆瓣 ID 时先按手动重新识别走一次;认出来就返回 409「已识别为《X》,不需要补全」,不消耗补全预算 |
| 订阅相关的候选(命中订阅 / 本地作品别名,`tracked_alias`)的池复查(`review`) | 是 |
| RSS / 列表批量入库(`observed`) | 否。订阅相关的推迟(`not_now:batch_ingest`,15 分钟内复查时再抓);其余不抓 |
| 上次因限额推迟(`pending`)的复查 | 是 |
每站每小时上限 `identity.detailIdsPerSiteHour`(设置 → 元数据 → 资源识别「识别时每站每小时最多抓详情页」,缺省 10,0 = 不抓),
计数在 `kv_cache` region `identity.detail_ids`(key `<siteId>:<小时>`,原子自增,2 小时过期)。达到上限记 `deferred:cap_reached`、
15 分钟内复查。这些详情请求同样计入站点请求统计(`site_request_count`)。
### 待处理
- 升级链推迟中(`pending`:每站每小时限额已满 / 入库时不抓):不开任何身份事项,之前开的从合并事项摘除。抓了但失败的不算推迟(不阻塞,照常进池 / 开事项)。
- 资源上已有 IMDb / TMDB / 豆瓣 ID 却仍无结果:不开「没识别出作品」(`identity_unresolved`),只留在池里,可手动指定。
- 有候选的歧义(`identity_ambiguous`)照旧开;资源被识别后(升级链 / 复查 / 手动)自动从合并事项摘除,组空即关闭。
### 排查
```sql
-- 最近为识别抓详情的资源
select id, title, raw->'_escalation' esc, resolution_evidence->'escalation' ev from source_release
where raw ? '_escalation' order by (raw->'_escalation'->>'at') desc limit 20;
-- 这一小时各站点用了多少名额
select key, value from kv_cache where region = 'identity.detail_ids' and expires_at > now();
-- 缓存的豆瓣条目
select key, value->>'title' title, value->>'year' y, value->>'source' src from kv_cache where region = 'douban.subject' limit 20;
```
测试:`apps/server/test/sources-identity/id-escalation.test.ts`(观众 DET-2 / 憨憨 HH-2 详情样本经注入 fetch、TMDB mock,零真实请求);
差分组 `packages/domain/test/differential/identity/site-ids-*.json`(MP `search/result.py _match_source`:种子 IMDb 与作品一致即命中;
我们年份 / 类型冲突时只作候选,登记为 intentional,见 `site-ids-deviations.json`),`packages/domain/test/identity-id3.test.ts`。
## 接管任务按文件名识别(QB-1)
用户手动加进 qB、贴了管理标签的任务(`download_task.origin = adopted`,没有 `source_release` / 计划)下载完成后,`verifyAndImport`
@@ -63,7 +122,8 @@
|---|---|
| `unmatched` | 否 |
| `ambiguous` / `proposed`(查过 TMDB,有候选) | 是,`identity_ambiguous`,级别 warning |
| `no_match` / 复查已停的 `lookup_failed` | 只有本地召回有候选(与本地作品相关)时开 `identity_unresolved`,级别 warning |
| `no_match` / 复查已停的 `lookup_failed` | 只有本地召回有候选(与本地作品相关)、且资源上没有站点给的 IMDb / TMDB / 豆瓣 ID 时开 `identity_unresolved`,级别 warning |
| 识别升级链推迟中(ID-3) | 否(之后会抓详情再认) |
合并:dedupeKey `identity-group:<siteId>:<归一化标题>`,同来源同标题的资源并成一条。成员 = `identity_candidate.decision_id`
指向该事项且仍 `pending` 的条目。标题 1 条时写资源原标题,多条时写「来源「X」的 N 条资源待识别 / 待确认身份:<标题>」,