Procurement
PR-to-payment, unblocked.
A defect that’s been “awaiting parts” for ninety days is almost always stuck somewhere in the procurement chain. The Procurement pipeline does two things: it tracks every active PO through the funnel, and it surfaces the structural problems — aged POs, supplier underperformance, budget overrun — that systematically slow it down.
The funnel
PR raised ──▶ PO approved ──▶ Supplier confirms ──▶Goods ready ──▶ Forwarder picks up ──▶ Vessel receives ──▶Invoice clearedEach transition has a stage threshold:
| Stage | Threshold | What “stuck” means |
|---|---|---|
| PR → PO approval | 5 days | Authority-limit or budget-code issue |
| PO → supplier confirmation | 3 days | Supplier didn’t acknowledge — chase needed |
| Supplier → goods ready | per quoted lead-time | Production / stock issue |
| Goods → forwarder pickup | 14 days | Forwarder consolidation gap |
| Forwarder → vessel | per route ETA | In transit |
| Vessel receipt → invoice clearance | 7 days | Documentation / accounting |
Time-in-stage for every active PO:
T_stage(PO) = t_exit − t_enterA PO sitting beyond the stage threshold is flagged. The aggregate of stuck POs across stages is the funnel-health view.
Three views, three questions
Forwarders backlog — POs with forwarders that haven’t reached the vessel yet. Sorted by ETA; safety-critical items surface at the top regardless of ETA.
Open POs older than 180 days — the structural-problem view. A PO older than 180 days is rarely just “still in transit”. It’s almost always: cancelled but not closed, supplier abandoned, wrong vessel, partial-delivery dispute, or simply forgotten. Most items can be closed with a single phone call.
Purchase log — end-to-end log from PR raise to invoice clearance. The source of truth for a specific PO’s history; the other two views aggregate from it.
Worked example — MV POSUN
Weekly procurement review:
| View | Count | Notable |
|---|---|---|
| Forwarders backlog | 18 | 2 safety-critical (one delayed 22 days vs ETA) |
| Open > 180 days | 11 | 6 likely “cancelled but not closed”, 3 supplier abandoned, 2 partial-delivery disputes |
| Aged at approval stage | 4 | Awaiting authority approval > 7 days |
| Budget variance | Technical +18%, Stores +6% | Technical category over threshold |
| Supplier scorecards (failing) | 2 | Vendor X 62% on-time, Vendor Y 71% on-time |
Verdict: HIGH — technical-budget overrun and the safety-critical lifeboat-winch delay. The pipeline:
- Flags the lifeboat-winch motor as immediate action — supplier and forwarder need a chase today.
- Generates the 11-PO close-out list with predicted disposition for each.
- Routes the budget overrun to the financial pipeline for variance attribution and year-end forecast.
- Flags Vendor X for renegotiation review.
Under the hood
Safety-critical pipeline
Items tagged safety-critical get a separate timeline. Any delay against ETA is treated as critical regardless of magnitude:
| Item | Vessel | Supplier | Stage | Days in stage | ETA |
|---|---|---|---|---|---|
| Lifeboat winch motor | POSUN | Schat-Harding | Forwarder | 22 | 2026-05-12 |
| Fire-pump impeller | OCEAN | Wärtsilä | Goods ready | 3 | TBD |
| EEBD canister x4 | NEXUS | Drager | Vessel receipt | 1 | delivered, awaiting receipt |
The list is short by design — most safety-critical items move quickly. Anything sitting on this list more than a few days is escalation-worthy.
Supplier scorecards
Per supplier, the pipeline tracks:
On-time % = N_on_time / N_total × 100
Mean lead time = (1/N) × Σ (t_delivered_i − t_ordered_i)Plus quality issues per shipment and recent escalations. A scorecard below 70% on-time triggers a renegotiation flag.
The same supplier delivering late on five vessels is a fleet-wide problem, not a per-vessel one. Supplier metrics are aggregated across the fleet.
Budget compliance
Per category (technical / stores / lube / victualling / repairs / etc.):
Variance % = (Actual − Budget) / Budget × 100Variance over 10% is flagged; over 25% triggers escalation. Cross-references the financial pipeline for the year-end forecast view.
180-day filter — code snapshot
# Filter POs older than 180 days that are still openthreshold_date = datetime.utcnow() - timedelta(days=180)
aged_pos = po_collection.aggregate([ {"$match": { "imo": {"$in": active_imos}, "status": {"$nin": ["CLOSED", "CANCELLED", "INVOICED"]}, "prRaisedDate": {"$lte": threshold_date} }}, {"$addFields": { "ageDays": { "$divide": [ {"$subtract": [datetime.utcnow(), "$prRaisedDate"]}, 1000 * 60 * 60 * 24 ] } }}, {"$sort": {"ageDays": -1}},])Below 90 days the noise is too high (legitimately in-transit POs are common). Below 180 days the structural problems are isolated. Above 180 days, the structural problems dominate.
Closure-rate trend
Closure rate = N_POs_closed_in_period / (N_open_at_start + N_opened_in_period)A falling closure rate combined with rising aged-PO count is the structural drift indicator — the same logic used in the defects pipeline.
Escalation triggers
| Trigger | Severity |
|---|---|
| Safety-critical item delayed against ETA | CRITICAL |
| Stuck delivery more than 30 days with no update | CRITICAL |
| Budget overrun above 25% in any category | HIGH |
| Supplier on-time below 70% with active POs | HIGH |
| PO awaiting approval more than 14 days | HIGH |
| Aged-PO count rising for 2+ consecutive periods | HIGH |