careers-llm-extract
This commit is contained in:
@@ -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 1–4 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
|
||||
|
||||
Reference in New Issue
Block a user