feat(metadata): overview falls back to Chinese variants only (MD-2)
- adapters: overviewLanguages() limits overview/tagline chain to Chinese variants (zh-CN→zh-SG→zh-TW→zh-HK); titles keep full chain; season supplement fetch skips languages useless for overview - setting metadata.overviewNonChineseFallback (default off) + UI switch - media detail shows "暂无简介" for empty entity/episode overview - scripts/overview-recompute.ts: recompute stored non-Chinese overviews from kv_cache only (no TMDB requests), dry-run by default, --apply - tests: chain unit tests, Heart Signal (103147) fixture, server recompute test, MP differential for episode overview - runbook + CHANGELOG Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
081db4651c
commit
f9901b514b
22 files changed
+887
-38
No files matched your search
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 每集简介差分(MD-2):MP `TmdbApi.get_tv_season_detail`(e6820a8,app/modules/themoviedb/tmdbapi.py)只按配置语言
|
||||
* (TMDB_LOCALE = zh)请求季详情,episodes[].overview 原样使用,为空就为空;本项目 `fetchRichSeason` 按语言链补拉。
|
||||
* 响应按《心动的信号》(TMDB tv/103147)各语言季详情的真实形态裁剪(每期标题有 zh-CN,简介只有 en-US)。
|
||||
* 期望:简介与 MP 一致(中文变体都没有 → 空);标题允许按链回退(登记偏离 intentional:MD-1 占位集名按链补标题)。
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { fetchRichSeason } from "../../src/tmdb/index.ts";
|
||||
|
||||
type Ep = { episode_number: number; air_date: string; name: string; overview: string };
|
||||
const season = (episodes: Ep[]) => ({
|
||||
season_number: 1,
|
||||
name: "第 1 季",
|
||||
overview: "",
|
||||
air_date: "2018-08-01",
|
||||
episodes,
|
||||
});
|
||||
|
||||
/** 各语言的 /tv/103147/season/1 响应(裁剪) */
|
||||
const RECORDED: Record<string, ReturnType<typeof season>> = {
|
||||
"zh-CN": season([
|
||||
{ episode_number: 1, air_date: "2018-08-01", name: "心动不如行动,大胆亮出底牌!", overview: "" },
|
||||
{ episode_number: 2, air_date: "2018-08-08", name: "第 2 集", overview: "" },
|
||||
]),
|
||||
"zh-SG": season([
|
||||
{ episode_number: 1, air_date: "2018-08-01", name: "第 1 集", overview: "" },
|
||||
{ episode_number: 2, air_date: "2018-08-08", name: "第 2 集", overview: "" },
|
||||
]),
|
||||
"zh-TW": season([
|
||||
{ episode_number: 1, air_date: "2018-08-01", name: "第 1 集", overview: "" },
|
||||
{ episode_number: 2, air_date: "2018-08-08", name: "第 2 集", overview: "" },
|
||||
]),
|
||||
"zh-HK": season([
|
||||
{ episode_number: 1, air_date: "2018-08-01", name: "第 1 集", overview: "" },
|
||||
{ episode_number: 2, air_date: "2018-08-08", name: "第 2 集", overview: "" },
|
||||
]),
|
||||
"en-US": season([
|
||||
{
|
||||
episode_number: 1,
|
||||
air_date: "2018-08-01",
|
||||
name: "Episode 1",
|
||||
overview: "If Your Heart Skips a Beat, Play Your Cards Openly!",
|
||||
},
|
||||
{ episode_number: 2, air_date: "2018-08-08", name: "Who Is the One?", overview: "Who Is the One?" },
|
||||
]),
|
||||
};
|
||||
|
||||
const client = {
|
||||
getJson: <T>(_path: string, params: Record<string, string | undefined> = {}) =>
|
||||
Promise.resolve(RECORDED[params.language ?? "en-US"] as T),
|
||||
};
|
||||
|
||||
/** MP 侧:只取配置语言(zh-CN)的季详情,集名 / 简介原样(空串 = 没有) */
|
||||
function mpEpisodes(lang = "zh-CN") {
|
||||
return RECORDED[lang]!.episodes.map((e) => ({ ep: e.episode_number, name: e.name, overview: e.overview || null }));
|
||||
}
|
||||
|
||||
const CHAIN = ["zh-CN", "zh-SG", "zh-TW", "zh-HK", "en-US"];
|
||||
const DEVIATIONS: Record<string, { kind: "intentional"; note: string }> = {
|
||||
"E2.name": { kind: "intentional", note: "MD-1:zh-CN 是占位名「第 2 集」时按链补标题(可到 en-US)" },
|
||||
};
|
||||
|
||||
describe("每集简介差分(MP get_tv_season_detail ↔ fetchRichSeason)", () => {
|
||||
it("简介与 MP 一致:中文变体都没有时为空,不回退英文;标题偏离已登记", async () => {
|
||||
const ours = await fetchRichSeason(client, 103147, 1, CHAIN, { now: new Date("2026-01-01") });
|
||||
const mismatches: string[] = [];
|
||||
for (const mp of mpEpisodes()) {
|
||||
const e = ours.episodes.find((x) => x.episodeNumber === mp.ep)!;
|
||||
if (e.overview !== mp.overview) mismatches.push(`E${mp.ep}.overview`);
|
||||
if (e.name !== mp.name) mismatches.push(`E${mp.ep}.name`);
|
||||
}
|
||||
expect(mismatches.filter((m) => !DEVIATIONS[m])).toEqual([]);
|
||||
expect(Object.keys(DEVIATIONS).every((k) => mismatches.includes(k))).toBe(true);
|
||||
expect(ours.episodes.map((e) => e.overview)).toEqual([null, null]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user