@classytic/planning
v0.1.1
Published
Planning kernel — deterministic net-requirements (MRP) core PLUS its run/suggestion persistence boundary. Phase 1: the PURE distribution-mode calculation (forecast consumption, time-phasing, lot sizing, inter-branch allocation) exposed browser-safe at `./
Readme
@classytic/planning
The planning (MRP) kernel — a pure, deterministic net-requirements
calculation core. Given a plain snapshot of demand + supply, it returns
buy and inter-branch transfer suggestions. No HTTP, no persistence, no side
effects, zero runtime dependencies. The ./domain subpath is isomorphic and
runs unchanged in the browser, so a UI computes the exact same plan the
backend does.
Phase 1 of the ERP Demand → Forecast → Planning design (
spine/docs/erp-forecast-planning.md). This package is the calculation core ONLY. Persisted runs + supersession (Phase 2), thearc-planningHTTP surface (Phase 3), and MRP/BOM mode (Phase 5) build on top of it.
Install
npm install @classytic/planningPrimary entry
import { calculateNetRequirements, skuRef, nodeRef, buildFixedBuckets } from '@classytic/planning';
const result = calculateNetRequirements({
asOf, // the as-of instant (InstantMs) — NEVER read from a clock
calendar: { buckets: buildFixedBuckets(asOf, 7, 8) }, // 8 weekly buckets
items: [
{ skuRef: skuRef('WIDGET'), supplyType: 'buy', baseUom: 'ea',
leadTimeDays: 7, safetyStock: 5, lotSizing: { kind: 'moq', min: 10 },
transferPreferred: true },
],
demand: [ /* qualified open sales-order lines */ ],
forecast: [ /* independent/forecast demand */ ],
supply: [ /* on-hand + reserved + scheduled receipts per node */ ],
policy: { consumptionWindow: { backward: 1, forward: 2 }, preferTransferBeforeBuy: true },
});
result.plannedOrders; // buy / transfer suggestions (sorted, deterministic)
result.requirements; // time-phased demand/supply curve per (SKU, node, bucket)
result.diagnostics; // typed, result-carried findings (never thrown for expected cases)
result.stats; // roll-up: planned/skipped SKUs, past-due qty, order counts, ...calculateNetRequirements(input, options?) is a pure function: same input +
policy ⇒ byte-equivalent result, regardless of wall clock or the order
lines arrive in.
What it does (distribution mode)
- Forecast consumption — qualified customer orders consume forecast in a documented window so the same expected sale is never counted twice.
- Time-phasing — a projected-available walk per (SKU, node) across the horizon; a shortfall below safety stock raises a net requirement.
- Network allocation — a destination shortage is first covered by
surplus at source nodes (never draining a head-office / storefront-protected
node below its protected level), emitting
transferorders beforebuy. - Lot sizing — the remaining net is sized by the SKU's rule
(lot-for-lot / MOQ / multiple / MOQ+multiple) into a
buy(ormake) suggestion.
Everything runs off a base-UoM normalized snapshot; UoMs are converted explicitly (never mixed silently), past-due demand/receipts roll into the first bucket (reported separately), and a SKU with no policy or an unconvertible UoM is skipped with a diagnostic rather than crashing the run.
Determinism
The engine reads no clock, no ambient state, and no Math.random. Inputs are
canonicalized before summation and every output array is emitted in a total
order, so a JSON.stringify of the result is stable across input shuffles.
This is proved by tests/determinism.property.test.ts (a Phase-exit gate).
The Phase-5 BOM seam
A make SKU emits a make planned order without exploding its BOM in
Phase 1. The only place explosion will slot in is the optional explode hook:
calculateNetRequirements(input, {
explode: (makeOrder) => [ /* DependentDemand[] for components */ ],
});The engine already folds returned dependent demand into a
per-(SKU, node, bucket) accumulator that every SKU reads before its walk — so
turning on MRP mode later needs only (a) supplying explode and (b) visiting
SKUs in low-level-code order. No rewrite of the walk.
Subpaths
| Import | Contents |
|---|---|
| @classytic/planning | full public API (currently re-exports ./domain) |
| @classytic/planning/domain | the isomorphic pure core — browser-safe |
Scripts
npm run build # tsdown → dist (esm + d.ts, no sourcemaps)
npm run typecheck # tsc --noEmit
npm test # vitest (unit)Not in this package (by design)
No HTTP, no auth, no Mongo/mongoose, no scheduling, no release side effects,
no BOM explosion, no forecasting algorithm (the forecast series is an input).
Those are host / spine / later-phase concerns per the design's kernel-purity
rule (spine/docs/PACKAGE_RULES.md §8).
