mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 07:51:02 +00:00
110 lines
3.7 KiB
TypeScript
110 lines
3.7 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
||
import { PrismaService } from '../common/prisma.service';
|
||
|
||
const SEED_FLAG = 'SEED_SEGMENT_FARES';
|
||
|
||
/**
|
||
* Seeds SegmentFareRule rows for every origin→destination pair on the
|
||
* Addis Ababa–Djibouti route across all active seat classes.
|
||
*
|
||
* Skip-if-loaded: uses Prisma upsert on the unique constraint
|
||
* (routeId, originStopSequence, destinationStopSequence, seatClassId, nationality).
|
||
* Re-running is safe — existing rows are updated in-place.
|
||
*/
|
||
@Injectable()
|
||
export class SegmentFareSeeder {
|
||
private readonly logger = new Logger(SegmentFareSeeder.name);
|
||
|
||
constructor(private readonly prisma: PrismaService) {}
|
||
|
||
async run() {
|
||
if (process.env[SEED_FLAG]?.trim().toLowerCase() !== 'true') {
|
||
this.logger.log(`Skipping segment fare seed — set ${SEED_FLAG}=true to enable`);
|
||
return;
|
||
}
|
||
|
||
const route = await this.prisma.route.findFirst({
|
||
where: { active: true },
|
||
include: { stops: { orderBy: { sequence: 'asc' } } },
|
||
});
|
||
|
||
if (!route) {
|
||
this.logger.warn('No active route found — skipping segment fare seed');
|
||
return;
|
||
}
|
||
|
||
const seatClasses = await this.prisma.seatClass.findMany({
|
||
where: { isActive: true },
|
||
});
|
||
|
||
if (!seatClasses.length) {
|
||
this.logger.warn('No active seat classes found — skipping segment fare seed');
|
||
return;
|
||
}
|
||
|
||
const stops = route.stops;
|
||
const validFrom = new Date('2025-01-01T00:00:00.000Z');
|
||
|
||
// Rate table: ETB minor units per km, keyed by (nationalityType, bedPosition)
|
||
// null bedPosition = regular seat
|
||
const rateTable: Record<string, Record<string | 'null', number>> = {
|
||
LOCAL: { null: 3000, UPPER: 4000, MIDDLE: 5500, LOWER: 6000 },
|
||
INTERNATIONAL: { null: 6000, UPPER: 8000, MIDDLE: 11000, LOWER: 12000 },
|
||
};
|
||
|
||
let upserted = 0;
|
||
|
||
for (const seatClass of seatClasses) {
|
||
const natType = seatClass.nationalityType ?? 'LOCAL';
|
||
const bedPos = seatClass.bedPosition ?? 'null';
|
||
const ratePerKm = rateTable[natType]?.[bedPos] ?? rateTable['LOCAL']['null'];
|
||
|
||
for (let i = 0; i < stops.length - 1; i++) {
|
||
for (let j = i + 1; j < stops.length; j++) {
|
||
const origin = stops[i];
|
||
const dest = stops[j];
|
||
|
||
// Approximate distance: sum of per-stop distanceKm if available,
|
||
// otherwise fall back to sequence-gap × 50 km.
|
||
let distanceKm = 0;
|
||
for (let k = i; k < j; k++) {
|
||
distanceKm += stops[k + 1].distanceKm ?? 50;
|
||
}
|
||
|
||
const baseFareMinor = Math.round(distanceKm * ratePerKm);
|
||
|
||
await this.prisma.segmentFareRule.upsert({
|
||
where: {
|
||
routeId_originStopSequence_destinationStopSequence_seatClassId_nationality: {
|
||
routeId: route.id,
|
||
originStopSequence: origin.sequence,
|
||
destinationStopSequence: dest.sequence,
|
||
seatClassId: seatClass.id,
|
||
nationality: natType,
|
||
},
|
||
},
|
||
update: { baseFareMinor, validFrom },
|
||
create: {
|
||
routeId: route.id,
|
||
originStopSequence: origin.sequence,
|
||
destinationStopSequence: dest.sequence,
|
||
seatClassId: seatClass.id,
|
||
nationality: natType,
|
||
baseFareMinor,
|
||
currency: 'ETB',
|
||
validFrom,
|
||
},
|
||
});
|
||
|
||
upserted++;
|
||
}
|
||
}
|
||
}
|
||
|
||
this.logger.log(
|
||
`Segment fare seed complete — ${upserted} rules upserted ` +
|
||
`(${stops.length} stops × ${seatClasses.length} seat classes)`,
|
||
);
|
||
}
|
||
}
|