mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 23:00:57 +00:00
changes
This commit is contained in:
@@ -502,20 +502,48 @@ export function sumWagonsRequired(booking: Booking, wagonPlan?: WagonPlanSlot[])
|
||||
}
|
||||
|
||||
/**
|
||||
* One wagon carries one kind of cargo: a slot with a BULK allocation holds
|
||||
* nothing else — no container beside it and no second bulk booking. Container
|
||||
* allocations may still share a wagon with each other (TEU rules apply).
|
||||
* One wagon carries one kind of cargo AT A TIME: while a bulk load rides, the
|
||||
* wagon holds nothing else — no container beside it and no second bulk
|
||||
* booking. Container allocations may share a wagon with each other (TEU rules
|
||||
* apply).
|
||||
*
|
||||
* "At a time" is the whole rule: a wagon whose cargo alights at Dire Dawa is
|
||||
* empty steel for whatever boards there, so an import container on
|
||||
* Doraleh→Dire and bulk on Dire→Kality legitimately share one wagon. Pass
|
||||
* `legs` (booking id → stop-index span) to check per corridor edge; without
|
||||
* it every allocation is treated as riding the whole route, which is the
|
||||
* correct reading for a single-leg train.
|
||||
*/
|
||||
export function validateWagonCargoExclusivity(wagonPlan: WagonPlanSlot[]): string[] {
|
||||
export function validateWagonCargoExclusivity(
|
||||
wagonPlan: WagonPlanSlot[],
|
||||
legs?: Map<string, { from: number; to: number }>,
|
||||
edgeCount = 1,
|
||||
): string[] {
|
||||
const violations: string[] = [];
|
||||
const edges = Math.max(1, edgeCount);
|
||||
const spanOf = (bookingId: string) => {
|
||||
const leg = legs?.get(bookingId);
|
||||
if (!leg || leg.from < 0 || leg.to > edges || leg.from >= leg.to) {
|
||||
return { from: 0, to: edges };
|
||||
}
|
||||
return leg;
|
||||
};
|
||||
|
||||
for (const slot of wagonPlan) {
|
||||
const hasBulk = slot.allocations.some(
|
||||
(a) => a.loadType === AllocationLoadType.Bulk,
|
||||
);
|
||||
if (hasBulk && slot.allocations.length > 1) {
|
||||
violations.push(
|
||||
`Wagon #${slot.sequenceNo} mixes bulk with other cargo — a wagon carrying bulk takes that one load only`,
|
||||
);
|
||||
if (slot.allocations.length < 2) continue;
|
||||
// Per edge: who is on this wagon while it rides that edge?
|
||||
for (let edge = 0; edge < edges; edge += 1) {
|
||||
const riding = slot.allocations.filter((a) => {
|
||||
const span = spanOf(a.bookingId);
|
||||
return span.from <= edge && edge < span.to;
|
||||
});
|
||||
if (riding.length < 2) continue;
|
||||
if (riding.some((a) => a.loadType === AllocationLoadType.Bulk)) {
|
||||
violations.push(
|
||||
`Wagon #${slot.sequenceNo} mixes bulk with other cargo — a wagon carrying bulk takes that one load only`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return violations;
|
||||
@@ -547,6 +575,9 @@ export function validateTrainLimits(
|
||||
wagonPlan: WagonPlanSlot[],
|
||||
wagonType: Pick<WagonType, 'lengthMeters'>,
|
||||
limits?: TrainLimitConfig,
|
||||
/** Leg-aware cargo exclusivity — see {@link validateWagonCargoExclusivity}. */
|
||||
legs?: Map<string, { from: number; to: number }>,
|
||||
edgeCount?: number,
|
||||
): string[] {
|
||||
const maxWeightTons = limits?.maxWeightTons ?? MAX_TRAIN_WEIGHT_TONS;
|
||||
const maxLengthMeters = limits?.maxLengthMeters ?? MAX_TRAIN_LENGTH_METERS;
|
||||
@@ -564,7 +595,7 @@ export function validateTrainLimits(
|
||||
);
|
||||
|
||||
violations.push(...validateBulkWagonSlotWeights(wagonPlan));
|
||||
violations.push(...validateWagonCargoExclusivity(wagonPlan));
|
||||
violations.push(...validateWagonCargoExclusivity(wagonPlan, legs, edgeCount));
|
||||
|
||||
return violations;
|
||||
}
|
||||
@@ -578,6 +609,8 @@ export function validateMixedTrainLimits(
|
||||
wagonPlan: WagonPlanSlot[],
|
||||
wagonTypes: Array<Pick<WagonType, 'lengthMeters'>>,
|
||||
limits?: TrainLimitConfig,
|
||||
legs?: Map<string, { from: number; to: number }>,
|
||||
edgeCount?: number,
|
||||
): string[] {
|
||||
const maxLengthMeters = limits?.maxLengthMeters ?? MAX_TRAIN_LENGTH_METERS;
|
||||
const minWagonLength = Math.min(
|
||||
@@ -591,6 +624,8 @@ export function validateMixedTrainLimits(
|
||||
wagonPlan,
|
||||
{ lengthMeters: minWagonLength },
|
||||
{ ...limits, maxWagonsPerTrain },
|
||||
legs,
|
||||
edgeCount,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -608,8 +643,13 @@ export function validateMixedTrainLimitsPerEdge(
|
||||
stops: string[],
|
||||
/** Display names parallel to `stops` — violations then name the leg they hit. */
|
||||
stopLabels?: string[],
|
||||
/** Booking id → stop-index span, so cargo exclusivity is judged per edge. */
|
||||
legs?: Map<string, { from: number; to: number }>,
|
||||
): string[] {
|
||||
if (stops.length <= 2) return validateMixedTrainLimits(wagonPlan, wagonTypes, limits);
|
||||
const edges = Math.max(1, stops.length - 1);
|
||||
if (stops.length <= 2) {
|
||||
return validateMixedTrainLimits(wagonPlan, wagonTypes, limits, legs, edges);
|
||||
}
|
||||
const spans = slotSpans(wagonPlan, stops);
|
||||
const label = (i: number) => stopLabels?.[i] ?? stops[i];
|
||||
const violations = new Set<string>();
|
||||
@@ -618,7 +658,13 @@ export function validateMixedTrainLimitsPerEdge(
|
||||
(_, i) => spans[i].from <= edge && edge < spans[i].to,
|
||||
);
|
||||
if (!active.length) continue;
|
||||
for (const violation of validateMixedTrainLimits(active, wagonTypes, limits)) {
|
||||
for (const violation of validateMixedTrainLimits(
|
||||
active,
|
||||
wagonTypes,
|
||||
limits,
|
||||
legs,
|
||||
edges,
|
||||
)) {
|
||||
violations.add(`Leg ${label(edge)} → ${label(edge + 1)}: ${violation}`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user