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

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:

  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.

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 = 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:

{
  "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:

{
  "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.