# 02 — Planning Layer (MPS / MRP / CRP) — Technical Digest

> Lossless English digest of `files/manufacturing_spec/markdown/02_planning.md`. An implementer should never need the original. Source-of-truth wording, every entity, field, rule, enum, and formula reproduced, plus an explicit "expects from ERP" contract for mapping onto Moon ERP.

The Planning layer is "the brain": it translates expected/actual demand into a concrete, **feasible** production plan. It inherits all cross-cutting rules from `00` (frozen snapshots, status state machines, many-to-many demand↔supply, multi-level recursion, config-over-code, real-time GL postings, resource abstraction). Three **sequential** components with a **closed feedback loop**: `MPS → MRP → CRP`.

---

## 2.1 The Three Outputs of Planning

Planning as a whole produces exactly three deliverables (mental model):

| # | Output | Question answered | Produced by |
|---|---|---|---|
| 1 | Order acceptance + delivery date | "Can we accept the order? By when can we deliver?" | MPS + CTP |
| 2 | Material schedule | "What to buy/make, how much, when" | MRP |
| 3 | Detailed production plan | "When does each stage start?" | MRP draft + CRP confirm |

**Inputs to planning:** customer order, master data (BOM / Routing / Work Center), current inventory, item settings, material ownership.

---

## 2.2 Component: MPS (Master Production Schedule)

**Purpose:** Decides **what** to produce, **how much**, **when**. Under make-to-order it shifts role from "forecaster" to "order translator + acceptance/capacity checker."

### Behaviour branches by `production_type`
Enum `production_type ∈ { MakeToStock, MakeToOrder, AssembleToOrder, TollManufacturing }`.

```
if MakeToStock:
    demand = apply_forecast_consumption(forecast, actual_orders)
    PAB[t] = PAB[t-1] + planned_production[t] - demand
    if PAB[t] < safety_stock: suggest production
if MakeToOrder:
    ignore forecast; planned_production = confirmed_orders; safety_stock = 0
    run CTP check (capacity-based promise)
if AssembleToOrder:
    components → MakeToStock; finished assembly → MakeToOrder
if TollManufacturing:
    plan capacity only (material is the customer's — see 2.3)
```

### Core formula — PAB (Projected Available Balance)
```
PAB[t] = PAB[t-1] + scheduled_production[t] - demand[t]
demand[t] = (per forecast_consumption + time_fence rules below)
```

### Forecast Consumption (avoid double-counting)
Actual orders **consume** the forecast; they do not add to it.
```
consumed            = MIN(actual_orders[t], forecast[t])
remaining_forecast  = forecast[t] - consumed
total_demand[t]     = actual_orders[t] + remaining_forecast
if actual_orders[t] > forecast[t]: total_demand[t] = actual_orders[t]
```
**Config:** `forecast_consumption_method ∈ {None, Backward, Forward, Both}`, `consumption_window`.

### Time Fences (zone determined by distance from today)
```
distance = t - today
if   distance <= DTF (Demand Time Fence):   zone = FROZEN  → demand = actual_orders only (ignore forecast)
elif distance <= PTF (Planning Time Fence): zone = SLUSHY  → MAX/consumption blend; changes need approval
else:                                       zone = LIQUID  → demand = forecast (no orders yet)
```
Purpose: prevents "system nervousness" — the near-term plan is stable, the far-term flexible.

### CTP (Capable to Promise) — core of make-to-order
Capacity-based delivery promise (distinct from ATP which is inventory-based).
```
CTP_Check(product P, qty Q, due D):
    for op in routing(P):
        if op.wc is PRESS-like:
            cycles = Q / op.cavity_count
            load[wc] += cycles × op.cycle_time + op.setup_time
        else:
            load[wc] += Q × op.run_time + op.setup_time
    for wc in required: earliest[wc] = find_earliest_slot(wc, load[wc])
    promised = MAX(earliest[wc])          # bottleneck decides
    return promised <= D ? "on time" : ("earliest: " + promised)
```

### Order splitting & batching (Many-to-Many)
- Large order → multiple **delivery schedules** (e.g. monthly).
- Multiple similar orders → ONE production order (to reduce setup / mold changes).
- Links recorded in the **Pegging** table (defined in spec file 06):
```
Pegging: { production_order_id, sales_order_id, allocated_quantity }
```

### MPS data model
```
MPS_Header: { id, plan_name, plant_id, time_bucket ∈ {Day, Week, Month},
              start_date, end_date, frozen_fence, slushy_fence, status }
MPS_Line:   { product_id, period_date, forecast_qty, confirmed_orders_qty,
              planned_production, projected_balance, safety_stock_target,
              available_to_promise }
```

### ATP calculation — 3 modes (config)
```
Discrete:   ATP[t] = production[t] - orders until next production
Cumulative: ATP[t] = Σ production≤t - Σ orders≤t
Look-ahead: ATP[t] = production[t] - orders(t .. next production)   ← safest
```

---

## 2.3 Component: MRP (Material Requirements Planning)

**Purpose:** Turn production orders into material needs — what to buy/make, how much, when. MRP is a **process, not a table**; it produces transient **Planned Orders**.

### Item MRP settings
```
Item_MRP_Settings: {
  item_id,
  mrp_type           ∈ {Planned, ReorderPoint, NoPlanning},
  procurement_type   ∈ {Buy, Make},
  material_ownership ∈ {Own, Customer},
  lot_sizing_rule    ∈ {Exact, Fixed, MinMax, EOQ, PeriodOrder},
  lot_size, min_lot, max_lot,
  safety_stock, lead_time_days, reorder_point
}
```

### MRP algorithm (level-by-level, top to deepest)
```
MRP_Run():
  sort items by BOM level (finished = 0, raw = deepest)
  for each item, for each period t:
    Gross[t]     = MPS_demand (finished) + dependent_demand_from_parents (component)
    Available[t] = on_hand + open_POs[t] + open_WOs[t] - reserved[t]
    Net[t]       = MAX(0, Gross[t] - Available[t] + safety_stock)
    Order_Qty    = apply_lot_sizing(Net[t], lot_rule)
    Release_Date = Required_Date - lead_time
    if procurement_type == Make:
        explode_BOM → dependent demand for components → recurse deeper
```

### BOM Explosion (recursive, multi-level)
```
explode_BOM(product P, qty Q):
  for line in BOM(P):
    required = Q × line.quantity × (1 + line.scrap_pct)
    dependent_demand[line.component] += required
    if line.component.procurement_type == Make:
        explode_BOM(line.component, required)
```
Note: scrap is applied at each BOM line via `line.scrap_pct`.

### Lot Sizing (key branch for tooling factories)
```
apply_lot_sizing(net, rule):
  Exact:       net
  Fixed:       round_up(net, lot_size)
  MinMax:      clamp(net, min, max)
  EOQ:         economic_order_qty()
  PeriodOrder: Σ net over several periods   ← consolidate to cut setup / mold changes
```
Tooling-heavy items use `PeriodOrder` / `Fixed` to batch demand and minimize mold mounts.

### Toll-manufacturing branch (driven by `material_ownership`)
```
when generating purchase for a component:
  if component.material_ownership == Customer:
      DO NOT raise a purchase order        (customer supplies it)
      instead: verify customer-supplied quantity is sufficient; if short → alert
  else:
      raise a normal purchase order
```

### MRP outputs
```
- Planned Purchase Orders    → Procurement module
- Planned Production Orders   → Execution layer
- Exception Messages          → "will be late", "reschedule in/out", "cancel"
```

---

## 2.4 Component: CRP (Capacity Requirements Planning)

**Purpose:** MRP plans assuming **infinite capacity**. CRP injects reality — verifies that work centers (and tools, and labor) have enough capacity at the required time. In tooling factories CRP matters **more** than MRP (materials are easy; scheduling N tools on M machines is the real problem).

### CRP algorithm
```
CRP_Run():
  # 1. Load per planned order
  for order, op:
    if op.wc is PRESS-like:
      cycles = order.qty / op.cavity_count
      load   = cycles × op.cycle_time + op.setup_time
    else:
      load   = order.qty × op.run_time + op.setup_time
  # 2. Distribute over periods → 3. Aggregate per resource
  Total_Load[resource, period] = Σ loads
  # 4. Compare
  Util = Total_Load / effective_capacity × 100
  Util > 100 → OVERLOAD ; Util < 70 → UNDERLOAD ; else OK
  # 5. Resolve overload
  → reschedule / overtime / extra shift / defer order / use multi-cavity tool
```
Thresholds: `Util > 100 → OVERLOAD`, `Util < 70 → UNDERLOAD`, otherwise OK.

### Resource intersection (Machine + Tool + Labor)
```
earliest_start = MAX(machine_free_time, tool_free_time, labor_free_time)
```
The binding constraint is the **minimum-availability** resource. A **tool** is a *movable* resource that must be tracked across machines. (`resource_type ∈ {Machine, Tool, Labor}`.)

### RCCP vs CRP (both supported)
```
RCCP (Rough Cut): runs on MPS, critical resources only → used by CTP for order acceptance
CRP  (Detailed):  runs on MRP, all resources         → used for detailed scheduling
```

### Melamine test case (proves the design)
```
WC-PRESS week3 capacity 90h. Order 6000 dishes, mold cavity=1, cycle 80s → 133h.
Util = 148% → OVERLOAD. Resolution options surfaced, including:
  use a 4-cavity mold variant → 6000/4 × 80s = 33h ✓
Links a scheduling decision (CRP) to a master-data choice (which mold).
```

---

## 2.5 Known Gap — Detailed Scheduling (APS)

CRP detects overload but does **NOT** sequence operations hour-by-hour on specific machines. A future **APS (Advanced Planning & Scheduling)** component should:
- Sequence operations on each machine/tool on a timeline (Gantt).
- Optimize mold-mount order to minimize changeovers (the hardest problem: ~50 molds on ~4 presses).
- Respect machine warm-up, shift patterns, tool availability.

**APS interface:** consumes Planned Orders + Routing + Resource calendars; emits a time-phased schedule that SFC (spec file 05) executes.

---

## 2.6 Closed Loop

```
CRP finds conflict → feeds back to MPS → adjusts promised_date → re-runs
Costing variances (file 04) → inform standards & future planning
```

---

## Enums & config points introduced/used in this file

| Identifier | Domain | Owner |
|---|---|---|
| `production_type` | MakeToStock, MakeToOrder, AssembleToOrder, TollManufacturing | item / order setting (00) |
| `material_ownership` | Own, Customer | Item_MRP_Settings (00) |
| `procurement_type` | Buy, Make | Item_MRP_Settings (00) |
| `mrp_type` | Planned, ReorderPoint, NoPlanning | Item_MRP_Settings (02) |
| `lot_sizing_rule` | Exact, Fixed, MinMax, EOQ, PeriodOrder | Item_MRP_Settings (00/02) |
| `resource_type` | Machine, Tool, Labor | Resource (00) |
| `forecast_consumption_method` | None, Backward, Forward, Both | MPS config (02) |
| `time_bucket` | Day, Week, Month | MPS_Header (02) |
| ATP mode | Discrete, Cumulative, Look-ahead | MPS config (02) |
| Time-fence zone | FROZEN, SLUSHY, LIQUID | derived from DTF/PTF (02) |
| CRP util state | OVERLOAD (>100), OK, UNDERLOAD (<70) | CRP output (02) |

Numeric config fields: `DTF` (Demand Time Fence), `PTF` (Planning Time Fence), `consumption_window`, `lot_size`, `min_lot`, `max_lot`, `safety_stock`, `lead_time_days`, `reorder_point`, `frozen_fence`, `slushy_fence`, `scrap_pct` (per BOM line), `cavity_count`, `cycle_time`, `setup_time`, `run_time`.

---

## Entities this part defines (mapping contracts)

1. **MPS_Header** — planning run header (plan_name, plant_id, time_bucket, start/end dates, frozen/slushy fences, status).
2. **MPS_Line** — per product/period grid (forecast, confirmed orders, planned production, projected balance, safety-stock target, ATP).
3. **Item_MRP_Settings** — per-item planning policy (mrp_type, procurement_type, material_ownership, lot sizing, safety stock, lead time, reorder point).
4. **Planned Order (transient)** — output of MRP: Planned Purchase Order + Planned Production Order; promotable into real PO / Production Order.
5. **Exception Message** — MRP advisory (late / reschedule-in / reschedule-out / cancel).
6. **CRP Load record** — `Total_Load[resource, period]` with utilization & state vs effective capacity.
7. **Pegging** (referenced; defined in 06) — `{production_order_id, sales_order_id, allocated_quantity}` linking demand↔supply.

---

## Expects from ERP (integration contract)

- **Sales (demand)**: confirmed customer orders and their due dates + delivery schedules to feed MPS `confirmed_orders_qty` and CTP. Moon ERP `Modules\Sales\SalesOrder` (+ delivery-note schedules).
- **A forecast source**: nothing in Moon ERP produces demand forecasts today — MPS needs `forecast_qty` per product/period (new table or import).
- **Inventory (supply/availability)**: `on_hand`, `reserved` per item/period. Moon ERP `Modules\Inventory\StockBalance` + `InventoryMovement`.
- **Open supply visibility**: `open_POs[t]` (Purchases `PurchaseOrder` not yet received) and `open_WOs[t]` (Production `ProductionOrder` not yet completed) time-phased by due date.
- **Master data**: BOM (with per-line `quantity` + `scrap_pct` + `procurement_type`), Routing operations (with `run_time`, `setup_time`, `cycle_time`, `cavity_count`, target work center), Work Center capacity. Moon ERP `Modules\Production\BillOfMaterials` / `BomComponent` / `BomOperation` / `ProductionCenter` (`capacity_per_hour`).
- **Resource calendars & effective capacity** per work center / tool / labor — needed for CTP/RCCP/CRP `find_earliest_slot` and `effective_capacity`. Moon `ProductionCenter.capacity_per_hour` exists but no calendar/shift model yet.
- **Procurement handoff**: MRP Planned Purchase Orders must become Purchases `PurchaseRequest`/`PurchaseOrder`; must skip components where `material_ownership == Customer`.
- **Execution handoff**: MRP Planned Production Orders must become `Modules\Production\ProductionOrder` (Planned status); CRP must reschedule via `planned_start_date`/`planned_end_date`.
- **Pegging store**: a many-to-many link table between Sales orders and Production orders.
- **Item master**: every planned SKU resolves to Core `Product`; Item_MRP_Settings extends Product (extension-table pattern, like `AccBpExt`).
- **UoM**: BOM line quantities, lot sizes, stock balances, and order quantities must share a unit basis (UoM conversion service) — spec assumes consistent units but never states conversion.
</content>
</invoke>
