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.
2.8 kB · 93 lines
TypeScript
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394// Generic compile traversal: validate a pipeline graph, run each block's// `apply()` over its parsed params, group the outputs by family, and hand// the grouped result to a target. Targets live app-side (e.g. a mapping// onto an orchestration engine's inputs); future targets (direct serialSGS// constraint sets, MiniZinc) slot in behind the same interface without a// format change.
import type { BlockInstance, PipelineGraph } from "./graph.ts";import { validateGraph } from "./graph.ts";import type { BlockDef, BlockRegistry } from "./registry.ts";
export interface CompiledBlock { def: BlockDef; instance: BlockInstance; output: unknown;}
export interface CompiledFamilies { constraints: CompiledBlock[]; scorers: CompiledBlock[]; search: CompiledBlock; collectors: CompiledBlock[];}
export interface CompileTarget<T> { fromFamilies(families: CompiledFamilies, graph: PipelineGraph): T;}
export class GraphValidationError extends Error { readonly issues: readonly { instanceId?: string; code?: string; message: string; }[];
constructor(issues: GraphValidationError["issues"]) { super( `Pipeline graph failed validation:\n${issues.map((i) => `- ${i.message}`).join("\n")}`, ); this.name = "GraphValidationError"; this.issues = issues; }}
export function compileGraph<T>( registry: BlockRegistry, graph: PipelineGraph, target: CompileTarget<T>,): T { const validated = validateGraph(registry, graph); if (!validated.ok) throw new GraphValidationError(validated.issues);
const constraints: CompiledBlock[] = []; const scorers: CompiledBlock[] = []; const collectors: CompiledBlock[] = []; let search: CompiledBlock | undefined;
for (const instance of validated.graph.blocks) { // validateGraph guarantees the code resolves and already parsed the // params (defaults/transforms applied) — no second parse here. const def = registry.get(instance.code)!; const params = validated.parsedParams.get(instance.instanceId); const output: unknown = ( def.block as { apply(input: unknown): unknown } ).apply(params); const compiled: CompiledBlock = { def, instance, output };
switch (def.family) { case "constraint": constraints.push(compiled); break; case "scorer": scorers.push(compiled); break; case "search": search = compiled; break; case "collector": collectors.push(compiled); break; default: { const exhaustive: never = def.family; throw new Error(`Unhandled block family: ${String(exhaustive)}`); } } }
// validateGraph enforces exactly one search block. return target.fromFamilies( { constraints, scorers, search: search!, collectors }, validated.graph, );}