From 70935f4c173e53c3d1daf7b10a7480ce34aecb3a Mon Sep 17 00:00:00 2001 From: root Date: Sat, 3 Oct 2026 20:59:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B4=E7=90=86=EF=BC=9A=E8=A2=AB=E6=8E=92?= =?UTF-8?q?=E9=99=A4=E7=9A=84=E5=BE=85=E7=A1=AE=E8=AE=A4=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E4=B8=8D=E7=AE=97=E6=88=90=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 季集待确认 / 没有季目录 / 命名失败而被排除的视频(excluded:episode_unresolved / no_target_dir / naming_failed)让整理记录从 succeeded 降为 partial;样例、过小、已在库等预期跳过不受影响。 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- packages/mapping/src/organize.ts | 36 +++++++++++++++++++++------ packages/mapping/test/convert.test.ts | 10 ++++++++ 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/packages/mapping/src/organize.ts b/packages/mapping/src/organize.ts index b4232de0..8a3a99e8 100644 --- a/packages/mapping/src/organize.ts +++ b/packages/mapping/src/organize.ts @@ -142,16 +142,38 @@ export function organizePlanToFileResults( return [...planned, ...excluded]; } -/** 逐文件状态 → 整理记录状态(全部完成后调用;仍有 queued/running 时为 running) */ -export function organizationStatusFromFiles(statuses: readonly FileResultStatus[]): OrganizationStatus { +/** + * 被排除但"本该入库"的视频:季集没定下来、没有对应季目录、命名缺字段。文件留在原处没人管, + * 不能算整理成功(Move 成功后会移除下载任务)。样例、过小、已在库等预期跳过不在此列。 + */ +export const UNRESOLVED_EXCLUSIONS = ["episode_unresolved", "no_target_dir", "naming_failed"] as const; +const UNRESOLVED_SKIP = new Set(UNRESOLVED_EXCLUSIONS.map((r) => `excluded:${r}`)); + +/** 文件结果的 skip_reason 是否属于"本该入库却被排除" */ +export const isUnresolvedExclusion = (skipReason: string | null | undefined): boolean => + !!skipReason && UNRESOLVED_SKIP.has(skipReason); + +/** + * 逐文件状态 → 整理记录状态(全部完成后调用;仍有 queued/running 时为 running)。 + * unresolved:被排除的待确认视频数(isUnresolvedExclusion),有则成功降为 partial。 + */ +export function organizationStatusFromFiles( + statuses: readonly FileResultStatus[], + opts: { unresolved?: number } = {}, +): OrganizationStatus { if (statuses.some((s) => s === "queued" || s === "running")) return "running"; const done = statuses.filter((s) => s !== "skipped"); - // 全部跳过(或没有文件)视为成功:没有需要做的事 - if (done.length === 0) return "succeeded"; const ok = done.filter((s) => s === "succeeded").length; - if (ok === done.length) return "succeeded"; - if (done.every((s) => s === "cancelled")) return "cancelled"; - return ok > 0 ? "partial" : "failed"; + // 全部跳过(或没有文件)视为成功:没有需要做的事 + const base: OrganizationStatus = + done.length === 0 || ok === done.length + ? "succeeded" + : done.every((s) => s === "cancelled") + ? "cancelled" + : ok > 0 + ? "partial" + : "failed"; + return base === "succeeded" && (opts.unresolved ?? 0) > 0 ? "partial" : base; } /** 整理计划 → organize.plan 能力输出(planId / payloadHash 由保存计划的一方给出) */ diff --git a/packages/mapping/test/convert.test.ts b/packages/mapping/test/convert.test.ts index 4b988fd3..52fce4f5 100644 --- a/packages/mapping/test/convert.test.ts +++ b/packages/mapping/test/convert.test.ts @@ -177,6 +177,16 @@ describe("整理计划 → 整理记录", () => { expect(M.organizationStatusFromFiles(["succeeded", "running"])).toBe("running"); expect(M.organizationStatusFromFiles(["cancelled"])).toBe("cancelled"); expect(M.organizationStatusFromFiles([])).toBe("succeeded"); + // 被排除的待确认视频(季集 / 季目录 / 命名)让成功降为 partial;预期跳过不影响 + expect(M.organizationStatusFromFiles(["succeeded", "skipped"], { unresolved: 1 })).toBe("partial"); + expect(M.organizationStatusFromFiles(["succeeded", "failed"], { unresolved: 1 })).toBe("partial"); + expect(M.organizationStatusFromFiles(["failed", "skipped"], { unresolved: 1 })).toBe("failed"); + expect(M.isUnresolvedExclusion("excluded:episode_unresolved")).toBe(true); + expect(M.isUnresolvedExclusion("excluded:no_target_dir")).toBe(true); + expect(M.isUnresolvedExclusion("excluded:naming_failed")).toBe(true); + expect(M.isUnresolvedExclusion("excluded:too_small")).toBe(false); + expect(M.isUnresolvedExclusion("already_in_library")).toBe(false); + expect(M.isUnresolvedExclusion(null)).toBe(false); }); });