/** "每日新资源"拆成的三种模式(REQ §6.3) */ export type AcquisitionMode = "new_title" | "catch_up" | "upgrade"; export interface ModeInput { /** 本地实体;null = 库里没有这部作品 */ entity: { tracked: boolean; kind: "movie" | "tv" } | null; /** 候选覆盖的 content_unit(剧集/综艺);电影留空 */ releaseUnits: readonly string[]; /** Slot 中已满足的 content_unit */ satisfiedUnits: readonly string[]; /** 电影:该 Slot 是否已满足 */ slotSatisfied: boolean; /** 作品发行/首播日期 YYYY-MM-DD(不是站点上传日期) */ firstReleaseDate?: string; /** 当前时间(ISO),由调用方注入 */ now: string; /** 新作品窗口(天) */ newTitleWindowDays: number; /** evaluateCandidate 对当前资产给出了 upgrade */ upgradeCandidate?: boolean; } export type ModeDecision = { mode: AcquisitionMode; reason: string } | { mode: null; reason: string }; const DAY_MS = 86_400_000; /** * 判定一条候选属于哪种收录模式。 * - 库里没有:只有发行/首播在窗口内才是新作品;站点今天上传旧片不算新片 * - 已有剧集:候选覆盖未满足的单元 → 追更补缺;都已满足且可升级 → 升级 * - 已有电影:Slot 缺口或更优版本 → 升级(独立于上映日期) * - 只登记(未追踪)的作品不因新规则隐式变成自动下载 */ export function determineMode(input: ModeInput): ModeDecision { const { entity } = input; if (!entity) { if (!input.firstReleaseDate) return { mode: null, reason: "release_date_unknown" }; const released = Date.parse(`${input.firstReleaseDate}T00:00:00Z`); const now = Date.parse(input.now); if (Number.isNaN(released) || Number.isNaN(now)) return { mode: null, reason: "release_date_unknown" }; const ageDays = (now - released) / DAY_MS; if (ageDays < -1) return { mode: null, reason: "not_yet_released" }; if (ageDays <= input.newTitleWindowDays) return { mode: "new_title", reason: "released_within_window" }; return { mode: null, reason: "not_new_release" }; } if (!entity.tracked) return { mode: null, reason: "not_tracked" }; if (entity.kind === "tv") { const satisfied = new Set(input.satisfiedUnits); const gaps = input.releaseUnits.filter((u) => !satisfied.has(u)); if (gaps.length > 0) return { mode: "catch_up", reason: `covers_missing_units:${gaps.length}` }; if (input.releaseUnits.length === 0) return { mode: null, reason: "no_units_resolved" }; return input.upgradeCandidate ? { mode: "upgrade", reason: "better_version" } : { mode: null, reason: "already_satisfied" }; } if (!input.slotSatisfied) return { mode: "upgrade", reason: "slot_gap" }; return input.upgradeCandidate ? { mode: "upgrade", reason: "better_version" } : { mode: null, reason: "already_satisfied" }; }