# Task Estimates & Burndown

**Date:** 2026-05-31  
**Status:** Draft  
**Spec:** 112  
**Tier:** All tiers (burndown chart: Business+)  
**Depends on:** `tasks-board-engine`, `time-management`, `projects-module`, `foundation-auth-rbac`  
**Referenced by:** `tasks-board-engine`, `projects-module`

---

## Overview

Spec 8 (`tasks-board-engine`) already has `tasks.estimated_hours NUMERIC(6,2)`. This spec adds estimation UX (inline edit, bulk set), links actual hours from `time_entries` to task estimates, and provides a burndown chart on the project overview page.

---

## Estimate Display on Task Card

Task cards (Kanban and list view) show estimated vs actual hours when `estimated_hours` is set:

```
┌──────────────────────────────────────────────────────────────┐
│  ☐  Implement auth flow                           [Assignee] │
│     ↳ 6h est / 4.5h logged  [████████░░] 75%                │
└──────────────────────────────────────────────────────────────┘
```

Progress bar: `actual / estimated * 100`. Turns red when actual > estimated (over-budget).

`actual_hours` computed via `SELECT SUM(duration_seconds)/3600.0 FROM time_entries WHERE task_id = :id`.

---

## Inline Estimate Edit

Click on estimate badge → popover:

```
│  Estimated hours: [6.0___]  [Save]  [Cancel]                │
```

`PATCH /api/tasks/:id` with `{ estimated_hours: 6.0 }`. Requires `tasks:write`.

---

## Bulk Estimate Entry

On project task list view, **[Set estimates]** button opens a bulk-edit mode:

```
┌──────────────────────────────────────────────────────────────┐
│  Set estimates — Website Redesign              [Done editing] │
│                                                              │
│  Task                           Estimated    Logged          │
│  Implement auth flow            [6.0 h]      4.5h            │
│  Design homepage                [8.0 h]      8.0h            │
│  Write user guide               [3.0 h]      0h              │
│  QA testing                     [4.0 h]      —               │
└──────────────────────────────────────────────────────────────┘
```

Inline number inputs, saved on blur (individual `PATCH` per task). Tab-key navigation through rows.

---

## Project Burndown Chart (Business+)

On the project overview page (`/projects/:id`), **Burndown** tab:

```
┌──────────────────────────────────────────────────────────────┐
│  Burndown                                                    │
│                                                              │
│  Sprint: [Current ▾]   [May 20 – Jun 3]                      │
│                                                              │
│  Total estimated: 82h    Logged: 54h    Remaining: 28h       │
│                                                              │
│  Hours ▲                                                     │
│  80 │╲                                                       │
│  60 │ ╲ ideal                                                │
│  40 │  ╲___                                                  │
│  20 │      ╲  actual                                         │
│   0 └──────────────────────→ days                            │
│     May20  May24  May28  Jun1  Jun3                          │
│                                                              │
│  Ideal: straight line from total → 0 over sprint duration    │
│  Actual: daily remaining (total_est - logged_to_date)        │
└──────────────────────────────────────────────────────────────┘
```

Chart library: Recharts (already in stack). Two lines: ideal and actual remaining.

---

## Burndown Data API

```
GET /api/projects/:id/burndown?from=2026-05-20&to=2026-06-03
    → {
        totalEstimatedHours: number,
        days: [{
          date: string,           // YYYY-MM-DD
          loggedCumulative: number,
          remaining: number
        }]
      }
```

Server aggregates `time_entries` by day (`SUM(duration_seconds)/3600.0 GROUP BY date_trunc('day', started_at)`), then computes running `remaining = totalEstimated - loggedCumulative`.

---

## Schema Delta

No new columns. `tasks.estimated_hours` already exists (spec 8). Burndown uses existing `time_entries` data with `task_id` join.

---

## Estimation Summary on Project Overview

Existing project overview (spec 3 `projects-module`) gets an **Estimates** summary bar:

```
│  Tasks: 12   Estimated: 82h   Logged: 54h   Remaining: 28h  │
│  Budget consumed: 66%   On track ✓                           │
```

"On track" = `logged <= elapsed_pct * totalEstimated` (linear expectation). "At risk" if > 20% over pace.

---

## API

```
PATCH /api/tasks/:id
      → existing endpoint; accepts { estimated_hours }
        Requires: tasks:write

GET /api/projects/:id/tasks
    → extended: includes actual_hours (computed) alongside estimated_hours
      Requires: tasks:read

GET /api/projects/:id/burndown
    → daily burndown data
      query: { from, to }
      Requires: tasks:read (Business+)

GET /api/projects/:id/summary
    → extended: includes { totalEstimated, totalLogged, remaining }
      Requires: tasks:read
```

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| actual_hours computed at query time | Not stored column | Stored would require maintaining on every time entry write; computed is accurate and cheap at task scale |
| Burndown Business+ | Not all tiers | Burndown implies sprint/project management workflows; basic task management is sufficient for starter tiers |
| Two-line chart (ideal + actual) | Not single remaining line | Ideal line is the reference; without it, "is remaining good or bad?" is ambiguous — context is everything in burndown |
