/** * Fixed train-number pools assigned to a train on dispatch. * * The prefix encodes cargo type (8 = container, 1 = bulk) and the parity encodes * trade direction (odd = export, even = import). Numbers are finite and recycle: * a number is "in use" only while its train is DISPATCHED and not yet ARRIVED. */ export const CONTAINER_EXPORT_NUMBERS = [ '8001', '8101', '8201', '8301', '8401', '8501', '8601', '8701', '8801', '8901', ] as const; export const CONTAINER_IMPORT_NUMBERS = [ '8002', '8102', '8202', '8302', '8402', '8502', '8602', '8702', '8802', '8902', ] as const; export const BULK_EXPORT_NUMBERS = ['1101', '1103', '1105', '1107'] as const; export const BULK_IMPORT_NUMBERS = ['1002', '1004', '1006', '1008'] as const; export type CargoKind = 'CONTAINER' | 'BULK'; export type PoolDirection = 'IMPORT' | 'EXPORT'; export interface TrainNumberPool { cargo: CargoKind; /** EXPORT = odd numbers, IMPORT = even numbers. */ direction: PoolDirection; numbers: readonly string[]; } /** * Resolve which fixed pool a train draws from. * * - Cargo: container vs bulk by dominant wagon count; ties resolve to container. * - Direction: EXPORT → odd pool, IMPORT → even pool. DOMESTIC (neither end is * Djibouti) has no dedicated pool, so it defaults to the export/odd pool. */ export function pickTrainNumberPool( containerWagons: number, bulkWagons: number, direction: 'IMPORT' | 'EXPORT' | 'DOMESTIC' | null | undefined, ): TrainNumberPool { const cargo: CargoKind = bulkWagons > containerWagons ? 'BULK' : 'CONTAINER'; const poolDirection: PoolDirection = direction === 'IMPORT' ? 'IMPORT' : 'EXPORT'; const numbers = cargo === 'CONTAINER' ? poolDirection === 'IMPORT' ? CONTAINER_IMPORT_NUMBERS : CONTAINER_EXPORT_NUMBERS : poolDirection === 'IMPORT' ? BULK_IMPORT_NUMBERS : BULK_EXPORT_NUMBERS; return { cargo, direction: poolDirection, numbers }; } /** Lowest pool number not currently in use, or null when the pool is exhausted. */ export function pickLowestFreeNumber( pool: readonly string[], usedNumbers: Iterable, ): string | null { const used = new Set(usedNumbers); for (const number of pool) { if (!used.has(number)) return number; } return null; }