feat(identify-words): 识别词——全局识别词(MP 语法)+ 作品识别词(季集换算),规则页「识别词」页签

- 新表 identify_words(0094):全局一块 + 每部作品一块原文,带版本与审计;episode_mapping_rule 转成文本行后删表
- 全局识别词移植 MP WordsMatcher(屏蔽 / 替换 / 集数偏移 / 替换+偏移 / {[tmdbid=…]}),所有按标题 / 文件名识别的地方解析前套用,命中的行记在解析结果上;保存后后台按改动行重新识别
- 作品识别词:S02 >> S01 EP+24 / S01E13 >> S00E01 / S01 @组 >> 忽略,解析器 + 打印器 + 编译;发布组繁简视为同一个
- 去掉规则自动停用:算出来对不上季表只待确认,要停用就注释掉那一行
- 集数待确认的采用 / 忽略与智能体 apply_rule 改为往作品识别词追加一行(带来历注释)
- 编辑器可视化 / 文本两种模式改同一份原文,保存前试算;作品页「集数映射」用同一个编辑器

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-08 17:03:00 +08:00
1 parent 12f8fc59c3
commit 63e94b308e
90 files changed
+6366 -2131

No files matched your search

+25 -41
View File
@@ -7,9 +7,6 @@ import { CAPABILITY_CATALOG, type CapabilitySpec, type KnownCapabilityId } from
import { Id, JsonObject, NonNegInt } from "./common.ts";
import { MediaEntity, MediaKind } from "./content.ts";
import {
EpisodeMappingRuleSpec,
EpisodeMappingRuleStatus,
EpisodeMappingTableEntry,
EpisodeReresolveResult,
EpisodeResolutionMethod,
EpisodeSpecialKind,
@@ -17,14 +14,7 @@ import {
EpisodeValidationState,
} from "./episode-mapping.ts";
import { AcquisitionPlan, DownloadStatus, DownloadTask, Task, TaskStatus } from "./execution.ts";
import {
EnrichmentStatus,
EnrichSuggestion,
EnrichUsage,
EvidenceLevel,
ExternalEntityType,
ExternalIdentity,
} from "./identity.ts";
import { EnrichmentStatus, EnrichSuggestion, EnrichUsage, ExternalEntityType, ExternalIdentity } from "./identity.ts";
import { OrganizeMetadataInput, OrganizeMetadataOutput } from "./local-metadata.ts";
import { ServerMapping } from "./ops.ts";
import { OrganizationAction, OrganizationRecord } from "./organization.ts";
@@ -927,7 +917,7 @@ export const WebSearchTestResult = z.object({
export type WebSearchTestResult = z.infer<typeof WebSearchTestResult>;
// ── episode_mapping.*:集数映射的智能体兜底(docs/plan/episode-resolution.md 批次 5) ──
// inspect 给上下文包(有上限),simulate 试草稿规则,apply_rule 服务端强制先模拟、通过才写规则并重算。
// inspect 给上下文包(有上限),simulate 试一行作品识别词,apply_rule 服务端强制先模拟、通过才把这一行写进作品识别词并重算。
const EpisodeRange = z.object({ start: z.number().int(), end: z.number().int() });
const EpisodeSourceView = z.object({
@@ -974,8 +964,6 @@ export const EpisodeMappingInspectOutput = z.object({
status: DecisionStatus,
title: z.string(),
summary: z.string().nullable(),
/** 算出越界被停用的规则 */
failedRules: z.array(z.string()),
})
.nullable(),
/** 事项内资源(没有事项时 = 这个范围里当前待确认的资源),最多 50 条 */
@@ -994,25 +982,18 @@ export const EpisodeMappingInspectOutput = z.object({
),
latestSeason: z.number().int().nullable(),
ended: z.boolean(),
/** 这部作品的映射规则(含停用及原因);byAgent = 智能体写的 */
rules: z.array(
z.object({
id: Id,
season: z.number().int().nullable(),
releaseGroup: z.string().nullable(),
kind: z.enum(["expression", "table", "special"]),
expression: z.string().nullable(),
targetSeason: z.number().int().nullable(),
tableEntries: z.array(EpisodeMappingTableEntry).nullable(),
special: EpisodeSpecialKind.nullable(),
status: EpisodeMappingRuleStatus,
disabledReason: z.string().nullable(),
evidenceLevel: EvidenceLevel,
byAgent: z.boolean(),
/** 智能体写规则时的理由 */
reasons: z.array(z.string()),
}),
),
/** 这部作品的作品识别词原文与逐行解析(注释掉的行是用户停用的,不要再写同范围的行) */
words: z.object({
text: z.string(),
lines: z.array(
z.object({
line: z.number().int(),
text: z.string(),
kind: z.enum(["blank", "comment", "map", "table", "special"]),
error: z.string().nullable(),
}),
),
}),
/** 确定性建议(按发布时间对播出日期,全部样本成立且唯一才有) */
suggestion: EpisodeSuggestion.nullable(),
truncated: z.object({ releases: z.boolean(), neighbors: z.boolean() }),
@@ -1023,8 +1004,8 @@ export const EpisodeMappingSimulateInput = z.object({
decisionId: Id.optional(),
/** 不给 decisionId 时必填 */
mediaEntityId: Id.optional(),
/** 草稿规则(expression / table / special + 作用域) */
rule: EpisodeMappingRuleSpec,
/** 一行作品识别词,如 `S02 @ANi >> S01 EP+12`、`S01E13 >> S00E01`、`S03 @Grp >> 忽略` */
line: z.string().trim().min(1).max(500),
/** 理由:模拟通过时作为智能体建议留在事项上(后台调查没有写规则授权时) */
reasons: z.array(z.string().trim().min(1).max(300)).max(10).optional(),
});
@@ -1061,7 +1042,8 @@ export const EpisodeSimulationSummary = z.object({
});
export type EpisodeSimulationSummary = z.infer<typeof EpisodeSimulationSummary>;
export const EpisodeMappingSimulateOutput = z.object({
rule: EpisodeMappingRuleSpec,
/** 规范写法的那一行 */
line: z.string(),
summary: EpisodeSimulationSummary,
/** 没通过的原因(通过时为空) */
failures: z.array(z.string()),
@@ -1074,14 +1056,16 @@ export const EpisodeMappingSimulateOutput = z.object({
export const EpisodeMappingApplyRuleInput = z.object({
/** 集数待确认事项 ID */
decisionId: Id,
/** 草稿规则;季 / 发布组不填时用事项的 */
rule: EpisodeMappingRuleSpec,
/** 为什么这样映射(依据),至少一条 */
/** 一行作品识别词(写法同 simulate) */
line: z.string().trim().min(1).max(500),
/** 为什么这样映射(依据),至少一条;写进识别词时作为这一行上方的注释 */
reasons: z.array(z.string().trim().min(1).max(300)).min(1).max(10),
});
export const EpisodeMappingApplyRuleOutput = z.object({
ruleId: Id,
evidenceLevel: EvidenceLevel,
/** 追加进作品识别词的原文(注释 + 规则行) */
appended: z.string(),
/** 规则行在识别词里的行号 */
lineNumber: z.number().int(),
summary: EpisodeSimulationSummary,
reresolve: EpisodeReresolveResult,
/** 写入后事项的状态(资源都解决时已自动关闭) */