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.
8.9 kB · 248 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249import { test, expect, describe } from "bun:test";import { z } from "zod";
import { deriveBlockMeta, dumpBlockMeta, ISO_DATE_PATTERN, type BlockFieldSpec,} from "../../src/level-blocks/meta.ts";import type { BlockDef } from "../../src/level-blocks/registry.ts";import { standardBlockDefs, standardRegistry, STANDARD_CODES,} from "../../src/level-blocks/standard.ts";import { DEFAULT_MAX_COMBINATIONS, HARD_MAX_COMBINATIONS, SWEEP_ENABLED_TARGET,} from "../../src/level-blocks/sweep.ts";
const defsByCode = new Map(standardBlockDefs().map((d) => [d.code, d]));function meta(code: string) { return deriveBlockMeta(defsByCode.get(code)!);}function field( fields: BlockFieldSpec[], path: string,): BlockFieldSpec | undefined { return fields.find((f) => f.path === path);}
describe("deriveBlockMeta — scalar fields", () => { test("integer field carries bounds, integer flag, and shadow default", () => { const m = meta(STANDARD_CODES.maxConcurrentResource); const max = field(m.fields, "max")!; expect(max.kind).toBe("number"); expect(max.integer).toBe(true); expect(max.min).toBe(1); // exclusiveMinimum 0 → inclusive 1 for integers expect(max.max).toBeUndefined(); // INT_SENTINEL stripped expect(max.defaultValue).toBe(1); // from defaultShadow expect(max.label).toBe("max"); });
test("number field with a schema default surfaces that default", () => { const m = meta(STANDARD_CODES.concurrentResourceCost); const threshold = field(m.fields, "threshold")!; expect(threshold.kind).toBe("number"); // defaultShadow omits threshold, so the schema default (0) fills in. expect(threshold.defaultValue).toBe(0); const growth = field(m.fields, "growthBase")!; expect(growth.min).toBe(1); // exclusiveMinimum 1, non-integer → stays 1 expect(growth.integer).toBe(false); });
test("boolean field becomes a checkbox", () => { const m = meta(STANDARD_CODES.unimodalDeviation); const flag = field(m.fields, "allowSecondPeak")!; expect(flag.kind).toBe("checkbox"); expect(flag.defaultValue).toBe(false); });
test("enum field becomes a dropdown with string options", () => { const m = meta(STANDARD_CODES.gate); const fallback = field(m.fields, "fallback")!; expect(fallback.kind).toBe("dropdown"); expect(fallback.options).toEqual(["leastBad", "none"]); });});
describe("deriveBlockMeta — variadic scalar arrays", () => { test("integer array → number variadic (ConcurrentUnitsLimit.unitIds)", () => { const m = meta(STANDARD_CODES.concurrentUnitsLimit); const v = m.variadic.find((p) => p.path === "unitIds")!; expect(v.itemKind).toBe("number"); expect(v.integer).toBe(true); expect(v.minItems).toBe(0); expect(v.emptySeed).toBe(0); expect(v.defaultValues).toEqual([]); // unitIds is a variadic, not a complex param. expect(m.complexParams).not.toContain("unitIds"); });
test("string array → text variadic (KBest.by)", () => { const m = meta(STANDARD_CODES.kBest); const v = m.variadic.find((p) => p.path === "by")!; expect(v.itemKind).toBe("text"); expect(v.integer).toBe(false); expect(v.minItems).toBe(1); expect(v.emptySeed).toBe(""); expect(v.defaultValues).toEqual(["makespan"]); });});
describe("deriveBlockMeta — complex (object-array) params", () => { test("ModeSelection.modes is complex, taskUniqueId is a field", () => { const m = meta(STANDARD_CODES.modeSelection); expect(m.complexParams).toContain("modes"); expect(field(m.fields, "taskUniqueId")).toBeDefined(); // No nested field is emitted for the object-array's inner scalars. expect(m.fields.some((f) => f.path.startsWith("modes."))).toBe(false); });
test("PhaseLateness.phases and Gate.limits are complex", () => { expect(meta(STANDARD_CODES.phaseLateness).complexParams).toContain( "phases", ); expect(meta(STANDARD_CODES.gate).complexParams).toContain("limits"); });});
describe("deriveBlockMeta — nested objects flatten to dotted fields", () => { test("PeakCap.window flattens to window.fromDay / window.toDay", () => { const m = meta(STANDARD_CODES.peakCap); expect(field(m.fields, "window.fromDay")).toBeDefined(); expect(field(m.fields, "window.toDay")).toBeDefined(); // The container object itself is neither a field nor complex. expect(m.fields.some((f) => f.path === "window")).toBe(false); expect(m.complexParams).not.toContain("window"); });});
describe("deriveBlockMeta — empty-schema blocks", () => { test("Makespan and SerialSGS have no fields, variadic, or complex params", () => { for (const code of [STANDARD_CODES.makespan, STANDARD_CODES.serialSGS]) { const m = meta(code); expect(m.fields).toEqual([]); expect(m.variadic).toEqual([]); expect(m.complexParams).toEqual([]); } });});
describe("deriveBlockMeta — registry facts", () => { test("carries family, tier, doc, defaultShadow, and inputSchema", () => { const m = meta(STANDARD_CODES.makespan); expect(m.family).toBe("scorer"); expect(m.tier).toBe("sales"); expect(m.doc.nl.length).toBeGreaterThan(0); expect(m.defaultShadow).toEqual({}); expect((m.inputSchema as { type?: string }).type).toBe("object"); });
test("assumptionTemplate is included when present, omitted otherwise", () => { expect( meta(STANDARD_CODES.maxConcurrentResource).assumptionTemplate, ).toContain("concurrent"); expect(meta(STANDARD_CODES.makespan).assumptionTemplate).toBeUndefined(); });});
describe("deriveBlockMeta — panelBinding-driven kinds (date, slider)", () => { // The standard registry declares no panelBindings and no date params, so a // synthetic def exercises the slider and date branches. const schema = z.object({ start: z.string().regex(/^\d{4}-\d{2}-\d{2}$/), level: z.number().min(0).max(10), }); const def: BlockDef = { code: "Synthetic", family: "constraint", tier: "engineer", block: { id: "Synthetic", schema: { input: schema, output: z.unknown() }, apply: (i: unknown) => i, toMiniZinc: () => ({ text: "" }), doc: { nl: "synthetic", pseudocode: "" }, }, defaultShadow: { start: "2026-01-01", level: 5 }, panelBindings: [ { paramPath: "level", label: "Aggressiveness", control: "slider", options: { min: 1, max: 8, step: 0.5 }, }, ], };
test("date pattern → date field", () => { const m = deriveBlockMeta(def); const start = field(m.fields, "start")!; expect(start.kind).toBe("date"); expect(start.defaultValue).toBe("2026-01-01"); });
test("slider binding → slider field with binding bounds and label", () => { const m = deriveBlockMeta(def); const level = field(m.fields, "level")!; expect(level.kind).toBe("slider"); expect(level.label).toBe("Aggressiveness"); expect(level.slider).toEqual({ min: 1, max: 8, step: 0.5 }); expect(level.defaultValue).toBe(5); });
test("ISO_DATE_PATTERN matches the block-author date convention", () => { expect(new RegExp(ISO_DATE_PATTERN).test("2026-07-16")).toBe(true); expect(new RegExp(ISO_DATE_PATTERN).test("not-a-date")).toBe(false); });});
describe("dumpBlockMeta", () => { test("dumps every registered block with the composition grammar", () => { const dump = dumpBlockMeta(standardRegistry()); expect(dump.formatVersion).toBe(1); expect(dump.blocks.length).toBe(standardBlockDefs().length); expect(dump.composition.order).toEqual([ "constraint", "scorer", "search", "collector", ]); expect(dump.composition.cardinality.search).toEqual({ min: 1, max: 1 }); expect(dump.levelsetVersion.length).toBeGreaterThan(0); });
test("derives canvas authoring contracts from the instance and sweep schemas", () => { const dump = dumpBlockMeta(standardRegistry()); expect(dump.authoring.instance.reservedParamNames).toEqual([ "code", "provenance", "shadow", "enabled", ]); expect(dump.authoring.instance.enabledWhenOmitted).toBe(true); expect(dump.authoring.instance.schema).toMatchObject({ type: "object", required: ["instanceId", "code", "params"], }); expect(dump.authoring.sweep).toMatchObject({ enabledTarget: SWEEP_ENABLED_TARGET, defaultMaxCombinations: DEFAULT_MAX_COMBINATIONS, hardMaxCombinations: HARD_MAX_COMBINATIONS, schema: { type: "object", required: ["axes"] }, }); expect(dump.authoring.objective.lexicographicTierSchema).toMatchObject({ anyOf: [ { type: "string" }, { type: "object", required: ["weightedSum"], properties: { weightedSum: { type: "object" } }, }, ], }); });});