mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 09:28:19 +00:00
feat(freight): offer built-train wagons per boarding yard on multi-yard consists
This commit is contained in:
@@ -20,17 +20,53 @@ import type { CorridorLeg } from './corridor-capacity.util';
|
||||
* Gelan→Adama never competes for stock with an export on Adama→Doraleh.
|
||||
*/
|
||||
export class WagonStockLedger {
|
||||
/**
|
||||
* Usage rows keyed by pool. A single-yard train has one pool (''), so this is
|
||||
* exactly the original per-type accounting. A multi-yard consist keys by
|
||||
* boarding yard as well, because the Dire wagons and the Mojo wagons are
|
||||
* disjoint sets of steel: 5 Dire wagons riding the whole corridor occupy the
|
||||
* Mojo→Addis edge, but they must not shrink what Mojo itself can offer.
|
||||
*/
|
||||
private readonly usedPerEdge = new Map<string, number[]>();
|
||||
|
||||
constructor(
|
||||
private readonly remainingByTypeId: Map<string, number>,
|
||||
private readonly edgeCount: number,
|
||||
/**
|
||||
* Multi-yard consist only (see {@link WagonStock.byYardId}): the wagons
|
||||
* standing at each yard. When present, a leg is served ONLY by the wagons
|
||||
* standing at the yard it boards from — a Dire→Addis booking on a train
|
||||
* whose wagons sit 20 in Dire and 33 in Mojo sees 20, and a Mojo→Addis
|
||||
* booking sees 33, never the Dire wagons that ride past empty.
|
||||
*/
|
||||
private readonly byYardId?: Map<string, Map<string, number>>,
|
||||
/** Ordered corridor stops, parallel to the edges — maps an edge to its yard. */
|
||||
private readonly stops: readonly string[] = [],
|
||||
) {}
|
||||
|
||||
/** The yard a leg boards from, or '' when the train is not split across yards. */
|
||||
private poolYardOf(leg: CorridorLeg): string {
|
||||
if (!this.byYardId) return '';
|
||||
return this.stops[leg.fromEdge] ?? '';
|
||||
}
|
||||
|
||||
/** Usage-row key: one row per (pool, wagon type). */
|
||||
private rowKey(wagonTypeId: string, leg: CorridorLeg): string {
|
||||
const pool = this.poolYardOf(leg);
|
||||
return pool ? `${pool}\u0000${wagonTypeId}` : wagonTypeId;
|
||||
}
|
||||
|
||||
/** Wagons of one type offered at the yard a leg boards from. */
|
||||
private totalForType(wagonTypeId: string, leg: CorridorLeg): number {
|
||||
const pool = this.poolYardOf(leg);
|
||||
if (!pool) return this.remainingByTypeId.get(wagonTypeId) ?? 0;
|
||||
return this.byYardId?.get(pool)?.get(wagonTypeId) ?? 0;
|
||||
}
|
||||
|
||||
/** Free wagons of ONE type on a leg: total minus its busiest edge within that leg. */
|
||||
private availableForType(wagonTypeId: string, leg: CorridorLeg): number {
|
||||
const total = this.remainingByTypeId.get(wagonTypeId) ?? 0;
|
||||
const row = this.usedPerEdge.get(wagonTypeId);
|
||||
const total = this.totalForType(wagonTypeId, leg);
|
||||
const row = this.usedPerEdge.get(this.rowKey(wagonTypeId, leg));
|
||||
if (!row) return total;
|
||||
let busiest = 0;
|
||||
for (let edge = leg.fromEdge; edge < leg.toEdge; edge += 1) {
|
||||
@@ -69,10 +105,11 @@ export class WagonStockLedger {
|
||||
if (!deepest) break;
|
||||
|
||||
const take = Math.min(outstanding, deepest.free);
|
||||
let row = this.usedPerEdge.get(deepest.id);
|
||||
const key = this.rowKey(deepest.id, leg);
|
||||
let row = this.usedPerEdge.get(key);
|
||||
if (!row) {
|
||||
row = new Array<number>(this.edgeCount).fill(0);
|
||||
this.usedPerEdge.set(deepest.id, row);
|
||||
this.usedPerEdge.set(key, row);
|
||||
}
|
||||
for (let edge = leg.fromEdge; edge < leg.toEdge; edge += 1) {
|
||||
row[edge] = (row[edge] ?? 0) + take;
|
||||
|
||||
Reference in New Issue
Block a user