Files
Santhosh Janardhanan b8262bbcaf docs(openspec): archive completed changes and sync main specs
Archive three completed changes to archive/:
- api-resource-standard (70 tasks, 14 resource classes)
- capacity-expert-mode (68 tasks, expert mode planning grid)
- enhanced-allocation (62 tasks, planning fidelity + reporting)

Sync all delta specs to main specs/:
- api-resource-standard: API response standardization
- capacity-expert-mode: Expert mode toggle, grid, KPIs, batch API
- resource-allocation: Month execution comparison, bulk, untracked
- untracked-allocation: Null team member support
- allocation-indicators: Variance indicators (red/amber/neutral)
- monthly-budget: Explicit project-month planning

All changes verified and tested (157 tests passing).
2026-03-08 19:13:28 -04:00

161 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## ADDED Requirements
### Requirement: Toggle Expert Mode
The system SHALL provide a toggle switch on the Capacity Planning page that enables or disables Expert Mode. The toggle SHALL be persisted in `localStorage` under the key `headroom.capacity.expertMode` so the user's preference survives page reloads.
#### Scenario: Toggle defaults to off
- **WHEN** a user visits the Capacity Planning page for the first time
- **THEN** Expert Mode is off and the standard calendar view is shown
#### Scenario: Toggle persists across reloads
- **WHEN** a user enables Expert Mode and reloads the page
- **THEN** Expert Mode is still enabled and the grid view is shown
#### Scenario: Toggle is right-aligned on the tabs row
- **WHEN** the Capacity Planning page is rendered
- **THEN** the Expert Mode toggle appears right-aligned on the same row as the Calendar, Summary, Holidays, and PTO tabs
#### Scenario: Switching mode with unsaved changes warns user
- **WHEN** a user has dirty (unsaved) cells in the Expert Mode grid
- **AND** the user toggles Expert Mode off
- **THEN** the system shows a confirmation dialog: "You have unsaved changes. Discard and switch?"
- **AND** if confirmed, changes are discarded and the calendar view is shown
---
### Requirement: Display Expert Mode planning grid
The system SHALL render a dense planning grid when Expert Mode is enabled. The grid SHALL show all active team members as rows and all days of the selected month as columns.
#### Scenario: Grid shows all active team members
- **WHEN** Expert Mode is enabled for a given month
- **THEN** each active team member appears as a row in the grid
- **AND** inactive team members are excluded
#### Scenario: Grid shows all days of the month as columns
- **WHEN** Expert Mode is enabled for February 2026
- **THEN** the grid has 28 columns (one per calendar day)
- **AND** each column header shows the day number
#### Scenario: Weekend columns are visually distinct
- **WHEN** the grid is rendered
- **THEN** weekend columns (Saturday, Sunday) are visually distinguished (e.g. muted background)
#### Scenario: Holiday columns are visually distinct
- **WHEN** a day in the month is a company holiday
- **THEN** that column header is visually marked as a holiday
#### Scenario: Grid loads existing availability data
- **WHEN** Expert Mode grid is opened for a month where availability overrides exist
- **THEN** each cell pre-populates with the stored token matching the saved availability value
---
### Requirement: Cell token input and validation
The system SHALL accept exactly the following tokens in each grid cell: `H`, `O`, `0`, `.5`, `0.5`, `1`. Any other value SHALL be treated as invalid.
#### Scenario: Valid token accepted on blur
- **WHEN** a user types `1` into a cell and moves focus away
- **THEN** the cell displays `1` and is marked valid
#### Scenario: Valid token `.5` normalized on blur
- **WHEN** a user types `.5` into a cell and moves focus away
- **THEN** the cell displays `0.5` and is marked valid with numeric value `0.5`
#### Scenario: `H` and `O` accepted on any date
- **WHEN** a user types `H` or `O` into any cell (weekend, holiday, or working day)
- **THEN** the cell is marked valid with numeric value `0`
- **AND** the display shows the typed token (`H` or `O`)
#### Scenario: Invalid token marked red on blur
- **WHEN** a user types `2` or `abc` or any value not in the allowed set into a cell and moves focus away
- **THEN** the cell border turns red
- **AND** the raw text is preserved so the user can correct it
#### Scenario: Submit disabled while invalid cell exists
- **WHEN** any cell in the grid has an invalid token
- **THEN** the Submit button is disabled
#### Scenario: `0` auto-renders as `O` on weekend column
- **WHEN** a user types `0` into a cell whose column is a weekend day and moves focus away
- **THEN** the cell displays `O` (not `0`)
- **AND** the numeric value is `0`
#### Scenario: `0` auto-renders as `H` on holiday column
- **WHEN** a user types `0` into a cell whose column is a company holiday and moves focus away
- **THEN** the cell displays `H` (not `0`)
- **AND** the numeric value is `0`
---
### Requirement: Live KPI bar in Expert Mode
The system SHALL display a live KPI bar above the grid showing team-level Capacity (person-days) and Projected Revenue, updating in real time as cell values change.
#### Scenario: KPI bar shows correct capacity on load
- **WHEN** Expert Mode grid loads for a month
- **THEN** the KPI bar shows total team capacity in person-days matching the sum of all members' numeric cell values
#### Scenario: KPI bar updates when a cell changes
- **WHEN** a user changes a valid cell from `1` to `0.5`
- **THEN** the KPI bar immediately reflects the reduced capacity and revenue without a page reload
#### Scenario: Invalid cells excluded from KPI totals
- **WHEN** a cell contains an invalid token
- **THEN** that cell contributes `0` to the KPI totals (not `null` or an error)
#### Scenario: Projected Revenue uses hourly rate and hours per day
- **WHEN** the KPI bar calculates projected revenue
- **THEN** revenue = SUM(member capacity in person-days × hourly_rate × 8 hours/day) for all active members
---
### Requirement: Batch save availability from Expert Mode
The system SHALL allow saving all pending cell changes in a single batch operation via a Submit button.
#### Scenario: Submit saves all dirty valid cells
- **WHEN** a user has changed multiple cells and clicks Submit
- **THEN** the system sends a single batch request with all dirty cell values
- **AND** on success, all dirty flags are cleared and a success toast is shown
#### Scenario: Submit is disabled when no dirty cells exist
- **WHEN** no cells have been changed since the last save (or since load)
- **THEN** the Submit button is disabled
#### Scenario: Submit is disabled when any invalid cell exists
- **WHEN** at least one cell contains an invalid token
- **THEN** the Submit button is disabled regardless of other valid dirty cells
#### Scenario: Submit failure shows error
- **WHEN** the batch save API call fails
- **THEN** the system shows an error alert
- **AND** dirty flags are preserved so the user can retry
#### Scenario: Batch endpoint validates each availability value
- **WHEN** the batch endpoint receives an availability value not in `[0, 0.5, 1]`
- **THEN** the system returns HTTP 422 with a validation error message
---
### Requirement: Batch availability API endpoint
The system SHALL expose `POST /api/capacity/availability/batch` accepting an array of availability updates for a given month.
#### Scenario: Batch endpoint saves multiple updates
- **WHEN** a POST request is made to `/api/capacity/availability/batch` with a valid month and array of updates
- **THEN** the system upserts each `{team_member_id, date, availability}` entry
- **AND** returns HTTP 200 with `{ "data": { "saved": <count>, "month": "<YYYY-MM>" } }`
#### Scenario: Batch endpoint invalidates cache once
- **WHEN** a batch save completes for a given month
- **THEN** the month-level capacity cache is flushed exactly once (not once per row)
#### Scenario: Batch endpoint rejects invalid team_member_id
- **WHEN** a batch request contains a `team_member_id` that does not exist
- **THEN** the system returns HTTP 422 with a validation error
#### Scenario: Batch endpoint rejects invalid availability value
- **WHEN** a batch request contains an `availability` value not in `[0, 0.5, 1]`
- **THEN** the system returns HTTP 422 with a validation error
#### Scenario: Empty batch is a no-op
- **WHEN** a POST request is made with an empty `updates` array
- **THEN** the system returns HTTP 200 with `{ "data": { "saved": 0, "month": "<YYYY-MM>" } }`