# 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:
```json
{
  "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:
```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.
