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:
1 parent
7b9939e0a0
commit
691671a1d2
20 files changed
+1181
-17
No files matched your search
@@ -0,0 +1,67 @@
|
||||
import type { Kysely } from "kysely";
|
||||
import { entityTable, execDdl, oneOf } from "../src/migration-helpers.ts";
|
||||
|
||||
// 集数解析与映射(docs/plan/episode-resolution.md 批次 2):
|
||||
// - episode_mapping_rule:作品 + 来源季(可空 = 任意)+ 发布组(可空 = 任意)的映射规则;
|
||||
// 同一作用域(发布组忽略大小写,空值也算一种取值)同时只能有一条 active
|
||||
// - source_release.episode_resolution:集数解析结果(原始季集、规范季集、方式、规则、证据)
|
||||
// - decision.kind 增加 episode_mapping(资源已绑定作品但集数待确认)
|
||||
|
||||
const OLD_DECISION_KINDS = [
|
||||
"identity_ambiguous",
|
||||
"download_stalled",
|
||||
"download_failed",
|
||||
"path_conflict",
|
||||
"space_insufficient",
|
||||
"approval_required",
|
||||
"metadata_conflict",
|
||||
"source_unreachable",
|
||||
"other",
|
||||
"identity_unresolved",
|
||||
];
|
||||
const NEW_DECISION_KINDS = [...OLD_DECISION_KINDS, "episode_mapping"];
|
||||
|
||||
const swapDecisionKinds = (values: readonly string[]) => `
|
||||
alter table decision drop constraint decision_kind_check;
|
||||
alter table decision add constraint decision_kind_check ${oneOf("kind", values)};
|
||||
`;
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await execDdl(
|
||||
db,
|
||||
`${entityTable(
|
||||
"episode_mapping_rule",
|
||||
`media_entity_id uuid not null references media_entity (id) on delete cascade,
|
||||
season integer,
|
||||
release_group text,
|
||||
kind text not null ${oneOf("kind", ["expression", "table", "special"])},
|
||||
expression text,
|
||||
target_season integer,
|
||||
table_entries jsonb,
|
||||
special text ${oneOf("special", ["special", "extra", "ignore"])},
|
||||
status text not null default 'active' ${oneOf("status", ["active", "disabled"])},
|
||||
evidence_level text not null default 'manual' ${oneOf("evidence_level", ["manual", "rule_confirmed", "multi_evidence", "needs_review"])},
|
||||
evidence jsonb not null default '{}'::jsonb,
|
||||
disabled_reason text,
|
||||
created_by_type text not null ${oneOf("created_by_type", ["user", "agent", "system", "api_key"])},
|
||||
created_by_id text`,
|
||||
)}
|
||||
create unique index episode_mapping_rule_active_scope on episode_mapping_rule
|
||||
(media_entity_id, coalesce(season, -1), coalesce(lower(release_group), '')) where status = 'active';
|
||||
create index episode_mapping_rule_entity on episode_mapping_rule (media_entity_id);
|
||||
alter table source_release add column episode_resolution jsonb;
|
||||
${swapDecisionKinds(NEW_DECISION_KINDS)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: 同上
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await execDdl(
|
||||
db,
|
||||
`update decision set kind = 'other' where kind = 'episode_mapping';
|
||||
${swapDecisionKinds(OLD_DECISION_KINDS)}
|
||||
alter table source_release drop column if exists episode_resolution;
|
||||
drop table if exists episode_mapping_rule;`,
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user