diff --git a/.claude/commands/html-report.md b/.claude/commands/html-report.md
index a264d0d..ec1e814 100644
--- a/.claude/commands/html-report.md
+++ b/.claude/commands/html-report.md
@@ -49,8 +49,8 @@ From the normalised data compute:
- **By sector:** count per unique sector value
- **By channel:** portal vs online vs referral vs other
- **By year/season:** group by the `date` field (which may be a year like `2025` or a full date)
-- **Funnel rates:** what % progressed past resume screen (reached Interview or beyond)
-- **Rejection rate:** Rejected/Closed ÷ Total with a resolved status (exclude Active)
+- **Funnel rates:** what % progressed past resume screen (reached Interview or beyond). Compute stage-reached from history, not current status: an application counts as having reached a stage when its current status implies it **or** its merged `outcome.md` stage checkboxes (Step 1.2) show the stage was reached - a `rejected` row whose outcome file ticks an interview stage reached Interview, and a `hired` row reached every stage before Hired. Current status alone structurally undercounts every earlier stage: a finished search would read as though nobody ever interviewed.
+- **Rejection rate:** true rejections (`rejected`, `no_response`) ÷ applications with a final outcome. `offer_declined` (the candidate turned the offer down - a success) and `withdrawn` (candidate-initiated) are not rejections and stay out of the numerator; Interview and Offer rows are still unresolved, so they stay out of the denominator along with Active. The Rejected/Closed status *bucket* still groups all closed rows for the doughnut - the rate just must not reuse the bucket blindly.
---
@@ -106,7 +106,7 @@ Write a single self-contained HTML file. All CSS is inline in a `