feat(reports): record loading and unloading time per stop

The OCC report publishes total loading and unloading time and the other
activity left over from a station stay, but nothing recorded when handling
started or ended — the July 2026 seed had to write the figure into a
checkpoint note. Four nullable stamps now ride the stop's arrival row, which
is the row the staying-time report builds a stay from (a turnaround's
departure belongs to a different schedule).

Handling is unloading start to loading end, so a container stop reads as one
window and a bulk station that only loads or only unloads still reports its
half; other activity is the rest of the stay. Both stay NULL where nothing
was logged rather than collapsing to zero.

- station-staying-time: + loading/unloading and other activity per stop
- turnaround-cycle: + the same, summed over the cycle's stops
- loading-unloading (new): per train per station per period, so a week or
  month view is that train's average over its stops
- the stop/stay query moves to operations-classification, shared by both
This commit is contained in:
Nathnael
2026-08-24 12:09:04 +00:00
parent 2286135228
commit 17c2dca3cc
16 changed files with 763 additions and 106 deletions

View File

@@ -0,0 +1,41 @@
import { MigrationInterface, QueryRunner } from "typeorm";
/**
* Loading and unloading times, on the stop that already records the train
* standing at a station.
*
* The OCC report publishes, per train, "total loading and unloading time" and
* the "other activity" left over from the station stay. Nothing recorded when
* handling started or ended — the July 2026 seed had to write the figure into
* a checkpoint's note — so the staying-time report could only ever publish the
* whole stay.
*
* These four go on `train_checkpoint_events` rather than a table of their own:
* a stop is already one row there, keyed (schedule, sequence_no), and the
* arrival row is the one the staying-time report builds a stay from. All four
* are nullable — a stop where nobody logged the handling still reports its
* staying time, with the handling columns empty rather than zero.
*/
export class CheckpointHandlingTimes3690000000000 implements MigrationInterface {
name = "CheckpointHandlingTimes3690000000000";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.train_checkpoint_events
ADD COLUMN IF NOT EXISTS unloading_started_at timestamptz,
ADD COLUMN IF NOT EXISTS unloading_completed_at timestamptz,
ADD COLUMN IF NOT EXISTS loading_started_at timestamptz,
ADD COLUMN IF NOT EXISTS loading_completed_at timestamptz;
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.train_checkpoint_events
DROP COLUMN IF EXISTS unloading_started_at,
DROP COLUMN IF EXISTS unloading_completed_at,
DROP COLUMN IF EXISTS loading_started_at,
DROP COLUMN IF EXISTS loading_completed_at;
`);
}
}