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).
8.9 KiB
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:
- Projects (Lifecycle):
approved_estimateis the lifecycle total. - Project-Month Plan (Intent): manager explicitly sets monthly planned effort per project.
- Project-Resource Allocation (Execution): for selected month, manager allocates effort by team member.
- Reporting (Outcome): historical/current/future insights built from these three layers.
Goals
- Implement explicit project-month planning as first-class data.
- Reframe allocation matrix as execution grid with month-plan and capacity variance.
- Support untracked effort (
team_member_id = null) end-to-end. - Implement partial bulk success for allocations.
- 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_estimateplan_sum > approved_estimate->OVERplan_sum < approved_estimate->UNDERplan_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_effortis deprecated for this planning workflow.- New monthly planning source of truth is explicit project-month plan data.
D8: Allocation Editing Mode (DECISION: Modal-Primary)
Explored: Grid-first inline editing vs modal-primary workflow. Decision: Modal-primary editing is acceptable for this release. Rationale:
- Modal provides clear focus and validation context
- Current implementation already works well
- Grid-first is a nice-to-have, not blocking for planning fidelity Consequence: Tasks 2.12-2.13 (grid-first editing) intentionally skipped.
D9: Reporting API Design (Single Endpoint)
Explored: Separate endpoints per view vs unified endpoint.
Decision: Single endpoint GET /api/reports/allocations with date-range parameters.
View Type Inference: Backend determines "did/is/will" from date range:
did= past months (all dates < current month)is= current month (dates include current month)will= future months (all dates > current month) Response Shape: Unified structure regardless of view:
{
"period": { "start": "2026-01-01", "end": "2026-03-31" },
"view_type": "is",
"projects": [...],
"members": [...],
"aggregates": { "total_planned", "total_allocated", "total_variance", "status" }
}
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 = nullmeans 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
-
GET /api/project-month-plans?year=YYYY- returns month-plan grid payload by project/month
- includes reconciliation status per project
-
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:
{
"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-MMand CRUD endpoints. - Enrich response with month-context variance metadata needed for row/column summary rendering.
POST /api/allocations/bulkmust support partial success.
Partial bulk response contract:
{
"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 | MATCHcompared toapproved_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 missingrow_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 capacitycol_variance = member_allocated - member_capacity- exclude
team_member_id = nullrows 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
-
Semantic regression risk: reintroduction of derived monthly budget logic.
- Mitigation: explicit tests and prohibition in specs.
-
Blank vs zero confusion.
- Mitigation: explicit API contract and UI behavior tests.
-
Untracked leakage into capacity metrics.
- Mitigation: query filters and dedicated tests.
-
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
- Introduce project-month plan model and APIs.
- Remove derived budget rendering in allocation UI.
- Wire allocation UI to explicit month plan for variance.
- Deprecate
forecasted_effortusage in this workflow path. - Keep backward compatibility for existing allocation records.