# levelset

> A TypeScript resource-leveling and project-scheduling toolkit. Author constraints and scorers as composable blocks, run a schedule-generation search over any `ProjectFile`, and materialize a re-leveled schedule. Bundled readers/writers move real plans in and out of Microsoft Project (`.mpp`, `.mspdi`), JSON, CSV, and XLSX.

The library has two halves that can be used independently:

- **Leveling** (`levelset/leveling`) — the scheduling engine: resolve calendars, search for feasible schedules under a set of constraints, score and pick among them, then write the result back. Pulls only `zod` (a peer dependency), so the file-I/O dependencies stay out of your bundle.
- **File I/O** (`levelset`, `levelset/advanced`) — read a `.mpp`/`.mspdi`/JSON plan into a structured `ProjectFile`, or write a `ProjectFile` out as MSPDI, JSON, CSV, or XLSX. The MPP reader extracts tasks, resources, assignments, calendars, and relations.

The unifying data type is `ProjectFile`: a plain object holding `tasks`, `resources`, `assignments`, `calendars`, `relations`, and `properties`. Readers produce it, writers consume it, and the leveling engine round-trips through it.

## Core model

- `ProjectFile` — the interchange document (tasks, resources, assignments, calendars, relations, properties).
- `Task` — a unit of work with a `uniqueId`, `duration` (a `Duration` instance), dates, and `predecessors` (precedence edges).
- `Resource` — a person, crew, equipment, or cost item, referenced by `uniqueId`.
- `Assignment` — links a task to a resource with `units` (how many of that resource the task demands while active).
- A **`WorkUnit`** is the largest independent work package — the minimum complete increment that can be handed over, grouping a set of `taskUniqueIds`. Work units are what the WIP-limiting constraints and scorers operate on. They are authored once via `resolveCalendar`'s `workUnits` option, which resolves them into a by-id registry on `ResolvedProject` (`workUnits: Map<number, WorkUnit>`, duplicate ids rejected); constraints and scorers then reference them by id.

## Leveling pipeline

The flow is a sequence of small composable steps:

```
ProjectFile ──resolveCalendar──▶ ResolvedProject ──serialSGS.run(constraints)──▶
  ScheduleStream ──bestBy(scorer)──▶ Schedule ──materialize──▶ ProjectFile
```

- `resolveCalendar(project)` turns calendars into day-indexed working time (a bitmap + prefix sum) so "working days between A and B" is O(1).
- `serialSGS` is a greedy serial Schedule Generation Scheme: topo-sort by precedence, then place each task on the earliest day that satisfies every constraint. It yields one feasible schedule per run via an async generator.
- `ScheduleStream` is a lazy iterable of feasible schedules with `filter` / `map` / `take` / `branch` and the materializers `bestBy` / `paretoFrontier` / `collect`.
- `materialize(schedule)` writes a chosen `Schedule` back into a `ProjectFile`, ready to hand to any writer.

Minimal end-to-end example:

```ts
import { resolveCalendar, serialSGS, streamFromFactory, materialize } from "levelset/leveling";
import type { Constraint, Scorer } from "levelset/leveling";

const resolved = resolveCalendar(project, {
  // Work units are defined once here; constraints/scorers reference them by id.
  workUnits: [
    { id: 10, taskUniqueIds: [1, 2, 3] },
    { id: 20, taskUniqueIds: [4, 5, 6] },
  ],
});

const constraints: Constraint[] = [
  { kind: "MaxConcurrentResource", resourceUniqueId: 100, max: 3 },
  {
    kind: "ConcurrentUnitsLimit",
    max: 2, // at most 2 work units open at once (a hard WIP cap)
    unitIds: [10, 20], // references the units resolved above
  },
];

const stream = streamFromFactory(() => serialSGS.run(resolved, constraints));

const makespan: Scorer = { name: "makespan", direction: "min", score: (s) => s.makespan };
const best = await stream.bestBy(makespan);

if (best) {
  const leveled = materialize(best); // a ProjectFile you can write out
}
```

## Constraints

`Constraint` is a discriminated union (the `kind` field) — the interchange format between blocks and the search. `serialSGS` enforces a subset directly; the rest are recorded as `unsupportedConstraints` annotations on the emitted schedule (intended for a future CP-SAT / MiniZinc backend).

Enforced by `serialSGS`:

- `Precedence` — FS / SS / FF / SF edges with lag (FF/SF are approximated).
- `MaxConcurrentResource` — at most N tasks may demand a given resource on any day (a crew/throughput cap).
- `PeakCap` — sum of fractional units on a resource ≤ a cap (optionally windowed).
- `ConcurrentUnitsLimit` — at most N work units have active work on any day; optional `discipline` (a `resourceUniqueId`) scopes the cap per-discipline. This is the hard WIP cap that prioritizes completion.
- `Release` / `Deadline` — earliest-start / latest-finish bounds per task. (Pin a task to a fixed window by pairing a `Release` and a `Deadline` on it.)
- `Calendars` — baked into `resolveCalendar` upstream.

Recorded as annotations only (not yet enforced): `UnitPrecedence`, `UnimodalProfile`, `ModeSelection`, `CrewFlowContinuity`.

## Scorers and blocks

A `Scorer` ranks a `Schedule` (`direction: "min" | "max"`); `stream.bestBy(scorer)` picks the best. Scoring **blocks** (`level-blocks`) build configured scorers and also emit a MiniZinc fragment for the compiled backend.

- `ConcurrentResourceCostBlock` — prices each marginal concurrent worker on an exponential curve (a soft `MaxConcurrentResource`).
- `OpenUnitPenaltyBlock` — prices each open-unit-day beyond a `softMax` (a soft WIP limit).
- `HiringLagPenaltyBlock` — prices week-over-week headcount increases (training/ramp cost).
- `UnimodalDeviationBlock` — prices how far a resource histogram departs from a single-peak shape.

Constraint blocks (`MaxConcurrentResourceBlock`, `ConcurrentUnitsLimitBlock`) build the corresponding hard `Constraint` plus a MiniZinc fragment.

Note: because `serialSGS` emits a single schedule, a scorer currently *ranks* output rather than *steers* it. Scorers become an optimization lever once a multi-candidate search (restart / LDS / LNS) consumes them. Hard constraints are what shape `serialSGS` output today.

## File I/O

```ts
import { readMpp, readMspdi, readJson, writeMspdi, writeJson, writeCsv, writeXlsx } from "levelset";

const project = readMpp(mppBytes);        // Uint8Array | ArrayBuffer (MPP14+ / Project 2010+)
const project2 = readMspdi(xmlString);    // MSPDI XML
const json = writeJson(project);          // also writeMspdi, writeCsv
const xlsx = await writeXlsx(project);    // returns a Uint8Array
```

| Format | Read | Write |
| ------ | ---- | ----- |
| MPP (binary, MPP14+) | Yes | No |
| MSPDI (XML) | Yes | Yes |
| JSON | Yes | Yes |
| CSV | No | Yes |
| XLSX | No | Yes |

Validate untrusted JSON with `levelset/schema` (Zod schemas with transforms that rebuild `Duration` and `Date` instances): `ProjectFileSchema.safeParse(data)`.

## Subpath exports

- `levelset` — convenience functions (`readMpp`, `writeJson`, `writeCsv`, `writeXlsx`, …) and the core model types.
- `levelset/leveling` — the resource-leveling toolkit (`level-core` + `level-blocks`); no file-I/O dependencies.
- `levelset/advanced` — reader/writer classes and container utilities (`MppReader`, `MspdiWriter`, `parseMppBuffer`, `detectMppVariant`, …).
- `levelset/schema` — Zod validation schemas for all model types.

## Docs

- [README](README.md): full usage guide — install, file I/O, the leveling toolkit, supported formats, and scripts.
- [Literature review](docs/literature-review.md): the published research behind the engine's design (compositional constraint programming, lazy enumeration, hyper-heuristics, LLM-assisted optimization modeling).
- [CHANGELOG](CHANGELOG.md): release history.

## Source entry points

- [src/index.ts](src/index.ts): the `levelset` convenience API (readers/writers + model types).
- [src/leveling.ts](src/leveling.ts): the `levelset/leveling` surface (core types, pipeline functions, blocks).
- [src/level-core](src/level-core): the leveling engine — `resolveCalendar`, `serialSGS`, `scheduleStream`, `materialize`, `types.ts` (the `Constraint`/`Schedule`/`Scorer` definitions).
- [src/level-blocks](src/level-blocks): configured constraint and scorer blocks.
- [src/model](src/model): the `ProjectFile` model (`Task`, `Resource`, `Assignment`, `Calendar`, `Relation`, `Duration`).

## Optional

- [src/advanced.ts](src/advanced.ts): lower-level reader/writer classes and MPP container inspection.
- [src/schema](src/schema): Zod schemas exported under `levelset/schema`.
- [src/mpp](src/mpp), [src/mspdi](src/mspdi), [src/json](src/json), [src/csv](src/csv), [src/xlsx](src/xlsx): per-format reader/writer implementations.
- [test](test): the test suite, a source of worked usage examples.
