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 · 75 lines
TypeScript
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576// PhaseEarliness — Scoring block (companion to PhaseLateness, generalized// from an earlier early-start term).//// The mirror of PhaseLateness on the start side. Each phase has a target// earliest day (its planned window start). A phase's realized start is the// EARLIEST start across its tasks; the penalty is the working days that// start precedes the target, weighted by the phase's completion-priority// `weight` and summed over phases://// penalty = sum_p weight_p × workingDays(phaseStart_p, target_p) [start < target]//// Why a phase should not start too early: pulling a phase in ahead of plan// costs factory-build / customer-readiness pressure. The original formulation// charged this term in a lower objective tier than lateness — earliness// trades off against cost, whereas lateness is the hard priority — so this is// a separate block, not a mode of PhaseLateness: the two anchor on different// target days (window start vs window end) and land in different objective// tiers, and a single phase carries both at once. The per-day scale// (`earlyStartPerDay` in the original formulation) comes from the run// config's scorer weight.//// MiniZinc compile contract: harness supplies `start_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 PhaseEarlinessInputSchema = z.object({ phases: z.array(PhaseWindowSchema),});
export type PhaseEarlinessInput = z.infer<typeof PhaseEarlinessInputSchema>;
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 PhaseEarlinessBlock: ScoringBlock<PhaseEarlinessInput> = { id: "PhaseEarliness", schema: { input: PhaseEarlinessInputSchema, output: ScorerOutputSchema }, apply: (input): Scorer => { const { phases } = PhaseEarlinessInputSchema.parse(input); return { name: "PhaseEarliness", direction: "min", reads: { taskUniqueIds: phaseTaskIds(phases) }, score: (schedule: Schedule): number => phaseDeviation(schedule, phases, "start"), }; }, toMiniZinc: (input): MiniZincFragment => { const { phases } = PhaseEarlinessInputSchema.parse(input); return { text: phaseDeviationMiniZinc("phase_earliness", phases, "start") }; }, doc: { nl: "Early-start penalty: for each phase, the working days its earliest task starts before the phase's target day, weighted by the phase's completion-priority weight and summed. Starting on or after target costs nothing.", pseudocode: "sum_p weight_p * workingDays(min(start(t) for t in phase_p), target_p) when that start is early", },};