Something went wrong. Try again.
A functional toolkit for authoring resource-leveling algorithms — compose constraint and scoring blocks, enumerate feasible schedules
Something went wrong. Try again.
3.1 kB · 88 lines
TypeScript
at main
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889// Shared schedule profiles (the listener pattern): one derivation per// Schedule, read by every scorer.
import { describe, expect, test } from "bun:test";
import { openUnitCount, scheduleProfiles,} from "../../src/level-core/profiles.ts";import { buildResourceLoad } from "../../src/level-core/weeklyProfile.ts";import type { Schedule } from "../../src/level-core/types.ts";
import { MON_JAN_5, SAT_JAN_10, makeAssignment, makeProject, makeResource, makeTask, runOnce,} from "./fixtures.ts";
async function crewSchedule(): Promise<Schedule> { const t1 = makeTask({ uniqueId: 1, start: MON_JAN_5, finish: SAT_JAN_10 }); const t2 = makeTask({ uniqueId: 2, start: MON_JAN_5, finish: SAT_JAN_10 }); const project = makeProject([t1, t2], { resources: [makeResource(100, "Crew"), makeResource(200, "Electrical")], assignments: [makeAssignment(1, 100), makeAssignment(2, 200)], }); return runOnce( project, [], [ { id: 10, taskUniqueIds: [1] }, { id: 20, taskUniqueIds: [2] }, { id: 30, taskUniqueIds: [] }, ], );}
describe("scheduleProfiles", () => { test("same Schedule object yields the same profiles instance and load map", async () => { const schedule = await crewSchedule(); const first = scheduleProfiles(schedule); const second = scheduleProfiles(schedule); expect(second).toBe(first); // The derivation itself is memoized, not recomputed per read. expect(second.resourceLoad).toBe(first.resourceLoad); expect(second.unitSpans()).toBe(first.unitSpans()); });
test("resourceLoad matches buildResourceLoad", async () => { const schedule = await crewSchedule(); const shared = scheduleProfiles(schedule).resourceLoad; const direct = buildResourceLoad(schedule); const byId = (a: number, b: number) => a - b; expect([...shared.keys()].sort(byId)).toEqual( [...direct.keys()].sort(byId), ); for (const [resourceId, arr] of direct) { expect([...shared.get(resourceId)!]).toEqual([...arr]); } });
test("unitSpans covers scheduled units and omits empty ones", async () => { const schedule = await crewSchedule(); const spans = scheduleProfiles(schedule).unitSpans(); expect(spans.get(10)).toEqual({ startDay: 0, finishDay: 5 }); expect(spans.get(20)).toEqual({ startDay: 0, finishDay: 5 }); expect(spans.has(30)).toBe(false); // no tasks → no span });
test("unitSpans with a discipline only counts that resource's tasks", async () => { const schedule = await crewSchedule(); const spans = scheduleProfiles(schedule).unitSpans(200); expect(spans.has(10)).toBe(false); // unit 10's task is on resource 100 expect(spans.get(20)).toEqual({ startDay: 0, finishDay: 5 }); });
test("openUnitCount counts open units per working day, deduplicating ids", async () => { const schedule = await crewSchedule(); const counts = openUnitCount(schedule, [10, 20, 10]); // Both units open on working days 0–4; weekend days (5, 6) are zero. expect(counts[0]).toBe(2); expect(counts[4]).toBe(2); expect(counts[5]).toBe(0); });});