mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 08:36:25 +00:00
fix(html-report): funnel from stage history, rejection rate from true rejections
The funnel was computed from current status - a state, not a history - so an application that interviewed and was later rejected never counted as reaching Interview, and a hired candidate produced Interview=0. The stage checkboxes Step 1.2 already merges from outcome.md are the history; Step 2 and chart 4 now use them. The rejection rate also counted offer_declined (a success) and withdrawn (candidate-initiated) as rejections and left unresolved Interview/Offer rows in the denominator. Review findings F10 and F11 (2026-08-19). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
dab215073e
commit
2c3d2d8558
@@ -94,6 +94,43 @@ class HtmlReportTrackerFieldTests(unittest.TestCase):
|
||||
class HtmlReportGitignoreTests(unittest.TestCase):
|
||||
"""reports/ must be gitignored — it holds personal generated output."""
|
||||
|
||||
def test_funnel_is_computed_from_stage_history_not_current_status(self):
|
||||
"""status is a current state, not a history: an application that
|
||||
interviewed and was then rejected carries status `rejected` and would
|
||||
never count as having reached Interview, so a finished search reads
|
||||
as though nobody ever interviewed (review finding F10, 2026-08-19).
|
||||
The stage checkboxes merged from outcome.md in Step 1.2 are the
|
||||
history; the funnel must be told to use them."""
|
||||
text = COMMAND_FILE.read_text(encoding="utf-8")
|
||||
self.assertIn(
|
||||
"stage checkboxes",
|
||||
text.split("## Step 3")[0].split("## Step 2")[1],
|
||||
"Step 2's funnel definition must derive stage-reached from the "
|
||||
"merged outcome.md stage checkboxes",
|
||||
)
|
||||
self.assertIn(
|
||||
"not current status",
|
||||
text,
|
||||
"the funnel rule must say explicitly that current status alone undercounts",
|
||||
)
|
||||
|
||||
def test_rejection_rate_excludes_declined_offers_and_withdrawals(self):
|
||||
"""offer_declined is the candidate turning an offer down (a success)
|
||||
and withdrawn is candidate-initiated; counting either as a rejection
|
||||
inflates the rejection rate on a self-assessment dashboard (review
|
||||
finding F11, 2026-08-19)."""
|
||||
text = COMMAND_FILE.read_text(encoding="utf-8")
|
||||
self.assertIn(
|
||||
"`offer_declined`",
|
||||
text.split("## Step 3")[0].split("## Step 2")[1],
|
||||
"the rejection-rate definition must address offer_declined",
|
||||
)
|
||||
self.assertIn(
|
||||
"not rejections",
|
||||
text,
|
||||
"the rate must exclude candidate-initiated outcomes explicitly",
|
||||
)
|
||||
|
||||
def test_reports_folder_is_gitignored(self):
|
||||
rules = {line.strip() for line in GITIGNORE.read_text(encoding="utf-8").splitlines()}
|
||||
self.assertIn(
|
||||
|
||||
Reference in New Issue
Block a user