feat(domain): W5-C subscription gaps, episode totals, multi-inflight slot, hard conditions and priority ladder

- resolveEpisodeTotal: totals only grow; TMDB regression floored at highest covered episode; manual lock
- computeUnitGaps / seasonProgress / unitsOverlap: missing = units ∩ range − covered − in-flight,
  season wildcard reservation, suspected-missing when total unknown
- transitionWithInflight: slot status aggregates several in-flight plans (Slot × unit reservation)
- hard conditions: excludeSources, hdr sets, codec sets, per-unit size, include/exclude regex
  (whitelist-validated), site scope, minSeeders, freeOnly (site facts via EvaluationContext)
- priority ladder (MP rule-group tiers) → pri 0-100 with per-tier trace; priority-basis upgrades,
  targetPri ceiling, upgrade modes movie/per_unit/full_pack, improvableUnits
- all evaluations carry readable trace via describeViolation

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-27 23:33:46 +08:00
1 parent 89ea68465e
commit d162d464c4
9 files changed
+1023 -42

No files matched your search

+2 -2
View File
@@ -47,7 +47,7 @@ describe("evaluateCandidate 硬条件", () => {
});
it("缺字段 → unknown_field(与不满足区分)", () => {
const r = evaluateCandidate(archival4k, rel("Dune 2021 BluRay"));
expect(r).toEqual({ decision: "unknown_field", missing: ["resolution", "size"] });
expect(r).toMatchObject({ decision: "unknown_field", missing: ["resolution", "size"] });
});
it("unknownPolicy 可按字段配置 reject / accept", () => {
const spec: SlotSpec = { ...archival4k, unknownPolicy: { size: "accept", resolution: "reject" } };
@@ -111,7 +111,7 @@ describe("升级规则", () => {
expect(
evaluateCandidate({ ...archival4k, upgrade: undefined }, rel("Dune.2021.2160p.BluRay 2GB"), cur4kWeb),
).toMatchObject({ decision: "reject" });
expect(evaluateCandidate(archival4k, rel("Dune.2021.2160p.BluRay 2GB"), {})).toEqual({
expect(evaluateCandidate(archival4k, rel("Dune.2021.2160p.BluRay 2GB"), {})).toMatchObject({
decision: "unknown_field",
missing: ["current_asset"],
});