From 889b82bf13b64f3ec7058e80bf50705b9fb54c54 Mon Sep 17 00:00:00 2001 From: Rudy Mizrahi Celekli <47457359+rudycelekli@users.noreply.github.com> Date: Thu, 15 Oct 2026 05:39:10 -0400 Subject: [PATCH] fix: expire scored jobs with passed preserved deadlines (#560) --- CHANGELOG.md | 8 +++++ tests/test_rank_state.py | 67 ++++++++++++++++++++++++++++++++++++++++ tools/rank_state.py | 14 ++++++++- 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a20c154d..dfd5cb28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,14 @@ per-file diff commands. ### Fixed +- **`/rank` retires scored jobs whose preserved deadline has passed** + (`tools/rank_state.py`, `tests/test_rank_state.py`) - a fresh `null` deadline + preserves the stored date, but `apply` previously wrote the job as `ranked` + even after that date had passed. The later sweep excludes re-scored keys, + so it could not correct the state or shortlist. Apply now expires the job + before changing rank metadata; current-day deadlines, explicit future + extensions, invalid dates, dry runs, and scoring errors keep their behavior. + - **`/reset profile` keeps the `framework_version` frontmatter on the two files it rewrites** (`.claude/commands/reset.md` Step 3, `tests/test_reset_command.py`) - the full-file skeletons for `01-candidate-profile.md` and `02-behavioral-profile.md` started at the `#` title, while diff --git a/tests/test_rank_state.py b/tests/test_rank_state.py index 01b09829..8554886e 100644 --- a/tests/test_rank_state.py +++ b/tests/test_rank_state.py @@ -259,6 +259,73 @@ class Apply(RankStateCase): path.write_text(json.dumps(payload), encoding="utf-8") return str(path) + def test_scored_keys_keep_past_deadlines_out_of_the_shortlist(self): + for status in ("new", "ranked", "expired"): + with self.subTest(status=status): + original = entry(status=status, deadline="2026-09-02") + if status != "new": + original.update(rank_score=61, rank_date="2026-08-31", strengths=["stored strength"]) + self.write_state({"a": original}) + args = ["candidates", "--tracker", str(self.tmp / "none.csv")] + if status != "new": + args.append("--all") + selected = self.run_tool(*args)["selected"] + self.assertEqual([row["key"] for row in selected], ["a"]) + result = {"key": "a", "status": "scored", "deadline": None, + "scores": {dimension: 80 for dimension in ("technical", "experience", "behavioral", "career")}, + "location_verdict": "PASS", "language_gate": "PASS", "language": "en", + "strengths": ["fresh strength"], "gaps": []} + out = self.run_tool("apply", "--results", self.results([result])) + self.assertEqual(out["ranked"], []) + self.assertEqual(out["vetoed"], []) + self.assertEqual([row["key"] for row in out["expired"]], ["a"]) + self.assertEqual(self.read_state()["a"], dict(original, status="expired")) + swept = self.run_tool("sweep", "--write", "--exclude", "a") + self.assertEqual(swept["swept"], 0) + self.assertEqual(self.read_state()["a"]["status"], "expired") + + def test_deadline_apply_boundaries_and_dry_run_keep_existing_semantics(self): + cases = [ + (TODAY, None, "scored", "ranked"), + ("2026-09-04", None, "scored", "ranked"), + ("ASAP", None, "scored", "ranked"), + (None, None, "scored", "ranked"), + ("2026-09-02", "2026-09-04", "scored", "ranked"), + (None, None, "expired", "expired"), + ("2026-09-02", None, "scored", "expired"), + ] + for stored, fresh, status, expected in cases: + with self.subTest(stored=stored, fresh=fresh, status=status): + self.write_state({"a": entry(deadline=stored)}) + before = self.state.read_bytes() + result = {"key": "a", "status": status, "deadline": fresh, + "scores": {dimension: 80 for dimension in ("technical", "experience", "behavioral", "career")}} + path = self.results([result]) + dry = self.run_tool("apply", "--results", path, "--dry-run") + self.assertFalse(dry["written"]) + self.assertEqual(self.state.read_bytes(), before) + self.assertEqual([row["key"] for row in dry[expected]], ["a"]) + out = self.run_tool("apply", "--results", path) + self.assertEqual([row["key"] for row in out[expected]], ["a"]) + self.assertEqual(self.read_state()["a"]["status"], expected) + self.assertEqual(self.read_state()["a"]["deadline"], fresh or stored) + + def test_past_deadline_does_not_bypass_scoring_errors(self): + for result in ({"key": "a", "status": "scored", "scores": {"technical": 80}}, + {"key": "missing", "status": "scored", "scores": {}}): + with self.subTest(result=result): + original = entry(deadline="2026-09-02") + self.write_state({"a": original}) + before = self.state.read_bytes() + path = self.results([result]) + dry = self.run_tool("apply", "--results", path, "--dry-run", expect=1) + self.assertEqual(len(dry["errors"]), 1) + self.assertEqual(self.state.read_bytes(), before) + out = self.run_tool("apply", "--results", path, expect=1) + self.assertEqual(len(out["errors"]), 1) + self.assertEqual(out["expired"], []) + self.assertEqual(self.read_state()["a"], original) + def test_weights_bands_and_persisted_fields(self): self.write_state({"a": entry()}) out = self.run_tool( diff --git a/tools/rank_state.py b/tools/rank_state.py index ea00cb2a..be3c6e4d 100644 --- a/tools/rank_state.py +++ b/tools/rank_state.py @@ -289,6 +289,19 @@ def cmd_apply(args) -> int: errors.append({"key": key, "error": str(exc)}) continue + # A missing deadline from the fresh fetch is not a correction to the + # stored date. Scored keys are excluded from the later sweep, so apply + # must retire a passed deadline before updating any ranking metadata. + parsed = parse_iso(result.get("deadline") or entry.get("deadline")) + if parsed is not None and parsed < today: + entry["status"] = "expired" + if result.get("deadline"): + entry["deadline"] = result["deadline"] + expired.append( + {"key": key, "title": entry.get("title"), "company": entry.get("company"), "url": entry.get("url")} + ) + continue + legacy = entry_location_verdict(entry) if entry.get("location") in ("PASS", "FAIL", "FLAG"): entry.pop("location", None) # legacy verdict, never a place @@ -312,7 +325,6 @@ def cmd_apply(args) -> int: if isinstance(value, list): entry[field] = [str(b) for b in value][:3] - parsed = parse_iso(entry.get("deadline")) rows.append( { "key": key,