feat(identify-words): 识别词——全局识别词(MP 语法)+ 作品识别词(季集换算),规则页「识别词」页签
- 新表 identify_words(0094):全局一块 + 每部作品一块原文,带版本与审计;episode_mapping_rule 转成文本行后删表
- 全局识别词移植 MP WordsMatcher(屏蔽 / 替换 / 集数偏移 / 替换+偏移 / {[tmdbid=…]}),所有按标题 / 文件名识别的地方解析前套用,命中的行记在解析结果上;保存后后台按改动行重新识别
- 作品识别词:S02 >> S01 EP+24 / S01E13 >> S00E01 / S01 @组 >> 忽略,解析器 + 打印器 + 编译;发布组繁简视为同一个
- 去掉规则自动停用:算出来对不上季表只待确认,要停用就注释掉那一行
- 集数待确认的采用 / 忽略与智能体 apply_rule 改为往作品识别词追加一行(带来历注释)
- 编辑器可视化 / 文本两种模式改同一份原文,保存前试算;作品页「集数映射」用同一个编辑器
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
12f8fc59c3
commit
63e94b308e
90 files changed
+6366
-2131
No files matched your search
@@ -1,30 +1,23 @@
|
||||
import type { EpisodeMappingPreview, EpisodeMappingRuleRow } from "@umt/contracts";
|
||||
import type { EpisodeMappingPreview } from "@umt/contracts";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { EXCLUDE_REASON } from "../organize-wizard/model.ts";
|
||||
import {
|
||||
absoluteGroups,
|
||||
agentRuleInfo,
|
||||
canonicalText,
|
||||
disabledReasonText,
|
||||
draftFromSearch,
|
||||
draftOfRule,
|
||||
EMPTY_DRAFT,
|
||||
groupMethodsText,
|
||||
lineFromSearch,
|
||||
METHOD_LABEL,
|
||||
mappingHref,
|
||||
methodText,
|
||||
parseTable,
|
||||
reresolveToast,
|
||||
ruleText,
|
||||
sourceText,
|
||||
specOfDraft,
|
||||
} from "./model.ts";
|
||||
|
||||
describe("集数映射文案", () => {
|
||||
it("方式名称给人话,覆盖全部方式", () => {
|
||||
expect(METHOD_LABEL).toEqual({
|
||||
raw: "原号",
|
||||
rule: "按规则",
|
||||
rule: "按作品识别词",
|
||||
absolute_in_season: "季内绝对集号",
|
||||
absolute_cross_season: "跨季绝对集号",
|
||||
air_date: "按播出日期",
|
||||
@@ -33,7 +26,7 @@ describe("集数映射文案", () => {
|
||||
unresolved: "待确认",
|
||||
skipped: "不适用",
|
||||
});
|
||||
expect(methodText({ method: "rule", special: "ignore" })).toBe("按规则:忽略");
|
||||
expect(methodText({ method: "rule", special: "ignore" })).toBe("按作品识别词:忽略");
|
||||
expect(methodText(null)).toBe("未解析");
|
||||
});
|
||||
|
||||
@@ -52,86 +45,16 @@ describe("集数映射文案", () => {
|
||||
expect(EXCLUDE_REASON.episode_unresolved).toBe("集数待确认");
|
||||
expect(EXCLUDE_REASON.special_extra).toBeTruthy();
|
||||
});
|
||||
|
||||
it("停用原因:手动 / 规则失效", () => {
|
||||
const rule = { status: "disabled", disabledReason: JSON.stringify({ manual: true }) } as EpisodeMappingRuleRow;
|
||||
expect(disabledReasonText(rule)).toBe("手动停用");
|
||||
expect(
|
||||
disabledReasonText({
|
||||
...rule,
|
||||
disabledReason: JSON.stringify({ source: { season: 2, episodes: { start: 30, end: 30 } }, evidence: [] }),
|
||||
}),
|
||||
).toBe("规则算出的集数对不上季表(来源 S02E30),已自动停用");
|
||||
expect(disabledReasonText({ ...rule, status: "active" })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("对照表与草稿", () => {
|
||||
it("对照表文本解析与回写", () => {
|
||||
const t = parseTable("26=S02E01\n27-28 = s2e2\n");
|
||||
expect(t.error).toBeNull();
|
||||
expect(t.entries).toEqual([
|
||||
{ from: { start: 26, end: 26 }, to: { season: 2, episode: 1 } },
|
||||
{ from: { start: 27, end: 28 }, to: { season: 2, episode: 2 } },
|
||||
]);
|
||||
expect(ruleText({ kind: "table", tableEntries: t.entries })).toBe("26=S02E01 等 2 行");
|
||||
expect(parseTable("26->").error).toContain("第 1 行");
|
||||
expect(parseTable("28-27=S02E01").error).toContain("写反");
|
||||
});
|
||||
|
||||
it("草稿 → 规则内容:不完整时给错误,不发预览", () => {
|
||||
expect(specOfDraft({ ...EMPTY_DRAFT, expression: "" }).error).toContain("偏移表达式");
|
||||
expect(specOfDraft({ ...EMPTY_DRAFT, season: "x", expression: "EP-12" }).error).toContain("来源季");
|
||||
expect(specOfDraft({ ...EMPTY_DRAFT, season: "2", releaseGroup: " ANi ", expression: "EP-12" }).spec).toEqual({
|
||||
season: 2,
|
||||
releaseGroup: "ANi",
|
||||
kind: "expression",
|
||||
expression: "EP-12",
|
||||
targetSeason: null,
|
||||
});
|
||||
expect(specOfDraft({ ...EMPTY_DRAFT, kind: "special", special: "extra" }).spec).toMatchObject({
|
||||
kind: "special",
|
||||
special: "extra",
|
||||
season: null,
|
||||
releaseGroup: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("规则 → 草稿 → 规则内容 保持一致", () => {
|
||||
const row = {
|
||||
season: 1,
|
||||
releaseGroup: "Lilith-Raws",
|
||||
kind: "expression",
|
||||
expression: "EP-30",
|
||||
targetSeason: 2,
|
||||
tableEntries: null,
|
||||
special: null,
|
||||
} as EpisodeMappingRuleRow;
|
||||
expect(specOfDraft(draftOfRule(row)).spec).toEqual({
|
||||
season: 1,
|
||||
releaseGroup: "Lilith-Raws",
|
||||
kind: "expression",
|
||||
expression: "EP-30",
|
||||
targetSeason: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it("待处理「修改」的跳转地址能读回预填草稿", () => {
|
||||
const href = mappingHref("/console/library/x", {
|
||||
season: 1,
|
||||
releaseGroup: "ANi",
|
||||
expression: "EP-12",
|
||||
targetSeason: 2,
|
||||
});
|
||||
expect(href).toBe("/console/library/x?mapSeason=1&mapGroup=ANi&mapExpr=EP-12&mapTarget=2#episode-mapping");
|
||||
describe("作品页地址", () => {
|
||||
it("集数待确认「修改」带上要追加的一行,作品页能读回", () => {
|
||||
const href = mappingHref("/console/library/x", "S01 @ANi >> S02 EP-12");
|
||||
expect(href).toBe("/console/library/x?mapLine=S01+%40ANi+%3E%3E+S02+EP-12#episode-mapping");
|
||||
const sp = new URLSearchParams(href.slice(href.indexOf("?") + 1, href.indexOf("#")));
|
||||
expect(draftFromSearch(sp)).toMatchObject({
|
||||
season: "1",
|
||||
releaseGroup: "ANi",
|
||||
expression: "EP-12",
|
||||
targetSeason: "2",
|
||||
});
|
||||
expect(draftFromSearch(new URLSearchParams())).toBeNull();
|
||||
expect(lineFromSearch(sp)).toBe("S01 @ANi >> S02 EP-12");
|
||||
expect(lineFromSearch(new URLSearchParams())).toBeNull();
|
||||
expect(mappingHref("/console/library/x")).toBe("/console/library/x#episode-mapping");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -161,24 +84,11 @@ describe("按发布组统计", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("应用后的提示", () => {
|
||||
describe("保存后的提示", () => {
|
||||
it("只列非零项", () => {
|
||||
expect(reresolveToast({ releases: 12, changed: 2, resolved: 2 })).toBe(
|
||||
"已应用:重算 12 条资源,2 条有变化,2 条待确认已解决",
|
||||
"已保存:重算 12 条资源,2 条有变化,2 条待确认已解决",
|
||||
);
|
||||
expect(reresolveToast({ releases: 3, changed: 0, resolved: 0 }, "已停用")).toBe("已停用:重算 3 条资源");
|
||||
});
|
||||
});
|
||||
|
||||
describe("智能体写的规则", () => {
|
||||
it("标出智能体来源与证据等级,带写规则时的理由;其它来源不标", () => {
|
||||
const rule = (over: Partial<EpisodeMappingRuleRow>) =>
|
||||
({ createdByType: "agent", evidenceLevel: "multi_evidence", evidence: {}, ...over }) as EpisodeMappingRuleRow;
|
||||
expect(agentRuleInfo(rule({ evidence: { reasons: ["发布时间对上播出日期", 3] } }))).toEqual({
|
||||
label: "智能体 · 多证据",
|
||||
reasons: ["发布时间对上播出日期"],
|
||||
});
|
||||
expect(agentRuleInfo(rule({ evidenceLevel: "needs_review" }))?.label).toBe("智能体 · 待复核");
|
||||
expect(agentRuleInfo(rule({ createdByType: "user", evidenceLevel: "manual" }))).toBeNull();
|
||||
expect(reresolveToast({ releases: 3, changed: 0, resolved: 0 }, "已写入")).toBe("已写入:重算 3 条资源");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user