6bfc013e35
Turns "23 shots to go" into "you are not going to make it". One card per episode from EpisodeDueDate (or the project's own date on a standard show): days left, the episode's shots by where they actually are, and the only comparison that matters - shots per day still needed against shots per day the studio managed over the last week. A shot counts as delivered when it appears in a DeliveryItem inside a DELIVERED EXR_DELIVERY package. Shot.status is deliberately not the measure: it says where the work is, not whether the client has the frames, and a MOV_REVIEW package ships nothing. The pace arithmetic is a pure function so the sentence a producer acts on is testable: a rate that only just lands on the due date reports as "no slack" rather than fine, and a week with no deliveries reports as stalled instead of projecting infinity. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
88 lines
3.4 KiB
TypeScript
88 lines
3.4 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { compareVersions, projectPace } from "./ops";
|
|
|
|
/**
|
|
* The countdown's whole job is to say "you are not going to make it" early
|
|
* enough to do something about it, so the arithmetic behind that sentence is
|
|
* worth pinning down.
|
|
*/
|
|
describe("projectPace", () => {
|
|
it("calls it behind when the trailing rate overruns the date", () => {
|
|
// 29 shots to go, a fortnight left, 8 delivered last week: 1.14/day
|
|
// against the 2.07/day needed, projecting 25.4 days of work — 11.4 days
|
|
// past the date, rounded up to a whole day late.
|
|
const pace = projectPace(29, 14, 8);
|
|
expect(pace.verdict).toBe("behind");
|
|
expect(pace.requiredPerDay).toBeCloseTo(2.07, 2);
|
|
expect(pace.actualPerDay).toBeCloseTo(1.14, 2);
|
|
expect(pace.daysBehind).toBe(12);
|
|
});
|
|
|
|
it("calls a rate that only just lands tight rather than fine", () => {
|
|
// Finishing on the last available day leaves no room for a QC failure.
|
|
expect(projectPace(10, 10, 7).verdict).toBe("tight");
|
|
});
|
|
|
|
it("is on track with real slack", () => {
|
|
const pace = projectPace(5, 30, 7);
|
|
expect(pace.verdict).toBe("ahead");
|
|
expect(pace.daysBehind).toBeNull();
|
|
});
|
|
|
|
it("separates a stall from being merely slow", () => {
|
|
// Nothing shipped in a week is not a slow rate, it is no rate at all —
|
|
// dividing by it would project infinity.
|
|
const pace = projectPace(12, 20, 0);
|
|
expect(pace.verdict).toBe("stalled");
|
|
expect(pace.daysBehind).toBeNull();
|
|
});
|
|
|
|
it("reports how far past the date an unfinished episode is", () => {
|
|
const pace = projectPace(3, -5, 4);
|
|
expect(pace.verdict).toBe("overdue");
|
|
expect(pace.daysBehind).toBe(5);
|
|
});
|
|
|
|
it("is complete once nothing is left, whatever the rate", () => {
|
|
expect(projectPace(0, 3, 0).verdict).toBe("complete");
|
|
expect(projectPace(0, 3, 0).requiredPerDay).toBe(0);
|
|
});
|
|
|
|
it("does not divide by zero on the due date itself", () => {
|
|
// daysLeft 0 means "today": the work still has to be quoted per day.
|
|
expect(projectPace(4, 0, 7).requiredPerDay).toBe(4);
|
|
expect(Number.isFinite(projectPace(4, 0, 7).requiredPerDay)).toBe(true);
|
|
});
|
|
});
|
|
|
|
/**
|
|
* The farm strip flags machines running an older worker than the newest one
|
|
* out there, which is only as reliable as this comparison: a plain string
|
|
* compare ranks "1.10.0" below "1.9.0" and would quietly stop reporting drift
|
|
* on the day the minor version reaches double digits.
|
|
*/
|
|
describe("compareVersions", () => {
|
|
it("orders by numeric component, not lexically", () => {
|
|
expect(compareVersions("1.10.0", "1.9.0")).toBeGreaterThan(0);
|
|
expect(compareVersions("1.9.0", "1.10.0")).toBeLessThan(0);
|
|
});
|
|
|
|
it("ignores the build stamp publish.ps1 appends", () => {
|
|
// The exe reports "1.6.1+fe420b9"; two machines on the same release built
|
|
// from different commits are not drifting.
|
|
expect(compareVersions("1.6.1+fe420b9", "1.6.1")).toBe(0);
|
|
expect(compareVersions("1.6.1+fe420b9", "1.6.0+2a88880")).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("treats a missing component as zero", () => {
|
|
expect(compareVersions("1.6", "1.6.0")).toBe(0);
|
|
expect(compareVersions("1.6", "1.6.1")).toBeLessThan(0);
|
|
});
|
|
|
|
it("is transitive enough to pick a maximum", () => {
|
|
const versions = ["1.5.2", "1.6.1", "1.6.0", "1.10.0"];
|
|
const latest = versions.reduce((a, b) => (compareVersions(a, b) >= 0 ? a : b));
|
|
expect(latest).toBe("1.10.0");
|
|
});
|
|
});
|