# CalculatedTimeBlock

## Description

CalculatedTimeBlock is the **calculated time block** — the top, derived layer of the pipeline and the **only layer payroll, costing, and Article-36 agreement monitoring are permitted to read** (ADR-014). It is produced by applying the effective work-rules (ADR-015) to the current ReportedTimeBlocks: the reported intervals are decomposed into categorized spans — regular (within-scheduled), overtime, late-night, holiday — each expressed as integer `minutes` under a `timeEntryCodeKey`. The `category` is an open, normalized string key produced by the calculation strategy; the default JP strategy emits `REGULAR` / `OVERTIME` / `NIGHT` / `HOLIDAY`.

It is explicitly **not a source of truth**: it is a cache-like projection that can be discarded and re-derived at any time from `sourceReportedBlockIds` plus the WorkRule effective on its `workDate`. Each block records `calculationTagKeys` — the semantic keys of the rules that fired — so any single block's result can be traced back to "which reported blocks, under which rules," reconstructing the UKG Rule Analysis / Workday Calculation Debugger view that matters for audit and labor disputes (issue #7). Rules are referenced strictly **by key**, never by display name, so renaming a code never changes a calculation (ADR-015, the #14 cautionary lesson). Multi-dimensional worktag allocation is deferred (ADR-018); the single-dimension `costObjectRef` seam that once stood in for it was removed in #39 (it was always null) — a nullable allocation column can be re-added later when the seam is needed.

## Domain Model Definitions

### Model type

Standard

### Command Definitions

Command docs are out of scope for this design phase (ADR-011). Anticipated commands:

- calculateTimeBlocks — derive/refresh CalculatedTimeBlocks for an Assignment and workday from current ReportedTimeBlocks plus the effective rules
- recalculateRange — re-derive a date range on retroactive correction or rule change, discarding and regenerating the affected blocks (ADR-013)

### Query Definitions

- getCalculatedBlock — retrieve a single calculated block by id
- listCalculatedBlocksByWorkDate — calculated blocks for an Assignment on a workday, paginated
- listCalculatedBlocksByTimeEntryCode — blocks of a given timeEntryCodeKey over a date range (payroll / Article-36 agreement aggregation input), paginated
### Models

- CalculatedTimeBlock

### Invariants

- Never a source of truth: a CalculatedTimeBlock is always re-derivable from its `sourceReportedBlockIds` plus the work-rules effective on its `workDate`, and may be discarded and regenerated without data loss (ADR-014)
- `minutes` is a non-negative integer count of minutes (no float hours)
- `timeEntryCodeKey` and `payCodeKey` reference work-rules TimeEntryCode / pay code by stable key, never by display name; a label change never alters a calculation (ADR-015)
- `payCodeKey` is an intentional denormalization: it is a straight copy of the resolved TimeEntryCode's `payMapKey` at derivation time, stamped onto the block so downstream reads need not re-resolve the code
- `category` is an open, normalized string key produced by the calculation strategy (the default JP strategy emits `REGULAR` / `OVERTIME` / `NIGHT` / `HOLIDAY`), never a fixed/closed enum
- `calculationTagKeys` records every rule/tag that fired to produce the block, sufficient to reconstruct the block's derivation for audit (traceability)
- `sourceReportedBlockIds` references only current (non-superseded) ReportedTimeBlocks; a superseded reported block is never the source of a live calculated block
- Every block references exactly one workforce `Assignment` (via `assignmentId`) effective on its `workDate`
- A retroactive correction or an effective-dated rule change over a period invalidates and triggers re-derivation of the CalculatedTimeBlocks in that period (ADR-013, ADR-014)
- `workRuleId` is the exact WorkRule generation effective on `workDate` at calculation time; it may point at a generation later superseded by a retroactive correction (ADR-013)
- Payroll, costing, and Article-36 agreement read this layer exclusively; they never read TimeClockEvent or ReportedTimeBlock directly

### Relationships

- **References Assignment** (workforce): `assignmentId` references the Assignment effective on `workDate`, establishing "whose" calculated time this is
- **Derived from ReportedTimeBlock**: `sourceReportedBlockIds` lists the current reported blocks this calculation consumed
- **References TimeEntryCode / WorkRule** (work-rules): `timeEntryCodeKey` and `payCodeKey` bind the block to rule definitions by key; `calculationTagKeys` names the WorkRule fragments that fired
- **References WorkRule** (work-rules): `workRuleId` is the exact generation applied — `calculationTagKeys` names which of its fragments fired, `workRuleId` names the generation itself
- **Aggregated by Timecard**: a Timecard's per-category minute totals sum the CalculatedTimeBlocks whose `workDate` falls in its period
- **Read by downstream domains**: payroll earnings, costing, and Article-36 agreement threshold evaluation consume this layer only (never the raw/reported layers)
- **Allocation seam**: multi-dimensional worktag distribution is deferred (ADR-018); the `costObjectRef` single-dimension hook was removed in #39 as always-null, and a nullable allocation column can be re-added when the seam is needed
