fix: follow-up review corrections, BIS rejection, and a gitignore miss

Second-pass review of this session's work by another agent. It corrected several
real mistakes of mine and finished F06 gaps I had left open. Reviewed, verified
and committed as-is.

THE MISS THAT MATTERS
`job_scout/state/applications.json` — the authoritative application registry
introduced by F02 — was never actually tracked. `job_scout/state/*` in
.gitignore silently excluded it, so my checkpoint commit afdef7e claimed to add
it and did not. Only `application_cohort.json`, the GENERATED projection, was
version-controlled; the source of truth and the migration history backup were
not. .gitignore now carries explicit negations for both.

MY ERRORS, CORRECTED
- "kastellaun" was in CH_LOCATION_KEYWORDS. Kastellaun is in Rhineland-Palatinate.
  I took it from a Bundeswehr reserve listing and filed it under Switzerland -
  precisely the class of bug F12 existed to fix.
- The F12 fix only widened the keyword list; SmartRecruiters still discarded the
  structured country, which was the concrete case the review named
  (city=Baar, country=CH). The adapter now preserves it, and a COUNTRY_NAMES
  mapping gives country precedence over a bare town name.
- F15 was applied to Taleo and Prospective only. Greenhouse still wrote
  `updated_at` into `posted`; it now uses `published_at` and carries provenance.
- My negation guard `\b(no|not|...)\b[^.;]{0,60}$` was too loose - an unrelated
  "not" within 60 characters could mask a real claim. Tightened to require the
  negation to govern the term.
- F18's retirement of the macro-count rule missed "Four to six compact skills
  lines" in the workflow; removed now.

F06 GAPS I LEFT OPEN, NOW CLOSED
Date-format validation on education/employment periods (the 2035-99 case named
in the review), evidence-structure validation, referential integrity on evidence
source files, and the missing-identity KeyError.

F10 TAKEN FURTHER, AND RIGHTLY
I demoted the US-GPA fields to *_orientation_only. They are now removed
entirely, with grade_output_rule saying what to do instead: use the German grade
and scale, and if a recipient requires a US GPA, use THEIR method and record it.
Keeping a number derived by a non-recognised method invites its use.

Also: the Capgemini omit-preference now sits in experience_capgemini.md itself,
alongside the instruction not to fill or explain the intentional gap.

BIS BASEL - REJECTED 2026-09-11
jr100429, applied 2026-07-10, no interview, no reason given. Recorded in the
registry with a dated event and in the decision log; the "prep interview brief
when screening lands" next action is cleared. This also settles the 2026-09-04
observation that the req had vanished from the BIS board - it was a real
rejection, not just a closed posting.

VERIFICATION I RAN MYSELF BEFORE COMMITTING
49/49 tests. Validator PASS with 0 advisory warnings. Package manifest and
registry views current. All 12 F12 location cases still correct, including
Kastellaun now rejecting. The F05 counter-proof still catches all 9 violations.
SBB, NVIDIA, RUAG and Kdo Cy resumes still scan clean. Diffs are net-additive
across every reference file - the deletions are replacements, not losses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qej5SBvhcbGXHwLqNWiskM
This commit is contained in:
dennisthiessenandClaude Opus 5 committed 2026-09-11 23:57:06 +02:00
1 parent 925a2d74a3
commit 34299bce54
25 files changed
+1427 -322

No files matched your search

+158
View File
@@ -0,0 +1,158 @@
"""Small regression cases from the follow-up review; no network or production writes."""
from __future__ import annotations
import contextlib
import copy
import io
import json
from pathlib import Path
import sys
import tempfile
import unittest
from argparse import Namespace
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
sys.path[:0] = [str(ROOT / "resume_builder/helpers"), str(ROOT / "job_scout")]
import application_records as records
import application_tracker as tracker
import package_manifest as manifest
import page_metrics
import scout
import validate_resume_system as validator
class ScannerRegressionTests(unittest.TestCase):
def scan(self, body):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "resume.tex"
path.write_text("dennis@thiessen.io +41 795 955 585\n" + body, encoding="utf-8")
return validator.scan_document(path, validator.load_json(validator.CLAIMS_PATH))
def test_context_cannot_be_borrowed_or_confused_with_model_training(self):
for body in ("Implemented production model training in PyTorch.",
"Coursera certification: PyTorch. Built production models in PyTorch.",
r"\skillline{Certificate}{Coursera, PyTorch}\skillline{Production}{PyTorch}"):
with self.subTest(body=body):
self.assertTrue(self.scan(body))
self.assertEqual([], self.scan("IBM AI Engineering certificate: PyTorch."))
def test_inline_markup_and_unrelated_negation_do_not_hide_claims(self):
for body in (r"Built Lang\textbf{Chain} services.",
"Delivered services without downtime using Terraform."):
with self.subTest(body=body):
self.assertTrue(self.scan(body))
self.assertEqual([], self.scan("I have no Terraform experience."))
def test_employer_name_is_not_a_skill_but_later_claim_still_is(self):
self.assertEqual([], self.scan("Application to Snowflake for Senior Software Engineer."))
self.assertTrue(self.scan("Application to Snowflake. Built Snowflake pipelines."))
def test_missing_identity_invalid_period_and_empty_evidence_report_errors(self):
original = validator.load_json(validator.CLAIMS_PATH)
history = validator.load_json(validator.HISTORY_PATH)
data = copy.deepcopy(original)
data.pop("identity")
self.assertTrue(validator.canonical_checks(data, history)[0])
data = copy.deepcopy(original)
data["employment"][0]["start"] = "2035-99"
data["claims"][0]["evidence"] = {"source": "missing/file.pdf"}
errors = validator.canonical_checks(data, history)[0]
for phrase in ("invalid start", "ownership", "does not exist"):
self.assertTrue(any(phrase in error for error in errors), errors)
class ScoutRegressionTests(unittest.TestCase):
def test_explicit_country_and_exclusion_override_town_name(self):
for location in ("Freiburg, Germany", "Baden, Austria", "Kastellaun, Germany", "Remote - Switzerland excluded"):
with self.subTest(location=location):
self.assertFalse(scout.location_eligibility(location)[0])
self.assertTrue(scout.location_eligibility("Baar")[0])
self.assertTrue(scout.location_eligibility("Wetzikon", country="CH")[0])
self.assertTrue(scout.location_eligibility("Aarhus, Denmark")[0])
self.assertTrue(scout.location_eligibility("Oslo, Norway", policy="norway_only")[0])
def test_smartrecruiters_preserves_country_through_matching(self):
payload = {"content": [{"id": "1", "name": "Data Engineer", "location": {"city": "Wetzikon", "country": "CH"}}], "totalFound": 1}
with patch.object(scout, "http_get_json", return_value=payload):
jobs = scout.fetch_smartrecruiters({"company": "example"})
rows, _, _ = scout._process_company("example", "Example", {}, jobs, {}, "2026-09-11", {})
self.assertEqual(1, len(rows))
def test_related_requisition_stays_visible_when_hiding_decided(self):
old = "https://jobs.ruag.ch/offene-stellen/data-engineer/11111111-1111-1111-1111-111111111111"
new = "https://jobs.ruag.ch/offene-stellen/data-engineer/22222222-2222-2222-2222-222222222222"
decisions = {old: {"decision": "skip", "date": "2026-09-01"}}
row = dict(company="RUAG", title="Data Engineer", location="Thun", url=new, score=3, pos=[], neg=[], in_ch=True, remote=False, relocation=False, is_new=True, posted="")
with tempfile.TemporaryDirectory() as directory, patch.object(scout, "_raw_decisions", return_value=decisions):
path = Path(directory) / "report.md"
scout.write_report(path, [row], [], False, True, decisions=scout.index_decisions(decisions), hide_decided=True)
text = path.read_text(encoding="utf-8")
self.assertIn(new, text)
self.assertIn("RELATED", text)
def test_new_only_commits_delivered_results_and_preserves_other_companies(self):
job = dict(id="1", title="Data Engineer", location="Bern", url="https://example.com/1")
with tempfile.TemporaryDirectory() as directory, contextlib.ExitStack() as stack:
for name, value in {"ROOT": Path(directory), "REPORTS_DIR": Path(directory)/"reports",
"COMPANIES": [("example", "Example", "fake", {})]}.items():
stack.enter_context(patch.object(scout, name, value))
stack.enter_context(patch.object(scout, "load_seen", return_value={"other": {"old": {"title": "Untouched"}}}))
for name in ("load_decisions", "load_last_scrape"):
stack.enter_context(patch.object(scout, name, return_value={}))
stack.enter_context(patch.object(scout, "save_last_scrape"))
saved = stack.enter_context(patch.object(scout, "save_seen"))
stack.enter_context(patch.object(scout, "_close_browser"))
stack.enter_context(patch.object(scout, "_fetch_company", return_value=("example", "Example", "fake", {}, [job], None, 0.0)))
stack.enter_context(patch.object(sys, "argv", ["scout.py", "--new-only"]))
stack.enter_context(contextlib.redirect_stdout(io.StringIO()))
stack.enter_context(contextlib.redirect_stderr(io.StringIO()))
scout.main()
state = saved.call_args.args[0]
self.assertIn("1", state["example"])
self.assertIn("old", state["other"])
def test_concurrent_scout_writer_is_rejected(self):
with tempfile.TemporaryDirectory() as directory, patch.object(scout, "ROOT", Path(directory)):
with scout.writer_lock():
with self.assertRaises(SystemExit), scout.writer_lock():
pass
self.assertFalse((Path(directory)/"state/scout.lock").exists())
class ArtifactRegressionTests(unittest.TestCase):
def test_manifest_detects_contents_but_allows_missing_local_pdf(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
folder = root / "example"
folder.mkdir()
source = folder/"resume.tex"
source.write_text("\\documentclass{article}\nOriginal", encoding="utf-8")
pdf = folder/"resume.pdf"
pdf.write_bytes(b"first PDF")
recorded = {"packages": {"example": manifest.describe(folder, record_pdfs=True)}}
current = {"packages": {"example": manifest.describe(folder)}}
self.assertEqual([], manifest.check_manifest(recorded, current, root))
pdf.unlink()
self.assertEqual([], manifest.check_manifest(recorded, current, root))
pdf.write_bytes(b"changed PDF")
self.assertTrue(manifest.check_manifest(recorded, current, root))
source.write_text("\\documentclass{article}\nChanged claims", encoding="utf-8")
self.assertNotEqual(current["packages"]["example"], manifest.describe(folder))
def test_page_metric_matches_heading_not_summary_word(self):
def bbox(args, **kwargs):
Path(args[-1]).write_text('<page width="595" height="842">'
'<word xMin="0" yMin="100" xMax="60" yMax="111">Ten</word>'
'<word xMin="60" yMin="100" xMax="160" yMax="111">experience</word>'
'<word xMin="0" yMin="420" xMax="100" yMax="431">Professional</word>'
'<word xMin="101" yMin="420" xMax="160" yMax="431">Experience</word></page>')
with patch.object(page_metrics.shutil, "which", return_value="pdftotext"), patch.object(page_metrics.subprocess, "run", side_effect=bbox):
self.assertEqual(49.9, page_metrics.measure(Path("unused.pdf"))["experience_pct"])
def test_older_imported_event_cannot_reopen_withdrawn_application(self):
data = records.load()
row = next(row for row in data["applications"] if row["company"] == "SBB")
args = Namespace(id=row["id"], date="2026-09-10", event=None, outcome="applied", note="older event", next_action=None)
with self.assertRaises(ValueError):
tracker.update_application(data, args)