docs(openspec): add reporting API contract documentation
Add comprehensive API documentation for the reporting endpoint: - Request/response structure - View type inference (did/is/will) - Blank vs explicit zero semantics - Status values and error responses Related to enhanced-allocation change.
This commit is contained in:
239
openspec/changes/enhanced-allocation/design.md
Normal file
239
openspec/changes/enhanced-allocation/design.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Design: Enhanced Allocation Fidelity
|
||||
|
||||
## Context
|
||||
|
||||
The allocation experience must reflect a strict planning-to-execution model. Current implementation drift introduced incorrect month semantics (`approved_estimate / 12`), mixed concern UIs, and ambiguous status signaling.
|
||||
|
||||
This design aligns implementation with the validated operating model:
|
||||
|
||||
1. **Projects (Lifecycle)**: `approved_estimate` is the lifecycle total.
|
||||
2. **Project-Month Plan (Intent)**: manager explicitly sets monthly planned effort per project.
|
||||
3. **Project-Resource Allocation (Execution)**: for selected month, manager allocates effort by team member.
|
||||
4. **Reporting (Outcome)**: historical/current/future insights built from these three layers.
|
||||
|
||||
## Goals
|
||||
|
||||
1. Implement explicit project-month planning as first-class data.
|
||||
2. Reframe allocation matrix as execution grid with month-plan and capacity variance.
|
||||
3. Support untracked effort (`team_member_id = null`) end-to-end.
|
||||
4. Implement partial bulk success for allocations.
|
||||
5. Keep visual signaling minimal and decision-focused.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Real-time collaboration/websockets.
|
||||
- Notification systems.
|
||||
- Export workflows.
|
||||
- Cosmetic UI experimentation beyond fidelity requirements.
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1: Month Plan Is Explicit (No Derived Monthly Budget)
|
||||
|
||||
- **Decision**: monthly plan is manager-entered and persisted.
|
||||
- **Rejected**: deriving monthly budget from `approved_estimate / 12`.
|
||||
- **Reason**: project phasing is dependency-driven, not uniform.
|
||||
|
||||
### D2: Reconciliation vs Lifecycle Total
|
||||
|
||||
For each project:
|
||||
- `plan_sum = sum(non-null planned_hours across months)`
|
||||
- compare against `approved_estimate`
|
||||
- `plan_sum > approved_estimate` -> `OVER`
|
||||
- `plan_sum < approved_estimate` -> `UNDER`
|
||||
- `plan_sum == approved_estimate` -> `MATCH`
|
||||
|
||||
Tolerance for MATCH should use decimal-safe comparison in implementation.
|
||||
|
||||
### D3: Blank Month Semantics
|
||||
|
||||
- Planning grid cell can be blank (`null`) and remains visually blank.
|
||||
- Allocation variance treats missing month plan as `0`.
|
||||
- Allocation is allowed when month plan is blank.
|
||||
|
||||
### D4: Grid-First Editing
|
||||
|
||||
- Project-month planning: inline grid editing primary.
|
||||
- Allocation matrix: inline grid editing primary.
|
||||
- Modal is optional/fallback, not primary path.
|
||||
|
||||
### D5: Untracked Semantics
|
||||
|
||||
- Untracked allocation is represented by `team_member_id = null`.
|
||||
- Included in project totals and grand totals.
|
||||
- Excluded from team member capacity/utilization calculations.
|
||||
|
||||
### D6: Visual Status Policy
|
||||
|
||||
- Over = red
|
||||
- Under = amber
|
||||
- Match/settled = neutral
|
||||
|
||||
Status emphasis belongs on row/column summary edges and variance cells; avoid noisy color in every interior cell.
|
||||
|
||||
### D7: Forecasted Effort Deprecation
|
||||
|
||||
- `projects.forecasted_effort` is deprecated for this planning workflow.
|
||||
- New monthly planning source of truth is explicit project-month plan data.
|
||||
|
||||
## Data Model
|
||||
|
||||
### New Table: `project_month_plans`
|
||||
|
||||
Recommended schema:
|
||||
- `id` (uuid)
|
||||
- `project_id` (uuid FK -> projects.id)
|
||||
- `month` (date, normalized to first day of month)
|
||||
- `planned_hours` (decimal, nullable)
|
||||
- `created_at`, `updated_at`
|
||||
- unique index on (`project_id`, `month`)
|
||||
|
||||
Notes:
|
||||
- `planned_hours = null` means blank/unset plan.
|
||||
- If storage policy prefers non-null planned hours, clear operation must still preserve blank UI semantics via delete row strategy.
|
||||
|
||||
### Existing Tables
|
||||
|
||||
- `projects.approved_estimate`: lifecycle cap (unchanged semantics)
|
||||
- `allocations.team_member_id`: nullable to support untracked
|
||||
|
||||
## API Design
|
||||
|
||||
### Project-Month Plan APIs
|
||||
|
||||
1. `GET /api/project-month-plans?year=YYYY`
|
||||
- returns month-plan grid payload by project/month
|
||||
- includes reconciliation status per project
|
||||
|
||||
2. `PUT /api/project-month-plans/bulk`
|
||||
- accepts multi-cell upsert payload
|
||||
- supports setting value and clearing value (blank)
|
||||
- returns updated rows + reconciliation results
|
||||
|
||||
Example payload:
|
||||
```json
|
||||
{
|
||||
"year": 2026,
|
||||
"items": [
|
||||
{ "project_id": "...", "month": "2026-01", "planned_hours": 1200 },
|
||||
{ "project_id": "...", "month": "2026-02", "planned_hours": 1400 },
|
||||
{ "project_id": "...", "month": "2026-03", "planned_hours": 400 },
|
||||
{ "project_id": "...", "month": "2026-04", "planned_hours": null }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Allocation APIs
|
||||
|
||||
- Keep `GET /api/allocations?month=YYYY-MM` and CRUD endpoints.
|
||||
- Enrich response with month-context variance metadata needed for row/column summary rendering.
|
||||
- `POST /api/allocations/bulk` must support partial success.
|
||||
|
||||
Partial bulk response contract:
|
||||
```json
|
||||
{
|
||||
"data": [{ "index": 0, "id": "...", "status": "created" }],
|
||||
"failed": [{ "index": 1, "errors": { "allocated_hours": ["..."] } }],
|
||||
"summary": { "created": 1, "failed": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
## Computation Rules
|
||||
|
||||
### Lifecycle Reconciliation (Project-Month Plan Surface)
|
||||
|
||||
For each project:
|
||||
- `plan_sum = SUM(planned_hours where month in planning horizon and value != null)`
|
||||
- `status = OVER | UNDER | MATCH` compared to `approved_estimate`
|
||||
|
||||
### Allocation Row Variance (Execution Surface)
|
||||
|
||||
For selected month and project:
|
||||
- `allocated_total = SUM(allocation.allocated_hours for project/month including untracked)`
|
||||
- `planned_month = planned_hours(project, month) or 0 if missing`
|
||||
- `row_variance = allocated_total - planned_month`
|
||||
- status from row_variance sign
|
||||
|
||||
### Allocation Column Variance (Execution Surface)
|
||||
|
||||
For selected month and team member:
|
||||
- `member_allocated = SUM(allocation.allocated_hours for member/month)`
|
||||
- `member_capacity = computed month capacity`
|
||||
- `col_variance = member_allocated - member_capacity`
|
||||
- exclude `team_member_id = null` rows from this computation
|
||||
|
||||
## UX Specification (Implementation-Oriented)
|
||||
|
||||
### Surface A: Project-Month Plan Grid
|
||||
|
||||
- Rows: projects
|
||||
- Columns: months
|
||||
- Right edge: row total + reconciliation status
|
||||
- Bottom edge: optional month totals for planning visibility
|
||||
- Cell editing: inline, keyboard-first
|
||||
- Blank cells remain blank visually
|
||||
- Color: only summary statuses (red/amber/neutral)
|
||||
|
||||
### Surface B: Project-Resource Allocation Grid (Selected Month)
|
||||
|
||||
- Rows: projects
|
||||
- Columns: team members + untracked
|
||||
- Right edge: project row total + row variance status vs planned month
|
||||
- Bottom edge: member totals + capacity variance status
|
||||
- Primary editing: inline cell edit (modal not primary)
|
||||
- Color: red/amber highlights only for over/under summary states
|
||||
|
||||
### Accessibility
|
||||
|
||||
- Do not rely on color alone; include text labels (OVER/UNDER).
|
||||
- Maintain focus and keyboard navigation in both grids.
|
||||
- Preserve contrast and readable status labels.
|
||||
|
||||
## Risk Register
|
||||
|
||||
1. **Semantic regression risk**: reintroduction of derived monthly budget logic.
|
||||
- Mitigation: explicit tests and prohibition in specs.
|
||||
|
||||
2. **Blank vs zero confusion**.
|
||||
- Mitigation: explicit API contract and UI behavior tests.
|
||||
|
||||
3. **Untracked leakage into capacity metrics**.
|
||||
- Mitigation: query filters and dedicated tests.
|
||||
|
||||
4. **Bulk partial side effects**.
|
||||
- Mitigation: per-item validation and clear response contract.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit
|
||||
- Lifecycle reconciliation calculator
|
||||
- Row/column variance calculators
|
||||
- Blank-plan-as-zero execution logic
|
||||
|
||||
### Feature/API
|
||||
- Project-month plan bulk upsert and clear behavior
|
||||
- Reconciliation status correctness
|
||||
- Allocation month variance data correctness
|
||||
- Untracked include/exclude behavior
|
||||
- Partial bulk success semantics
|
||||
|
||||
### Frontend Grid Tests
|
||||
- Inline edit commit/cancel/clear
|
||||
- Keyboard navigation
|
||||
- Summary status placement and labels
|
||||
- Blank visual state preservation
|
||||
|
||||
### E2E
|
||||
- Create lifecycle estimate project
|
||||
- Enter monthly plan across months and verify reconciliation
|
||||
- Execute month allocations and verify row/column variances
|
||||
- Validate untracked behavior
|
||||
- Validate partial bulk success handling
|
||||
|
||||
## Migration & Rollout Notes
|
||||
|
||||
1. Introduce project-month plan model and APIs.
|
||||
2. Remove derived budget rendering in allocation UI.
|
||||
3. Wire allocation UI to explicit month plan for variance.
|
||||
4. Deprecate `forecasted_effort` usage in this workflow path.
|
||||
5. Keep backward compatibility for existing allocation records.
|
||||
Reference in New Issue
Block a user