fix(identity): 不凭资源标题造结构外的季;刷新时收掉结构外的临时季;v0.3.43

药屋少女的呢喃:发布者把第二、三期写成 S02/S03,TMDB 连续编在第 1 季,资源懒建出第 2、3 季。

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 16:54:22 +08:00
1 parent 12f8fc59c3
commit ae137fd0ce
15 files changed
+98 -50

No files matched your search

@@ -175,8 +175,9 @@ describe.skipIf(!up)("DH-1 内容单元数据治理", () => {
] as const)
expect(byLabel.get(l)).toMatchObject({ id: ids[k], status: "confirmed", source: "tmdb" });
expect(byLabel.get("S1E1")!.title).toBe("第 1 期");
// 对不上的保持临时
for (const l of ["S1E13", "S3", "S3E1"]) expect(byLabel.get(l)).toMatchObject({ status: "provisional" });
// 结构内对不上的集保持临时;结构外的季(TMDB 只有 0–2 季)连同其临时集退役
expect(byLabel.get("S1E13")).toMatchObject({ status: "provisional" });
for (const l of ["S3", "S3E1"]) expect(byLabel.get(l)).toMatchObject({ status: "retired" });
// 季 0:特别篇季与两集按 TMDB 建(confirmed、来源 tmdb)
expect(byLabel.get("S0")).toMatchObject({ status: "confirmed", source: "tmdb" });
expect(list.filter((u) => u.season_number === 0 && u.kind === "episode").map((u) => [u.title, u.status])).toEqual([
@@ -253,16 +254,30 @@ describe.skipIf(!up)("DH-1 内容单元数据治理", () => {
expect(await seasonsNow()).toEqual(before);
expect(r.unitIds.length).toBe(before.filter((n) => n !== null && n >= 1).length);
// 空的临时季:S7 没人引用 → 退役;S8 被资源引用 → 留着;S3 下面有集 → 留着
// 结构外的季:新资源不再建(S05 单季也不建);已有的 S7(空)、S8(被资源挂着)刷新时退役,资源解开;
// S9 下的集有文件覆盖 → 整季留着要人看
const s5 = await h.db
.transaction()
.execute((trx) => ensureUnits(trx, id, "series", { season: 5 } as ParsedRelease));
expect(s5).toEqual({ unitIds: [], primary: null });
const s7 = await unit("season", 7, null);
const s8 = await unit("season", 8, null);
const s9 = await unit("season", 9, null);
const s9e1 = await unit("episode", 9, 1, { parent_unit_id: s9 });
await cover(await asset(), s9e1);
await release("s8-pack", s8);
await refreshMetadata(h.rt, id);
const status = async (uid: string) =>
(await h.db.selectFrom("content_unit").select("status").where("id", "=", uid).executeTakeFirstOrThrow()).status;
expect(await status(s7)).toBe("retired");
expect(await status(s8)).toBe("provisional");
expect(await status(ids.s3!)).toBe("provisional");
expect(await status(s8)).toBe("retired");
expect(await status(s9)).toBe("provisional");
const pack = await h.db
.selectFrom("source_release")
.select(["content_unit_id", "media_entity_id"])
.where("site_release_id", "=", "s8-pack")
.executeTakeFirstOrThrow();
expect(pack).toEqual({ content_unit_id: null, media_entity_id: id });
});
it("identity-cleanup --merge-duplicates:默认 dry-run 列清单不改库,--apply 只合并重复、不删孤立单元", async () => {