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.
4.8 kB · 182 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183// Shared project fixtures for level-core / level-blocks tests: a Mon–Fri// calendar, task/project builders, and run helpers mirroring the local// builders in serialSGS.test.ts (kept here so new suites stop copying them).
import { expect } from "bun:test";
import { resolveCalendar } from "../../src/level-core/resolveCalendar.ts";import { ScheduleStreamImpl } from "../../src/level-core/scheduleStream.ts";import { serialSGS } from "../../src/level-core/search/serialSGS.ts";import { ResourceType } from "../../src/model/types.ts";import type { Constraint, Failure, Schedule, WorkUnit,} from "../../src/level-core/types.ts";import type { Calendar } from "../../src/schema/calendar.ts";import type { ProjectFile } from "../../src/schema/project.ts";import type { Relation } from "../../src/schema/relation.ts";import type { Task } from "../../src/schema/task.ts";
export function monFriCalendar(uniqueId = 1): Calendar { return { uniqueId, name: "Standard", weekDays: [ { dayType: 1, working: false, workingTimes: [] }, { dayType: 2, working: true, workingTimes: [] }, { dayType: 3, working: true, workingTimes: [] }, { dayType: 4, working: true, workingTimes: [] }, { dayType: 5, working: true, workingTimes: [] }, { dayType: 6, working: true, workingTimes: [] }, { dayType: 7, working: false, workingTimes: [] }, ], exceptions: [], };}
export function makeTask(args: { uniqueId: number; start: Date; finish: Date; milestone?: boolean; predecessors?: Relation[];}): Task { return { id: null, uniqueId: args.uniqueId, name: `Task ${String(args.uniqueId)}`, wbs: null, outlineLevel: 0, start: args.start, finish: args.finish, duration: null, percentComplete: null, summary: false, milestone: args.milestone ?? false, critical: null, notes: null, priority: null, cost: null, work: null, actualStart: null, actualFinish: null, baselineStart: null, baselineFinish: null, baselineDuration: null, actualWork: null, constraintType: null, freeSlack: null, totalSlack: null, earlyStart: null, earlyFinish: null, lateStart: null, lateFinish: null, levelingDelay: null, deadline: null, splits: null, predecessors: args.predecessors ?? [], };}
export function makeProject( tasks: Task[], options: { calendars?: Calendar[]; resources?: ProjectFile["resources"]; assignments?: ProjectFile["assignments"]; } = {},): ProjectFile { const calendars = options.calendars ?? [monFriCalendar()]; return { properties: { title: null, author: null, startDate: null, finishDate: null, statusDate: null, defaultCalendarUniqueId: calendars[0]?.uniqueId ?? null, minutesPerDay: 480, minutesPerWeek: 2400, daysPerMonth: 20, saveVersion: null, }, tasks, resources: options.resources ?? [], assignments: options.assignments ?? [], calendars, };}
export function makeResource( uniqueId: number, name = `Resource ${String(uniqueId)}`,) { return { id: uniqueId, uniqueId, name, type: ResourceType.Work, email: null, group: null, maxUnits: 1, cost: null, work: null, resourcePool: null, };}
export function makeAssignment( taskUniqueId: number, resourceUniqueId: number, units = 1,) { return { taskUniqueId, resourceUniqueId, units, work: null, start: null, finish: null, actualWork: null, remainingWork: null, };}
export const MON_JAN_5 = new Date(2026, 0, 5);export const SAT_JAN_10 = new Date(2026, 0, 10);
/** Run serialSGS expecting exactly one feasible schedule. */export async function runOnce( project: ProjectFile, constraints: readonly Constraint[], workUnits?: readonly WorkUnit[],): Promise<Schedule> { const resolved = resolveCalendar(project, { workUnits }); const stream = new ScheduleStreamImpl({ [Symbol.asyncIterator]: () => serialSGS.run(resolved, constraints), }); const collected = await stream.collect(); expect(collected).toHaveLength(1); return collected[0]!;}
/** Run serialSGS expecting exhaustion — returns the Failure. */export async function runExpectingFailure( project: ProjectFile, constraints: readonly Constraint[], workUnits?: readonly WorkUnit[],): Promise<Failure> { const resolved = resolveCalendar(project, { workUnits }); const gen = serialSGS.run(resolved, constraints); for (;;) { const result = await gen.next(); if (result.done) { const failure = result.value; if (!failure) throw new Error("expected the search to return a Failure"); expect(failure.kind).toBe("failure"); return failure; } }}