# GetShiftVariance

## Overview

getShiftVariance computes the planned-vs-actual variance report for a Shift from planned facts, placements, and actuals the caller has already read. It is a pure computation — it performs no database reads. It exists as a shiftSchedule module query so the domain's planned-minutes definition and variance-classification thresholds (`computePlannedMinutes` / `classifyPlacementVariance`) are owned and unit-tested in the module rather than embedded in the app resolver (issue #38). The caller owns the Shift and ShiftPlacement reads and the cross-module CalculatedTimeBlock read, so nothing is re-queried here.

## Business Rules

- Planned working minutes = the sum over the Shift's segments of each segment's gross planned span minus its break (`computePlannedMinutes`)
- Each placement is classified against the supplied actuals with the priority order: PENDING (no actuals) → OVERTIME (worked more than planned) → LATE_ARRIVAL (first actual block starts after the planned start) → EARLY_LEAVE (last actual block ends before the planned end) → ON_TIME
- Actual CalculatedTimeBlocks are supplied by the caller keyed by Assignment. CalculatedTimeBlock is owned by time-tracking; the app resolver composes that cross-module read (shiftSchedule can neither query that table nor receive an injected query — erp-kit queries take no dependencies) and passes the actuals in
- A placement with no supplied actuals is reported PENDING with zero actual minutes
- Each placement is reported individually, never combined into one total

## Process Flow

```mermaid
flowchart TD
    A[Receive planned envelope + segments + placements + actuals per Assignment] --> B[Compute planned minutes from segments]
    B --> C[Classify each placement against its supplied actuals]
    C --> D[Return per-placement variance report]
```

## External Dependencies

- [shiftSchedule::Shift](../model/Shift.md) model — the planned envelope and segments the caller reads and passes in
- [shiftSchedule::ShiftPlacement](../model/ShiftPlacement.md) model — the placements the caller reads and passes in

## Error Scenarios

- None — the query is a pure computation over the supplied facts and does not read the database

## Test Cases

- reports PENDING for a placement with no actual blocks
- reports OVERTIME when actual minutes exceed planned
- reports LATE_ARRIVAL when the first actual block starts after the planned start
- reports EARLY_LEAVE when arrival is on time but the last actual block ends before the planned end
- reports ON_TIME when the placement matches the planned envelope
- reports each placement individually, never combined
