Files
edr-platform/apps/edr-freight-api/docs/priority-batch-window-flow.md
Marshal ddddcfb71f Refactor wagon type handling and container wagon calculations
- Removed maxWagonsPerTrain from WagonType entity and related DTOs.
- Updated containerWagonsForLines function to calculate required wagons based on container lines more accurately.
- Added unit tests for containerWagonsForLines to ensure correct calculations.
- Adjusted related services and scripts to reflect the removal of maxWagonsPerTrain.
- Enhanced booking and contract components to use new status labels for better user experience.
- Implemented validation for unique container numbers in shipment forms.
2026-07-09 03:36:35 +00:00

4.0 KiB

Priority & Batch Window Flow (Import, Freight)

Export = no batch, no priority. Pure first-come-first-served (booking-batch.service.ts:462-467, 625-628). Everything below is import only.

Step by step

1. Booking submitted → priority score computed booking-transition.service.ts:110-111,196-197booking-pricing.service.ts:403-407 computeSubmitPriorityScore()rule-engine.service.ts:118.

  • Government booking: +50,000 (government-priority.constants.ts:2, applied rule-engine.service.ts:201)
  • Plus cargo/weight modifiers
  • Stored on booking.priorityScore

2. Window opens (PRE_WINDOW → OPEN) Cron tick every 10s: booking-window.service.ts:63advanceImportbooking-window.service.ts:232-251. Times computed by computeImportWindowTimes (batch-window.util.ts:248-283).

3. Customers book during OPEN Booking lands as:

  • Commercial → FULLY_EXECUTED
  • Government → APPROVED/PAID (skips contract flow)

4. Window closes (OPEN → DOC_REVIEW) booking-window.service.ts:254-268. Staff review docs for docReviewMinutes.

5. Doc review ends Staff completeDocReview() (booking-window.service.ts:124-159) or timeout → booking-window.service.ts:270-295. Before batch runs: expireUnacceptedForRouteDay (booking-batch.service.ts:1853-1883) kills never-accepted bookings so they can't compete.

6. Batch fill runs processRouteDayfillRouteDay (booking-batch.service.ts:1138-1319), or single-schedule fillSchedule (:1018-1128).

  • Pool pulled pre-sorted: findBatchPool/findBatchPoolByCorridorDay (bookings.repository.ts:991-1008, 1055-1083) ORDER BY is_government DESC, priority_score DESC, fully_executed_at ASC, created_at ASC
  • Consolidated pairs grouped as one atomic unit: groupConsolidatedPool (:1962-1987) — never split.
  • Greedy placement, earliest-departing fitting train first: loop at :1218-1306.
  • No fit + government booking → preemptForGovernment (:1891-1910): bumps lowest-priorityScore commercial victim first, only if legs overlap (:1920).
  • No fit + commercial import (GENERAL/ONE_TIME) → maybe partial "split" offer: maybeOfferPartial/isSplitEligible (:1326-1370).
  • Still no fit → stays pooled, notifier.unplaced (:1278-1280).

7. Placed bookings get reserved/allocated

  • Commercial: reserve() (:1673-1703) → SELECTED_FOR_BATCH, payment deadline set, DOC_REVIEW→PAYMENT (booking-window.service.ts:275-294).
  • Government: allocate() directly (:1706-1746), no payment step.

8. Payment phase ends booking-window.service.ts:297-309settleDueReservationssettleReserved (:1437-1491):

  • paid → allocated
  • unpaid → expired, capacity freed

Then concludeCycle (:315-373):

  • Train full → DONE + auto-finalize (:320-329)
  • Not full → reopen same/next day (nextCycleOpensAt / office hours, :331-372, batch-window.util.ts:217-224) or DONE if no cycle fits before departure.

9. Backstop settleOverdueReservations (booking-window.service.ts:388-406) catches any reservation whose deadline passed outside the normal tick.

Phase enum

PRE_WINDOW → OPEN → DOC_REVIEW → PAYMENT → (reopen PRE_WINDOW | DONE) (booking-window.config.ts:27-34)

What decides priority

  1. is_government — always first, both in SQL sort and compareSchedulingPriority util (compare-scheduling-priority.util.ts:9-23)
  2. priority_score DESC (rule engine: government bonus + cargo/weight modifiers)
  3. fully_executed_at ASC (earlier wins)
  4. created_at ASC

Edge cases

  • Government preemption only bumps if legs overlap; picks lowest-priority victim first.
  • Consolidated pairs are both-or-neither, never split (:1326-1334, 1793).
  • Only GENERAL/ONE_TIME import bookings are eligible for partial "split" offers.
  • Per-unit try/catch around reserve — one failure can't cause silent trickle/stagger allocation (comment at :1283-1288).
  • Each train freezes its own rule snapshot at window-open time, not live config (booking-window.service.ts:85-93).