feat(identity): 识别接入集数解析——映射规则表、规范季集写回解析结果、待确认不建单元并开待处理、规则失效停用、按作品重算

迁移 0066_episode_mapping。

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-03 18:03:32 +08:00
1 parent 7b9939e0a0
commit 691671a1d2
20 files changed
+1181 -17

No files matched your search

+44
View File
@@ -0,0 +1,44 @@
import { z } from "zod";
import { ActorType, EntityBase, Id, JsonObject } from "./common.ts";
import { EvidenceLevel } from "./identity.ts";
// 集数映射规则(docs/plan/episode-resolution.md):作品 + 来源季(可空)+ 发布组(可空)的作用域,
// 同一作用域同时只有一条 active。取值与 0066 迁移的 CHECK 一致(db-sync.test.ts 会查)。
export const EpisodeMappingRuleKind = z.enum(["expression", "table", "special"]);
export type EpisodeMappingRuleKind = z.infer<typeof EpisodeMappingRuleKind>;
export const EpisodeSpecialKind = z.enum(["special", "extra", "ignore"]);
export type EpisodeSpecialKind = z.infer<typeof EpisodeSpecialKind>;
export const EpisodeMappingRuleStatus = z.enum(["active", "disabled"]);
export type EpisodeMappingRuleStatus = z.infer<typeof EpisodeMappingRuleStatus>;
const EpisodeRangeSchema = z.object({ start: z.number().int(), end: z.number().int() });
/** 表格规则的一条:来源集号区间 → 目标的一集 */
export const EpisodeMappingTableEntry = z.object({
from: EpisodeRangeSchema,
to: z.object({ season: z.number().int(), episode: z.number().int() }),
});
export type EpisodeMappingTableEntry = z.infer<typeof EpisodeMappingTableEntry>;
export const EpisodeMappingRuleRow = EntityBase.extend({
mediaEntityId: Id,
/** 来源季;null = 任意季 */
season: z.number().int().nullable(),
/** 发布组;null = 任意发布组,比较忽略大小写 */
releaseGroup: z.string().nullable(),
kind: EpisodeMappingRuleKind,
/** expression 规则的偏移表达式(如 `EP-12`) */
expression: z.string().nullable(),
targetSeason: z.number().int().nullable(),
tableEntries: z.array(EpisodeMappingTableEntry).nullable(),
special: EpisodeSpecialKind.nullable(),
status: EpisodeMappingRuleStatus,
evidenceLevel: EvidenceLevel,
evidence: JsonObject,
/** 规则失效被停用时的证据 */
disabledReason: z.string().nullable(),
createdByType: ActorType,
createdById: z.string().nullable(),
});
export type EpisodeMappingRuleRow = z.infer<typeof EpisodeMappingRuleRow>;