fix(identity): include episode and recognition words in resolve idempotency
This commit is contained in:
1 parent
864856c9bb
commit
27386ba9df
3 files changed
+39
-4
No files matched your search
@@ -118,10 +118,22 @@ export interface ResolveResult {
|
|||||||
const candKey = (c: DomainCandidate) =>
|
const candKey = (c: DomainCandidate) =>
|
||||||
c.localEntityId ? `local:${c.localEntityId}` : `${c.provider}:${c.entityType}:${c.externalId}`;
|
c.localEntityId ? `local:${c.localEntityId}` : `${c.provider}:${c.entityType}:${c.externalId}`;
|
||||||
|
|
||||||
|
/** 作品候选与季集映射输入一起判幂等;全局词改编号/发布组时必须重算。 */
|
||||||
export function evidenceVersion(parsed: ParsedRelease | null, ranked: RankedCandidate[]): string {
|
export function evidenceVersion(parsed: ParsedRelease | null, ranked: RankedCandidate[]): string {
|
||||||
const h = createHash("sha1")
|
const h = createHash("sha1")
|
||||||
.update(
|
.update(
|
||||||
JSON.stringify([parsed?.title ?? null, parsed?.year ?? null, ranked.map((r) => [candKey(r.candidate), r.level])]),
|
JSON.stringify([
|
||||||
|
parsed?.title ?? null,
|
||||||
|
parsed?.year ?? null,
|
||||||
|
parsed?.season ?? null,
|
||||||
|
parsed?.seasonEnd ?? null,
|
||||||
|
parsed?.seasonInferred ?? false,
|
||||||
|
parsed?.episodes ?? null,
|
||||||
|
parsed?.airDate ?? null,
|
||||||
|
parsed?.releaseGroup?.name ?? null,
|
||||||
|
parsed?.words?.applied ?? [],
|
||||||
|
ranked.map((r) => [candKey(r.candidate), r.level]),
|
||||||
|
]),
|
||||||
)
|
)
|
||||||
.digest("hex")
|
.digest("hex")
|
||||||
.slice(0, 16);
|
.slice(0, 16);
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { type ParsedRelease, parseRelease } from "@umt/domain";
|
||||||
|
import { expect, it } from "vitest";
|
||||||
|
import { evidenceVersion } from "../../src/modules/sources-identity/service/resolve.ts";
|
||||||
|
|
||||||
|
it("身份候选不变时,季集和规则匹配输入变化也必须重算;相同事实仍幂等", () => {
|
||||||
|
const original = parseRelease("[Lilith-Raws] Example S01E40 [1080p]");
|
||||||
|
const before = evidenceVersion(original, []);
|
||||||
|
const changes: Partial<ParsedRelease>[] = [
|
||||||
|
{ season: 2 },
|
||||||
|
{ seasonEnd: 2 },
|
||||||
|
{ seasonInferred: true },
|
||||||
|
{ episodes: { start: 12, end: 12 } },
|
||||||
|
{ airDate: "20250314" },
|
||||||
|
{ releaseGroup: { name: "ANi", source: "bracket" } },
|
||||||
|
{ words: { applied: ["Example >> EP-28"], original: original.raw } },
|
||||||
|
];
|
||||||
|
for (const change of changes) expect(evidenceVersion({ ...original, ...change }, [])).not.toBe(before);
|
||||||
|
expect(evidenceVersion(structuredClone(original), [])).toBe(before);
|
||||||
|
});
|
||||||
@@ -307,7 +307,7 @@ describe.skipIf(!pg)("识别词接口", () => {
|
|||||||
const g = (await call("/identify-words/global")).body.data;
|
const g = (await call("/identify-words/global")).body.data;
|
||||||
const saved = await call("/identify-words/global", {
|
const saved = await call("/identify-words/global", {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: { text: `${g.text}Kusuriya no Hitorigoto - 4 <> \\[Baha >> EP-28\n`, expectedVersion: g.version },
|
body: { text: `${g.text}Kusuriya no Hitorigoto - (?=4\\d) <> \\[Baha >> EP-28\n`, expectedVersion: g.version },
|
||||||
});
|
});
|
||||||
const version = saved.body.data.block.version as number;
|
const version = saved.body.data.block.version as number;
|
||||||
const before = h.engine.enqueued.length;
|
const before = h.engine.enqueued.length;
|
||||||
@@ -321,15 +321,19 @@ describe.skipIf(!pg)("识别词接口", () => {
|
|||||||
expect(p.scanned).toBeGreaterThanOrEqual(4);
|
expect(p.scanned).toBeGreaterThanOrEqual(4);
|
||||||
|
|
||||||
// 重新识别后集数按全局识别词偏移(40 → 12,对上第 1 季),命中的行记在解析结果上
|
// 重新识别后集数按全局识别词偏移(40 → 12,对上第 1 季),命中的行记在解析结果上
|
||||||
await resolveRelease(h.rt, { sourceReleaseId: lilith40, force: true, trigger: "review" });
|
const resolved = await resolveRelease(h.rt, { sourceReleaseId: lilith40, force: true, trigger: "review" });
|
||||||
|
expect(resolved.outcome).toBe("auto");
|
||||||
const row = await h.db
|
const row = await h.db
|
||||||
.selectFrom("source_release")
|
.selectFrom("source_release")
|
||||||
.select(["parsed"])
|
.select(["parsed"])
|
||||||
.where("id", "=", lilith40)
|
.where("id", "=", lilith40)
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
expect((row.parsed as { words?: { applied: string[] } }).words?.applied).toEqual([
|
expect((row.parsed as { words?: { applied: string[] } }).words?.applied).toEqual([
|
||||||
"Kusuriya no Hitorigoto - 4 <> \\[Baha >> EP-28",
|
"Kusuriya no Hitorigoto - (?=4\\d) <> \\[Baha >> EP-28",
|
||||||
]);
|
]);
|
||||||
|
expect(await er(lilith40)).toMatchObject({ source: { season: 1, episodes: { start: 12, end: 12 } } });
|
||||||
|
const unchanged = await resolveRelease(h.rt, { sourceReleaseId: lilith40, force: true, trigger: "review" });
|
||||||
|
expect(unchanged).toMatchObject({ outcome: "skipped", reason: "unchanged" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("全局示例:没给标题时从近期资源里找命中的", async () => {
|
it("全局示例:没给标题时从近期资源里找命中的", async () => {
|
||||||
|
|||||||
Reference in new issue
Block a user