Files
edr-platform/apps/edr-freight-api/src/scripts/tmp-ops-verify.ts
2026-08-19 13:51:18 +00:00

100 lines
3.4 KiB
TypeScript

/* Throwaway harness: applies the operations migration to a scratch database and
* runs every operations report across several parameter sets. Delete after use. */
import { DataSource } from 'typeorm';
import { OperationsReporting3580000000000 } from '../migrations/3580000000000-OperationsReporting';
import { REPORTS } from '../modules/reports/report.registry';
import { ReportContext } from '../modules/reports/report.types';
const NEW_KEYS = [
'station-staying-time',
'turnaround-cycle',
'train-delays',
'trainset-performance',
'teu-performance',
'cargo-volume-performance',
'charged-vs-actual-volume',
'cargo-volume-by-station',
];
const PARAM_SETS: Record<string, unknown>[] = [
{},
{ period: 'month' },
{ period: 'quarter', direction: 'IMPORT' },
{ period: 'week', country: 'Djibouti' },
{ period: 'year', country: 'Ethiopia', categories: ['CONTAINER_IMPORT_MULTIMODAL', 'BULK'] },
{ dateFrom: '2020-01-01', dateTo: '2030-01-01', delayedOnly: 'true' },
{ trainNumber: 'X', origin: 'NAGAD', destination: 'KALITY' },
];
async function main(): Promise<void> {
const ds = new DataSource({
type: 'postgres',
host: 'localhost',
port: 5433,
username: 'nati',
password: 'password',
database: 'nati_wt_opsreport',
entities: ['src/**/*.entity.ts'],
synchronize: false,
logging: false,
});
await ds.initialize();
// Apply the migration's DDL by hand — this scratch database is not under
// migration control and only needs the two new tables and two columns.
const runner = ds.createQueryRunner();
await new OperationsReporting3580000000000().up(runner);
await runner.release();
console.log('migration applied\n');
let failures = 0;
for (const key of NEW_KEYS) {
const def = REPORTS.find((r) => r.key === key);
if (!def) {
console.log(`MISSING ${key}`);
failures++;
continue;
}
for (const [i, params] of PARAM_SETS.entries()) {
for (const directions of [null, ['IMPORT']] as (string[] | null)[]) {
const ctx: ReportContext = { ds, params, directions };
const label = `${key} [set ${i}${directions ? ' scoped' : ''}]`;
try {
const qb = def.query(ctx);
const rows = await qb.limit(5).getRawMany();
// The runner wraps every query for the total count — prove that works too.
const [sql, bound] = qb.getQueryAndParameters();
const counted = await ds.query(`SELECT COUNT(*)::int AS n FROM (${sql}) AS sub`, bound);
// Sorting: every sortable column must be a legal ORDER BY.
for (const col of def.columns.filter((c) => c.sortable)) {
await def
.query({ ds, params, directions })
.orderBy(col.sortExpr ?? `"${col.key}"`, 'DESC')
.limit(1)
.getRawMany();
}
const kpis = def.summary ? await def.summary(ctx) : [];
console.log(
`OK ${label.padEnd(50)} rows=${rows.length} total=${counted[0]?.n ?? '?'} kpis=${kpis
.map((k) => `${k.label}:${k.value}${k.unit ?? ''}`)
.join(' ')}`,
);
} catch (err) {
failures++;
console.log(`FAIL ${label}: ${(err as Error).message.split('\n')[0]}`);
}
}
}
}
console.log(failures ? `\n${failures} failures` : '\nall checks passed');
await ds.destroy();
process.exit(failures ? 1 : 0);
}
void main();