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.2 kB · 73 lines
TypeScript
at main
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374// PhaseLateness — Scoring block (completion-priority, generalized from an// earlier phase-lateness term).//// A schedule is carved into phases (groups of tasks). Each phase has a// target completion day (its deadline / window end). A phase's realized// finish is the LATEST finish across its tasks; the penalty is the working// days that finish overruns the target, weighted by the phase's// completion-priority `weight` and summed over phases://// penalty = sum_p weight_p × workingDays(target_p, phaseFinish_p) [finish > target]//// This is the hard-priority "finish the phases on time" pressure. Earlier or// more-critical phases get a heavier `weight`, so their slippage dominates —// the original formulation used weights 3/2/1 across three phases and ranked// this term first in a lexicographic objective. The per-day scale// (`phaseLatenessPerDay` in the original formulation) is supplied by the run// config's scorer weight, not baked in here. `weight` here is a static// per-phase priority, NOT a dynamic fraction of tasks complete.//// MiniZinc compile contract: harness supplies `finish_day[t]` and the// working-day set `DAYS`.
import { z } from "zod";
import type { Schedule, Scorer } from "../level-core/types.ts";
import { PhaseWindowSchema, phaseDeviation, phaseDeviationMiniZinc, phaseTaskIds,} from "./phaseScoring.ts";import type { MiniZincFragment, ScoringBlock } from "./types.ts";
export const PhaseLatenessInputSchema = z.object({ phases: z.array(PhaseWindowSchema),});
export type PhaseLatenessInput = z.infer<typeof PhaseLatenessInputSchema>;
const ScorerOutputSchema: z.ZodType<Scorer> = z.custom<Scorer>( (val): val is Scorer => typeof val === "object" && val !== null && typeof (val as Scorer).name === "string" && ((val as Scorer).direction === "min" || (val as Scorer).direction === "max") && typeof (val as Scorer).score === "function",);
export const PhaseLatenessBlock: ScoringBlock<PhaseLatenessInput> = { id: "PhaseLateness", schema: { input: PhaseLatenessInputSchema, output: ScorerOutputSchema }, apply: (input): Scorer => { const { phases } = PhaseLatenessInputSchema.parse(input); return { name: "PhaseLateness", direction: "min", reads: { taskUniqueIds: phaseTaskIds(phases) }, score: (schedule: Schedule): number => phaseDeviation(schedule, phases, "finish"), }; }, toMiniZinc: (input): MiniZincFragment => { const { phases } = PhaseLatenessInputSchema.parse(input); return { text: phaseDeviationMiniZinc("phase_lateness", phases, "finish") }; }, doc: { nl: "Completion-priority penalty: for each phase, the working days its latest task finishes past the phase's target day, weighted by the phase's completion-priority weight and summed. A finish boundary at or before the target costs nothing — note the boundary is the exclusive finishDay index (finishDay d+1 for last work on day d), not the calendar date of that last work.", pseudocode: "sum_p weight_p * workingDays(target_p, max(finish(t) for t in phase_p)) when that finish is late", },};