# 02 — Planning Layer

> The brain. Translates expected/actual demand into a concrete, feasible production plan. Inherits all rules from `00`. Three sequential components: **MPS → MRP → CRP**, with a closed feedback loop.

---

## 2.1 The Three Outputs of Planning

Planning, as a whole, produces exactly three deliverables. Keep these as the mental model:

```
Output 1: Can we accept the order? By when can we deliver?   (MPS + CTP)
Output 2: Material schedule — what to buy/make, how much, when (MRP)
Output 3: Detailed production plan — when each stage starts    (MRP draft + CRP confirm)
```

Inputs: customer order, master data (BOM/Routing/WC), 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 from "forecaster" to "order translator + acceptance/capacity checker."

### Behaviour branches by `production_type`
```
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 customer's, see 2.3)
```

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

### Forecast Consumption (avoid double-counting)
Actual orders **consume** the forecast, they don't 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 (state 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)
```
Prevents "system nervousness": near-term plan is stable, far-term is flexible.

### CTP (Capable to Promise) — core of make-to-order
```
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决定
    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 (reduce setup/mold changes).
- Links recorded in **Pegging** table (see 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. It is a **process**, not a table — 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
}
```

### 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)
```

### 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
```
when generating purchase for a component:
  if component.material_ownership == Customer:
      DO NOT raise purchase order   (customer supplies it)
      instead: verify customer-supplied quantity is sufficient; if short → alert
  else:
      raise normal purchase order
```

### 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 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).

### 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
```

### 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.

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

### Melamine test case
```
WC-PRESS week3 capacity 90h. Order 6000 dishes, mold cavity=1, cycle 80s → 133h.
Util=148% → OVERLOAD. Resolution options surfaced, including:
  use 4-cavity mold variant → 6000/4 ×80s = 33h ✓
This 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 **Advanced Planning & Scheduling (APS)** 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.

Interface: consumes Planned Orders + Routing + Resource calendars; emits a time-phased schedule that SFC (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
```
