# Feature Spec - one page, written *before* any code

> **Who fills this in:** Product (with input from the table). **When:** in the first 10 minutes, before Gate 1. **Why:** this is [Way #1: Describe before you generate](../../ways-of-working.md) - the contract and the tests are written *from* this page, not the other way round. Keep it to one page. If you can't fill it in, you're not ready to build (`QUAL-PRINC-SHIFT-LEFT` - prevent defects at inception).

Delete the quote-blocks before you hand it to the team. The worked snippet at the bottom is for the `/recommend` endpoint - copy its shape, don't copy its content.

---

## 1. Problem - one sentence
*What can't the user do today? Whose pain is this?*

> e.g. "Alice can see her own assessment scores, but has no way to tell whether she's ahead of or behind her team."

## 2. Consumers - who calls this, and what they do with the answer
*Name the caller and the screen/job. This is the API-as-a-product mindset (`REQ-API-1`). One endpoint serves real consumers - list them.*

| Consumer | What they do with the response |
|---|---|
| | |
| | |

## 3. Success metric - pick exactly ONE
*One measurable thing that tells you it worked. Resist the urge to list five.* (`ENG-PRIN-SIMPLE-STMT` - YAGNI applies to metrics too.)

> e.g. "The dashboard renders a per-dimension comparison with no client-side maths - the API does all the averaging."

## 4. User stories - Given / When / Then
*Behaviour, not implementation. Each story is independently demoable. Keep them small (`ENG-PRIN-INCR-STMT`).*

- **Given** [starting state] **When** [the user / caller does X] **Then** [observable result].
- **Given** … **When** … **Then** …

## 5. Acceptance criteria - the checklist the demo is graded against
*Concrete, testable, unambiguous. These become your contract assertions and your tests. "Done" = all boxes ticked (`QUAL-PRINC-SDLC` - Definition of Done).*

- [ ] …
- [ ] …
- [ ] Errors use the shared `Problem` envelope (`REQ-API-5`).

---

## Worked snippet - `/recommend` (copy the shape, author your own for your endpoint)

This is what a *frozen* one-pager looks like for the deliberately-incomplete `/recommend` endpoint. The team writes this **before** touching [`../../../../stacks/nextjs/openapi.yaml`](../../../../stacks/nextjs/openapi.yaml) - then the OpenAPI `RecommendResponse` schema and the tests fall straight out of it.

**Problem:** Alice has two dimensions she scored as N/A (governance, business_impact) and no idea which lesson to start with. She needs the system to point at her biggest gaps.

**Consumers:**

| Consumer | What they do with the response |
|---|---|
| Lessons page "Recommended for you" rail | Renders a ranked list: lesson title + a one-line reason + a gap indicator |
| Dashboard nudge | Shows the single top recommendation as a call-to-action |

**Success metric (one):** The top recommendation is always a lesson targeting the user's largest gap, and the user has never already completed it.

**User stories:**
- **Given** Alice has scored governance and business_impact as N/A, **When** she opens the Lessons page, **Then** the top recommendations target those two dimensions first.
- **Given** Alice has already completed `lesson-10` (AI Security Basics), **When** recommendations are computed, **Then** `lesson-10` never appears.
- **Given** two candidate lessons have the same gap, **When** they are ranked, **Then** the `exploring`-stage lesson is listed before `building` before `applying`.

**Acceptance criteria:**
- [ ] `gapScore = 1.0 − userDimensionScore`. An N/A dimension (`responded:false`, value `0.0`) therefore has `gapScore = 1.0` - the maximum.
- [ ] Lessons the user has completed (`lesson-1, 4, 7, 8, 10`) are excluded.
- [ ] Exactly the **top 5** recommendations are returned.
- [ ] Sort order: `gapScore` **descending**, then lesson `stage` (`exploring` → `building` → `applying`).
- [ ] Each item carries `lesson`, a human-readable `reason`, and `gapScore` (matches the `Recommendation` schema already in the OpenAPI file).
- [ ] Errors use `application/problem+json` (`REQ-API-5`).

**Expected top-5 against the seed data** (use this to grade the demo - Alice's gaps are governance `1.0` and business_impact `1.0`):

| # | Lesson | Dimension | Stage | gapScore | Why it ranks here |
|---|---|---|---|---|---|
| 1 | `lesson-11` Responsible AI Usage | governance | exploring | 1.0 | Max gap, earliest stage |
| 2 | `lesson-16` Measuring AI Productivity | business_impact | exploring | 1.0 | Max gap, earliest stage |
| 3 | `lesson-12` Common AI Security Pitfalls | governance | building | 1.0 | Max gap, later stage than #1 |
| 4 | `lesson-17` AI ROI for Engineering | business_impact | building | 1.0 | Max gap, later stage |
| 5 | `lesson-18` Innovation with AI | business_impact | applying | 1.0 | Max gap, latest stage |

> Note: `lesson-10` (governance, exploring) would otherwise top the list, but it's **completed**, so it's excluded. That exclusion is exactly the kind of thing your contract test should catch.

---

*Next stop: freeze the contract in [`../../../../stacks/nextjs/openapi.yaml`](../../../../stacks/nextjs/openapi.yaml), then write the failing tests from [`test-pyramid-starter.md`](test-pyramid-starter.md). Built at Innovation Day - the five ways: [ways-of-working.md](../../ways-of-working.md).*
