mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-01 12:23:26 +00:00
chore(scripts): price one booking in every currency to check DJF
Exercising DJF through the portal means enabling the flag, building a
booking, and clicking through the wizard before a single franc figure
appears. This prices an existing booking three ways instead and prints the
lines side by side, so the rate feed, the conversion, the zero-decimal
rounding and the cross-currency parity are all visible in one command.
Read-only: computePriceForBooking persists nothing and the currency is
flipped on the in-memory entity, so it is safe against a shared database
and safe to re-run.
pnpm --filter @edr/freight-api exec ts-node -r tsconfig-paths/register \
src/scripts/check-djf-pricing.ts BK-2026-000221
The parity line is the check worth reading: the same freight quoted in ETB
and in DJF must come back to the same money once converted. Anything past
rounding drift means a rate or a conversion is wrong.
This commit is contained in:
94
apps/edr-freight-api/src/scripts/check-djf-pricing.ts
Normal file
94
apps/edr-freight-api/src/scripts/check-djf-pricing.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import 'reflect-metadata';
|
||||
import { config } from 'dotenv';
|
||||
import { resolve } from 'path';
|
||||
|
||||
config({ path: resolve(__dirname, '../../.env') });
|
||||
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { ExchangeService } from '@edr/api-common';
|
||||
|
||||
import { AppModule } from '../app.module';
|
||||
import { BookingPricingService } from '../modules/bookings/booking-pricing.service';
|
||||
import { BookingsService } from '../modules/bookings/bookings.service';
|
||||
|
||||
/**
|
||||
* Prices one existing booking in each currency and prints the totals side by side.
|
||||
*
|
||||
* READ-ONLY. computePriceForBooking() persists nothing, and the booking's currency is
|
||||
* flipped on the in-memory entity only — nothing is saved, so this is safe to run against
|
||||
* a shared database and safe to re-run.
|
||||
*
|
||||
* Usage:
|
||||
* pnpm --filter @edr/freight-api exec ts-node -r tsconfig-paths/register \
|
||||
* src/scripts/check-djf-pricing.ts BK-2026-000221
|
||||
*/
|
||||
async function main() {
|
||||
const reference = process.argv[2];
|
||||
if (!reference) {
|
||||
console.error('Usage: check-djf-pricing.ts <booking-reference>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const app = await NestFactory.createApplicationContext(AppModule, {
|
||||
logger: ['error', 'warn'],
|
||||
});
|
||||
|
||||
try {
|
||||
const exchange = app.get(ExchangeService, { strict: false });
|
||||
const pricing = app.get(BookingPricingService, { strict: false });
|
||||
const bookings = app.get(BookingsService, { strict: false });
|
||||
|
||||
const usdToEtb = await exchange.getRate('USD', 'ETB');
|
||||
const usdToDjf = await exchange.getRate('USD', 'DJF');
|
||||
const djfToEtb = await exchange.getRate('DJF', 'ETB');
|
||||
const feed = exchange.getProviderStatus();
|
||||
|
||||
console.log('\nRates');
|
||||
console.log(` source ${feed.source ?? 'unknown'}${feed.lastError ? ` (${feed.lastError})` : ''}`);
|
||||
console.log(` USD → ETB ${usdToEtb}`);
|
||||
console.log(` USD → DJF ${usdToDjf.toFixed(4)} (peg is 177.7210)`);
|
||||
console.log(` DJF → ETB ${djfToEtb.toFixed(6)}`);
|
||||
|
||||
const booking = await bookings.findByReference(reference);
|
||||
console.log(`\nBooking ${booking.reference} — ${booking.tradeDirection} ${booking.freightType}, stored in ${booking.paymentCurrency}`);
|
||||
|
||||
for (const currency of ['USD', 'ETB', 'DJF'] as const) {
|
||||
// In-memory only. Never saved.
|
||||
const priced = await pricing.computePriceForBooking({
|
||||
...booking,
|
||||
paymentCurrency: currency,
|
||||
} as typeof booking);
|
||||
|
||||
const decimals = currency === 'DJF' ? 0 : 2;
|
||||
console.log(`\n ${currency} total ${priced.totalAmount.toFixed(decimals)}`);
|
||||
for (const line of priced.lineItems) {
|
||||
console.log(
|
||||
` ${line.code.padEnd(28)} ${String(line.amount.toFixed(decimals)).padStart(14)} ${line.currency}`,
|
||||
);
|
||||
}
|
||||
const fractional = priced.lineItems.filter(
|
||||
(l) => currency === 'DJF' && !Number.isInteger(l.amount),
|
||||
);
|
||||
if (fractional.length > 0) {
|
||||
console.log(` ⚠ ${fractional.length} DJF line(s) carry centimes — DJF is zero-decimal`);
|
||||
}
|
||||
}
|
||||
|
||||
// The cross-check that matters: the same freight, quoted three ways, is one price.
|
||||
const etb = (await pricing.computePriceForBooking({ ...booking, paymentCurrency: 'ETB' } as typeof booking)).totalAmount;
|
||||
const djf = (await pricing.computePriceForBooking({ ...booking, paymentCurrency: 'DJF' } as typeof booking)).totalAmount;
|
||||
const djfAsEtb = djf * djfToEtb;
|
||||
const driftPct = Math.abs(djfAsEtb - etb) / etb * 100;
|
||||
console.log(
|
||||
`\nParity ${djf.toFixed(0)} DJF = ${djfAsEtb.toFixed(2)} ETB vs ${etb.toFixed(2)} ETB priced directly` +
|
||||
` → ${driftPct.toFixed(3)}% drift (rounding only; > 0.5% means something is wrong)`,
|
||||
);
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user