leg-aware capacity and wagon sharing

This commit is contained in:
Marshal
2026-07-30 19:08:06 +00:00
parent acef6870e9
commit f891f6abe2
7 changed files with 113 additions and 25 deletions

View File

@@ -6925,6 +6925,42 @@ export class TrainSchedulingService {
reverseWagonOrder: schedule.reverseWagonOrder,
});
// Heaviest-edge consist usage. Cross-leg slot sharing means plain sums
// over-report a multi-stop train — a wagon reused Gelan→Adama and
// Adama→Doraleh is two slots but ONE physical wagon, and the train is never
// heavier/longer than its heaviest single leg. Same math as the pull-limit
// enforcement; coupled-but-empty consist wagons ride every edge.
const heaviestLeg = schedule.trainSet
? (() => {
const usage = maxEdgeConsistUsage(
[
...(schedule.trainSet.wagons ?? []).map((w) => ({
lengthMeters: Number(w.lengthMeters),
tareWeightTons: w.wagonType
? Number(w.wagonType.tareWeightTons)
: 0,
assignedWeightTons: Number(w.assignedWeightTons),
boardYardId: w.boardYardId ?? null,
alightYardId: w.alightYardId ?? null,
allocations: w.allocations ?? [],
})),
...emptyConsistWagons.map((w) => ({
lengthMeters: w.lengthMeters,
tareWeightTons: Number(w.tareWeightTons ?? 0),
assignedWeightTons: 0,
allocations: [],
})),
],
this.mapScheduleStops(schedule).map((s) => s.yardId),
);
return {
grossWeightTons: roundTons(usage.grossWeightTons),
lengthMeters: roundTons(usage.lengthMeters),
loadedWagonCount: usage.loadedWagonCount,
};
})()
: null;
return {
id: schedule.id,
reference: schedule.reference ?? null,
@@ -6997,6 +7033,9 @@ export class TrainSchedulingService {
wagonCount: schedule.trainSet.wagonCount,
totalWeightTons: roundTons(Number(schedule.trainSet.totalWeightTons)),
totalLengthMeters: roundTons(Number(schedule.trainSet.totalLengthMeters)),
// What the locomotives actually haul: usage on the corridor's
// heaviest edge, not the sum of every leg's slots.
heaviestLeg,
locomotive: schedule.trainSet.locomotive
? {
id: schedule.trainSet.locomotive.id,