fix(F11-F13): scout selection bugs that dropped roles before any judgement
These three all cost roles BEFORE a human ever read them, which is the expensive kind of defect: a job that never reaches the report cannot be evaluated at all. F11 - dedupe by requisition, not title - The old rule collapsed everything sharing a normalised title within one employer. Two different reqs called "Senior Data Engineer" in Bern and Zurich became one row, and different teams hiring the same title vanished. - Requisition identity is now the key, parsed from the URL with the same patterns the decision log uses, employer-scoped so two boards cannot collide. - The legitimate case the old rule existed for - one role republished per location - is a SAME-REQUISITION case, so those merge their locations. - A shared title across different requisitions is now a warning, never a deletion. F12 - location filters - Substring matching accepted "Bernau bei Berlin" as Bern. Place matching is now whole-word. - Thun, Zimmerwald, Baar, Olten and ~25 more Swiss towns were rejected outright when the adapter supplied no country. Zimmerwald is the Kdo Cy site he applied to; Thun is where he lives. - Aarhus, Odense and other Danish cities added alongside Copenhagen. - A stray "Europe" no longer implies remote eligibility next to an explicit onsite marker or a single-country residence restriction. - All 14 cases named in the review now resolve correctly. F13 - decision identity - The generic fallback key dropped the query string, so /vacancy?job=1 and ?job=2 were the same key and one decision silently covered both. Identifying query parameters are now kept; tracking noise is not. - The READ path had no slug fallback although the WRITE path got one on 2026-09-04. Every RUAG repost therefore came back undecided despite a recorded decision - which is what happened to Lead-Architekt Network- und Richtfunk on 2026-09-10, days after 63 RUAG rule-outs were written. - Both paths now share `lookup_decision`. Verified on a live RUAG run: 7 reposted requisitions recovered their decisions. - A slug match is reported as RELATED with the matched URL, never applied as if it were the same requisition: a repost can differ materially, as RUAG demonstrated when it moved a disqualifier into a job title. tests/test_scout_matching.py added. Full suite 34/34 pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qej5SBvhcbGXHwLqNWiskM
This commit is contained in:
1 parent
3519b2ffe7
commit
afd8174bbd
2 files changed
+207
-17
No files matched your search
@@ -0,0 +1,65 @@
|
||||
"""Scout selection regressions from the 2026-09-11 review (F11 onwards).
|
||||
|
||||
These are the cases where a bug silently removed or mis-ranked a role *before* any human
|
||||
judgement happened, which is the expensive kind: a job that never reaches the report cannot
|
||||
be evaluated at all.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "job_scout"))
|
||||
|
||||
import scout # noqa: E402
|
||||
|
||||
|
||||
def process(jobs, cid="acme", display="Acme"):
|
||||
results, stat, warnings = scout._process_company(
|
||||
cid, display, {}, jobs, {}, "2026-09-11", {})
|
||||
return results, stat, warnings
|
||||
|
||||
|
||||
class RequisitionDedupeTests(unittest.TestCase):
|
||||
"""F11: title is not identity."""
|
||||
|
||||
def test_two_requisitions_sharing_a_title_both_survive(self):
|
||||
jobs = [
|
||||
{"id": "R-1", "title": "Senior Data Engineer",
|
||||
"location": "Bern, Switzerland", "url": "https://acme.example/jobs/R-1"},
|
||||
{"id": "R-2", "title": "Senior Data Engineer",
|
||||
"location": "Zurich, Switzerland", "url": "https://acme.example/jobs/R-2"},
|
||||
]
|
||||
results, _, warnings = process(jobs)
|
||||
self.assertEqual(2, len(results), "a different requisition must never be dropped")
|
||||
self.assertTrue(any("title(s) shared by different requisitions" in message
|
||||
for _, message in warnings),
|
||||
"the collision should be reported, not silently resolved")
|
||||
|
||||
def test_same_requisition_republished_per_location_is_merged(self):
|
||||
jobs = [
|
||||
{"id": "R-9", "title": "Platform Engineer | Switzerland | Remote",
|
||||
"location": "Bern, Switzerland", "url": "https://acme.example/jobs/R-9"},
|
||||
{"id": "R-9", "title": "Platform Engineer | Germany | Remote",
|
||||
"location": "Munich, Germany", "url": "https://acme.example/jobs/R-9"},
|
||||
]
|
||||
results, _, _ = process(jobs)
|
||||
self.assertEqual(1, len(results), "one requisition should stay one row")
|
||||
self.assertIn("Bern", results[0]["location"])
|
||||
|
||||
def test_distinct_titles_are_unaffected(self):
|
||||
jobs = [
|
||||
{"id": "A", "title": "Data Engineer", "location": "Bern, Switzerland",
|
||||
"url": "https://acme.example/jobs/A"},
|
||||
{"id": "B", "title": "Platform Engineer", "location": "Bern, Switzerland",
|
||||
"url": "https://acme.example/jobs/B"},
|
||||
]
|
||||
results, _, warnings = process(jobs)
|
||||
self.assertEqual(2, len(results))
|
||||
self.assertEqual([], [m for _, m in warnings if "title(s) shared" in m])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user