careers-llm-extract

This commit is contained in:
ldy
2026-06-17 22:56:04 -04:00
parent 113a4ced36
commit 41037fb2f5
7 changed files with 1350 additions and 28 deletions

View File

@@ -550,3 +550,112 @@ class TestSlugGuessTier:
result = find_careers_page("https://www.acme.com", client=FakeClient())
assert result.method == "url_pattern"
assert result.careers_url == "https://acme.com/careers"
# ---------------------------------------------------------------------------
# Tier 5 — Cheap-LLM careers-link classification
# ---------------------------------------------------------------------------
class TestLLMClassifyTier:
"""Tier 5 fires after sitemap, no-ops when LLM returns None, and is skipped on earlier hits."""
def _patch_all_deterministic_miss(self, monkeypatch, *, homepage_html: str = "<html/>") -> None:
"""Patch tiers 14 to all miss; leaves Tier 5 for the caller to control."""
monkeypatch.setattr(
"jobsource.careers.cascade._safe_get_html",
lambda website, client: homepage_html,
)
monkeypatch.setattr("jobsource.careers.cascade._ats.detect_and_fetch", lambda *a, **kw: None)
monkeypatch.setattr("jobsource.careers.cascade._ats.recover_via_slug_guess", lambda *a, **kw: None)
monkeypatch.setattr("jobsource.careers.cascade._heuristics.probe_url_patterns", lambda *a, **kw: None)
monkeypatch.setattr("jobsource.careers.cascade._heuristics.scan_homepage_links", lambda *a, **kw: None)
monkeypatch.setattr("jobsource.careers.cascade._heuristics.parse_sitemap", lambda *a, **kw: None)
# Prevent _finalize from making network calls
monkeypatch.setattr("jobsource.careers.cascade._ats.detect_ats_in_url", lambda url: None)
def test_llm_classify_hit_returns_055_confidence(self, monkeypatch):
from jobsource.careers.classify_llm import Anchor, CareerLinkResult
self._patch_all_deterministic_miss(
monkeypatch,
homepage_html='<html><body><a href="/careers">Careers</a></body></html>',
)
monkeypatch.setattr(
"jobsource.careers.cascade._classify_llm.extract_anchors",
lambda html, base_url: [Anchor(url="https://acme.com/careers", text="Careers")],
)
monkeypatch.setattr(
"jobsource.careers.cascade._classify_llm.classify_careers_link",
lambda anchors: CareerLinkResult(careers_url="https://acme.com/careers", confidence=0.85),
)
result = find_careers_page("https://www.acme.com", client=FakeClient())
assert result.method == "llm_classify"
assert result.confidence == 0.55 # fixed tier-level confidence from cascade
assert result.careers_url == "https://acme.com/careers"
def test_llm_classify_none_falls_through_to_method_none(self, monkeypatch):
from jobsource.careers.classify_llm import Anchor
self._patch_all_deterministic_miss(monkeypatch)
monkeypatch.setattr(
"jobsource.careers.cascade._classify_llm.extract_anchors",
lambda html, base_url: [Anchor(url="https://acme.com/about", text="About")],
)
monkeypatch.setattr(
"jobsource.careers.cascade._classify_llm.classify_careers_link",
lambda anchors: None,
)
result = find_careers_page("https://www.acme.com", client=FakeClient())
assert result.method == "none"
assert result.careers_url is None
def test_tier5_skipped_when_homepage_html_is_none(self, monkeypatch):
"""When the homepage fetch returns None, Tier 5 is silently bypassed."""
llm_called: list[bool] = []
self._patch_all_deterministic_miss(monkeypatch)
monkeypatch.setattr(
"jobsource.careers.cascade._safe_get_html",
lambda website, client: None,
)
monkeypatch.setattr(
"jobsource.careers.cascade._classify_llm.classify_careers_link",
lambda anchors: llm_called.append(True) or None,
)
find_careers_page("https://www.acme.com", client=FakeClient())
assert llm_called == []
def test_tier5_not_called_when_sitemap_hits(self, monkeypatch):
"""When sitemap resolves, the cascade returns before reaching Tier 5."""
llm_called: list[bool] = []
monkeypatch.setattr(
"jobsource.careers.cascade._safe_get_html",
lambda website, client: "<html/>",
)
monkeypatch.setattr("jobsource.careers.cascade._ats.detect_and_fetch", lambda *a, **kw: None)
monkeypatch.setattr("jobsource.careers.cascade._ats.recover_via_slug_guess", lambda *a, **kw: None)
monkeypatch.setattr("jobsource.careers.cascade._heuristics.probe_url_patterns", lambda *a, **kw: None)
monkeypatch.setattr("jobsource.careers.cascade._heuristics.scan_homepage_links", lambda *a, **kw: None)
monkeypatch.setattr(
"jobsource.careers.cascade._heuristics.parse_sitemap",
lambda *a, **kw: "https://acme.com/careers",
)
monkeypatch.setattr("jobsource.careers.cascade._ats.detect_ats_in_url", lambda url: None)
monkeypatch.setattr(
"jobsource.careers.cascade._classify_llm.classify_careers_link",
lambda anchors: llm_called.append(True) or None,
)
result = find_careers_page("https://www.acme.com", client=FakeClient())
assert result.method == "sitemap"
assert llm_called == []
def test_tier5_error_falls_through_gracefully(self, monkeypatch):
"""A Tier 5 exception should not abort the cascade."""
self._patch_all_deterministic_miss(monkeypatch)
monkeypatch.setattr(
"jobsource.careers.cascade._classify_llm.extract_anchors",
lambda html, base_url: (_ for _ in ()).throw(RuntimeError("classify boom")),
)
result = find_careers_page("https://www.acme.com", client=FakeClient())
assert result.method == "none"
assert result.careers_url is None