MP MetaInfo + TheMovieDbModule name matching vs parse/recall/resolveIdentity, TMDB responses replayed offline; includes the category-alias misbinding regression from the user stack. Co-Authored-By: Claude Opus 5.5 <[email protected]>
66 lines
2.9 KiB
TypeScript
66 lines
2.9 KiB
TypeScript
/**
|
|
* 身份匹配差分回归(ID-2):憨憨真实标题,MP 期望(mp.json)vs 我们(parse → 召回 → resolveIdentity),
|
|
* TMDB 响应离线回放(tmdb-fixtures.json)。说明见 identity-differential.lib.ts 与
|
|
* docs/porting/moviepilot-identity-match.md。
|
|
*/
|
|
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
DATA_DIR,
|
|
fixtureFetch,
|
|
loadData,
|
|
runDifferential,
|
|
runPoisoned,
|
|
summarize,
|
|
tmdbFor,
|
|
} from "./identity-differential.lib.ts";
|
|
|
|
const fixtures = JSON.parse(readFileSync(join(DATA_DIR, "tmdb-fixtures.json"), "utf8")) as Record<string, unknown>;
|
|
const tmdb = () => tmdbFor(fixtureFetch(fixtures));
|
|
|
|
describe("身份匹配差分(MP recognize ↔ resolveIdentity)", async () => {
|
|
const rows = await runDifferential(tmdb());
|
|
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
const { samples, deviations } = loadData();
|
|
|
|
it("样本 ≥ 100 条,覆盖「英文 [中文]」RSS、动漫、续作、通用词、短剧", () => {
|
|
expect(samples.length).toBeGreaterThanOrEqual(100);
|
|
const kinds = new Set(samples.map((s) => s.kind));
|
|
for (const k of ["rss_bracket", "title_subtitle", "short_drama"]) expect(kinds.has(k), k).toBe(true);
|
|
});
|
|
|
|
it("非故意偏离项一致率 ≥ 95%", () => {
|
|
expect(summarize(rows).rate).toBeGreaterThanOrEqual(0.95);
|
|
});
|
|
|
|
it("登记的偏离都对应存在的样本,且 mp_defect 的结果与登记一致", () => {
|
|
for (const [id, d] of Object.entries(deviations)) {
|
|
const r = byId.get(id);
|
|
expect(r, id).toBeDefined();
|
|
if (d.kind === "mp_defect") expect(r!.ours, id).toBe(d.expect ?? null);
|
|
}
|
|
});
|
|
|
|
it("未登记的样本全部与 MP 一致(新增不一致需要归因后登记)", () => {
|
|
const unexplained = rows.filter((r) => r.verdict !== "consistent" && !r.deviation).map((r) => `${r.id} ${r.title}`);
|
|
expect(unexplained).toEqual([]);
|
|
});
|
|
|
|
it("brief 三类:方括号中文名(武神主宰)、续作章节(物怪第三章)、通用词 + 副标题中文名(档案)", () => {
|
|
for (const r of rows.filter((x) => x.title.includes("Martial Master"))) expect(r.ours, r.id).toBe("tv:110181");
|
|
for (const r of rows.filter((x) => x.title.startsWith("Mononoke The Movie Chapter III")))
|
|
expect(r.ours, r.id).toBe("movie:1322562");
|
|
for (const r of rows.filter((x) => x.title.startsWith("Archives S01 2009"))) expect(r.ours, r.id).toBe("tv:201401");
|
|
});
|
|
});
|
|
|
|
describe("真机误识别回归:分类词别名污染的本地作品", async () => {
|
|
const results = await runPoisoned(tmdb());
|
|
it("被误并入《东北有个周东北》的资源重新识别到各自的作品,只有英文名时也不并入", () => {
|
|
expect(results.length).toBeGreaterThanOrEqual(15);
|
|
const bad = results.filter((r) => !r.ok).map((r) => `${r.c.title} → ${r.status} ${r.best}`);
|
|
expect(bad).toEqual([]);
|
|
});
|
|
});
|