# Health Engine Audit

Audit date: 2026-06-05  
Scope: Project Health, Executive Dashboard, Portfolio Health, Forecast Completion, Sprint Health, Risk Exposure

---

## 1. Project Health (`ProjectHealthService`)

### Formula

When **ready** (backlog work items exist):

```
score = round(Σ component_score × weight)
```

| Component | Weight | Method |
|-----------|--------|--------|
| Sprint Delivery | 25% | `sprintDeliveryScore()` |
| Velocity Trend | 15% | `velocityTrendScore()` |
| Risks | 20% | `riskScore()` |
| Dependencies | 15% | `dependencyScore()` |
| Milestones | 15% | `milestoneScore()` |
| Capacity | 10% | `capacityScore()` |

### Initializing gate

Returns **no score** when:

- No sprints **and** no backlog work items (`work_type` set), OR
- No backlog work items at all (terminal/completed projects exempt)

```json
{
  "score": null,
  "level": "initializing",
  "message": "Start planning work to generate project health metrics.",
  "ready": false
}
```

### Level thresholds

| Score | Level |
|-------|-------|
| ≥ 75 | `healthy` |
| ≥ 60 | `attention` |
| < 60 | `critical` |

### Inputs

- Project sprints (active/completed)
- Tickets with `work_type` (backlog + sprint items)
- Open `ProjectRisk` records (probability × impact)
- `TicketDependency` chains (unfinished source blocks target)
- `ProjectMilestone` overdue dates
- `ProjectWorkloadService` overload status
- `SprintCapacityService` capacity exceeded flag
- Project `due_date` (overdue factor)

### Outputs

- `score`, `level`, `message`, `ready`, `components`, `factors`, `weights`
- Snapshots via `ProjectHealthSnapshotService` (skipped when `initializing`)
- `ProjectHealthUpdated` event (skipped when `initializing`)

### Contributing factors (`factors[]`)

| Factor key | Condition |
|------------|-----------|
| `sprint_delivery_behind` | sprint_delivery component < 60 |
| `velocity_declining` | velocity_trend component < 60 |
| `critical_risk` | open risk with score ≥ 20 |
| `elevated_risk` | risks component < 70 |
| `blocked_dependency` | dependencies component < 70 |
| `overdue_milestone` | milestones component < 70 |
| `capacity_overload` | capacity component < 70 |
| `project_overdue` | project past due date |

### Alert gate (`shouldAlert`)

Alerts fire only when:

- `level !== initializing`
- `score` is not null
- `score < 60`
- `factors` array is non-empty

---

## 2. Sprint Health (`SprintHealthEngineService`)

### Formula

```
score = 100
score -= min(40, max(0, utilization - 100) × 2)
score -= min(25, blocked_items × 8)
score -= min(25, overdue_items × 6)
score -= sprint_overdue ? 20 : 0
score -= velocity_declining ? 10 : 0
score = clamp(0, 100)
```

### Status thresholds

| Score | Status |
|-------|--------|
| ≥ 80 | `good` |
| ≥ 60 | `warning` |
| ≥ 40 | `at_risk` |
| < 40 | `critical` |

### Inputs

- Sprint tickets (committed/completed MD, kanban/status, due dates)
- `SprintTeamCapacityService` utilization
- `VelocityService` trend (last closed sprints)

### Outputs

- `status`, `score`, `blocked_items`, `overdue_items`, `capacity_utilization_percent`, `sprint_progress_percent`, `velocity_trend`, `recommendation`

---

## 3. Executive Dashboard (`ExecutiveDashboardService`)

Aggregates:

- **Health**: `ProjectHealthService::calculate()` + `explainHealth()` + component breakdown
- **Risk summary/trend**: open/critical counts, 30-day history
- **Velocity**: average MD, trend, per-sprint history
- **Burndown**: active sprint snapshot
- **Blocked work / dependencies**: unresolved FS chains
- **Milestones**: upcoming + overdue
- **Sprint status**: active sprint progress
- **Capacity utilization**: team MD vs assigned
- **Forecast completion**: remaining work ÷ velocity → projected date vs due date
- **Attention items**: composite list (risks, blockers, overdue, sprint health)

### Forecast completion

```
remaining_md = open work estimate sum
days_remaining = remaining_md / (avg_velocity_md / 5)  // business days
forecast_date = today + days_remaining
is_late = forecast_date > project.due_date
```

Returns `can_forecast: false` when insufficient velocity history.

---

## 4. Portfolio Health (`PortfolioService`)

Per visible project:

- Full health payload from `ProjectHealthService`
- `velocity_average` (last 3 closed sprints)
- `overdue_count` from `ProjectOverdueService`
- `completion_percentage` from `Project::completionPercentage()`

Portfolio Gantt (`PortfolioGanttService`) maps levels:

- `healthy` → `healthy`
- `attention` → `at_risk` (UI label)
- `critical` → `critical`
- `initializing` → passed through as-is

---

## 5. Risk Exposure

Computed in:

- `ProjectHealthService::riskScore()` — deducts per open risk
- `ExecutiveDashboardService::riskSummary()` — counts by severity
- `ProjectReportsController` — portfolio risk matrix rows

Critical risk: `probability × impact ≥ 20` and status `open`.

---

## 6. Velocity Factors

`VelocityService::projectVelocity()`:

- Uses last N **completed** sprints
- `trend`: compares last two sprint completed-MD values (`up` / `down` / `stable`)
- Affects project health (15% weight) and sprint health (-10 penalty when declining)

---

## 7. Dependency Factors

`dependencyScore()`:

- Counts dependencies where source ticket is not completed
- Each blocker reduces score by up to 20 points (cap 85 deduction, floor 15)

---

## 8. Sprint Factors

`sprintDeliveryScore()`:

- **Active sprint**: done/total ticket ratio; -25 if sprint overdue
- **Last completed sprint**: completion ratio (min 40)
- **No sprint**: returns 65 (neutral-negative)

---

## Key files

| Area | File |
|------|------|
| Project health | `app/Modules/Projects/Services/ProjectHealthService.php` |
| Health snapshots/events | `app/Modules/Projects/Services/ProjectHealthSnapshotService.php` |
| Sprint health | `app/Modules/Projects/Services/SprintHealthEngineService.php` |
| Executive dashboard | `app/Modules/Projects/Services/ExecutiveDashboardService.php` |
| Portfolio | `app/Modules/Projects/Services/PortfolioService.php` |
| Velocity | `app/Modules/Projects/Services/VelocityService.php` |
