fix(report): measure events against the schedule they ran on

The runtime report joined live report data against the current rundown,
so editing an event after the show silently rewrote the report of a show
that had already happened. Durations, and the over/under chip in the
rundown, would change to match the edit.

Each event now records the schedule it actually ran on, and the report
is read from that.

- OntimeEventReport carries scheduledStart and scheduledDuration,
  captured when the event starts, plus a playCount so an event started
  twice no longer overwrites its own record without trace.
- The over/under calculation moves to ontime-utils, where the rundown
  chip and the settings panel share one implementation instead of
  deriving it separately and disagreeing.
- The report panel gains a summary of the show: planned against actual,
  drift, and how many events landed over, under or on time.
- Export CSV was rendering a trash bin icon.

No change to how or where anything is stored: the report stays in memory
exactly as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019nr3FbLbM8gB8Jm771YgTV
This commit is contained in:
Claude
2026-08-09 19:19:02 +00:00
parent a851414d13
commit 05cf7c7c56
29 changed files with 223 additions and 1761 deletions
@@ -7,20 +7,21 @@ export type OntimeEventReport = {
/**
* Snapshot of the schedule taken when the event ran.
* Keeping a copy is what makes a report a record: editing the rundown
* afterwards no longer rewrites history.
* afterwards no longer changes how a show that already happened is reported.
*/
scheduledStart: number;
scheduledDuration: number;
/** how many times the event was started within this run, >1 means it was re-run */
/** how many times the event was started, >1 means it was re-run */
playCount: number;
};
export type OntimeReport = Record<EntryId, OntimeEventReport>;
/** Headline numbers for everything in the current report */
export type RunSummary = {
/** events which produced a report entry */
eventsRun: number;
/** playable events in the rundown at the time the summary was made */
/** playable events in the rundown */
eventsPlanned: number;
scheduledDuration: number;
actualDuration: number;
@@ -32,38 +33,3 @@ export type RunSummary = {
/** largest single overrun, answers "what blew the schedule" */
worstOverrun: { id: EntryId; delta: number } | null;
};
export type ShowRun = {
id: string;
rundownId: string;
/**
* Denormalised so a run stays readable after its rundown
* is renamed or deleted.
*/
rundownTitle: string;
/** user editable, defaults to a formatted local date and time */
label: string;
/**
* Wall clock instant (milliseconds from epoch) the run began.
* Not a time of day: runs must be datable and orderable across days.
*/
startedAt: number;
/**
* Wall clock instant the run was finished.
* Always set: a run only becomes a report once it has been finished, so
* there is no such thing as a stored run still in progress.
*/
endedAt: number;
report: OntimeReport;
summary: RunSummary;
};
/** A run without its per event data, for list views */
export type ShowRunSummary = Omit<ShowRun, 'report'>;
/**
* The run currently being recorded.
* Lives in memory only until it is finished, so it carries no report data
* and has no end.
*/
export type OpenRun = Omit<ShowRun, 'report' | 'summary' | 'endedAt'>;
+1 -8
View File
@@ -24,14 +24,7 @@ export { TimerType } from './definitions/TimerType.type.js';
export type { Day, Duration, Instant, TimeOfDay } from './definitions/core/Temporal.js';
// ---> Report
export type {
OntimeReport,
OntimeEventReport,
OpenRun,
RunSummary,
ShowRun,
ShowRunSummary,
} from './definitions/core/Report.type.js';
export type { OntimeReport, OntimeEventReport, RunSummary } from './definitions/core/Report.type.js';
// ---> Automations
export { ontimeActionKeyValues } from './definitions/core/Automation.type.js';