You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
140 lines
5.2 KiB
Python
140 lines
5.2 KiB
Python
"""Tests for the offline role-audit helpers (no network)."""
|
|
from datetime import date
|
|
|
|
from gimme_job.proactive import role_audit
|
|
|
|
|
|
def test_build_audit_prompt_lists_titles_once():
|
|
entries = [
|
|
{"title": "Dental Assistant", "role": "support"},
|
|
{"title": "Orthodontic Clinician I", "role": "unknown"},
|
|
{"title": "dental assistant", "role": "support"},
|
|
]
|
|
prompt = role_audit.build_audit_prompt(entries, max_titles=10)
|
|
assert "- [support] Dental Assistant" in prompt
|
|
assert "- [unknown] Orthodontic Clinician I" in prompt
|
|
assert prompt.count("Dental Assistant") == 1
|
|
|
|
|
|
def test_parse_audit_response_tolerates_prose():
|
|
text = (
|
|
"Here you go:\n"
|
|
'{"items":[{"title":"X","role":"target"}],'
|
|
'"proposals":[{"role":"support","term":" Front Desk ","reason":"r"},'
|
|
'{"role":"bogus","term":"x","reason":""}]}\n'
|
|
"Thanks"
|
|
)
|
|
parsed = role_audit.parse_audit_response(text)
|
|
assert parsed["items"][0]["role"] == "target"
|
|
assert parsed["proposals"] == [
|
|
{"role": "support", "term": "front desk", "reason": "r"}
|
|
]
|
|
|
|
|
|
def test_parse_audit_response_garbage():
|
|
parsed = role_audit.parse_audit_response("not json")
|
|
assert parsed["proposals"] == []
|
|
assert parsed["items"] == []
|
|
assert parsed["raw"] == "not json"
|
|
|
|
|
|
def test_append_auto_terms_dedupes(tmp_path):
|
|
from gimme_job.utils.json_io import read_yaml
|
|
|
|
path = tmp_path / "role_terms.auto.yaml"
|
|
proposals = [
|
|
{"role": "support", "term": "surgical tech", "reason": "r"},
|
|
{"role": "support", "term": "surgical tech", "reason": "r"},
|
|
{"role": "non_clinical", "term": "revenue cycle", "reason": ""},
|
|
{"role": "target", "term": "dentist", "reason": "already a code default"},
|
|
{"role": "non_clinical", "term": "the", "reason": "generic stopword"},
|
|
]
|
|
_, added = role_audit.append_auto_terms(proposals, path=path)
|
|
assert added == 2 # duplicate + code-default + stopword terms skipped
|
|
_, added2 = role_audit.append_auto_terms(proposals, path=path)
|
|
assert added2 == 0
|
|
|
|
data = read_yaml(path)
|
|
assert data["role_terms"]["support"] == ["surgical tech"]
|
|
assert data["role_terms"]["non_clinical"] == ["revenue cycle"]
|
|
assert "target" not in data["role_terms"]
|
|
|
|
|
|
def test_run_audit_writes_report(monkeypatch, tmp_path):
|
|
monkeypatch.setattr(role_audit, "manifests_dir", lambda: tmp_path / "manifests")
|
|
monkeypatch.setattr(
|
|
role_audit,
|
|
"call_ollama",
|
|
lambda prompt, base_url=None, model=None: '{"items":[],"proposals":[]}',
|
|
)
|
|
path, parsed, entries = role_audit.run_audit(
|
|
date(2026, 9, 12), titles=["Orthodontic Clinician I"]
|
|
)
|
|
assert path.name == "role-audit-2026-09-12.md"
|
|
assert "Role audit" in path.read_text(encoding="utf-8")
|
|
assert entries[0]["title"] == "Orthodontic Clinician I"
|
|
assert parsed["proposals"] == []
|
|
|
|
|
|
# ── Engine daily hook ────────────────────────────────────────────────────────
|
|
|
|
|
|
def test_engine_role_audit_applies_only_safe_roles(monkeypatch, tmp_path):
|
|
from gimme_job.proactive.engine import ProactiveEngine, ProactiveRunResult
|
|
from gimme_job.proactive.plan import ProactivePlan
|
|
|
|
report = tmp_path / "role-audit-2026-09-13.md"
|
|
parsed = {
|
|
"items": [],
|
|
"proposals": [
|
|
{"role": "support", "term": "surgical tech", "reason": ""},
|
|
{"role": "target", "term": "clinician", "reason": ""},
|
|
],
|
|
}
|
|
monkeypatch.setattr(
|
|
role_audit, "run_audit", lambda *a, **k: (report, parsed, [{"title": "x"}])
|
|
)
|
|
applied = {}
|
|
|
|
def fake_append(proposals, path=None):
|
|
applied["proposals"] = proposals
|
|
return tmp_path / "auto.yaml", len(proposals)
|
|
|
|
monkeypatch.setattr(role_audit, "append_auto_terms", fake_append)
|
|
|
|
engine = ProactiveEngine(global_config=None)
|
|
result = ProactiveRunResult()
|
|
engine._run_role_audit(ProactivePlan(), result)
|
|
assert result.role_audit_path == str(report)
|
|
assert [p["term"] for p in applied["proposals"]] == ["surgical tech"]
|
|
|
|
|
|
def test_engine_role_audit_disabled(monkeypatch):
|
|
from gimme_job.proactive.engine import ProactiveEngine, ProactiveRunResult
|
|
from gimme_job.proactive.plan import ProactivePlan
|
|
|
|
def boom(*a, **k):
|
|
raise AssertionError("run_audit should not be called")
|
|
|
|
monkeypatch.setattr(role_audit, "run_audit", boom)
|
|
plan = ProactivePlan()
|
|
plan.role_audit.enabled = False
|
|
engine = ProactiveEngine(global_config=None)
|
|
result = ProactiveRunResult()
|
|
engine._run_role_audit(plan, result)
|
|
assert result.role_audit_path is None
|
|
|
|
|
|
def test_engine_role_audit_skips_without_candidates(monkeypatch):
|
|
from gimme_job.proactive.engine import ProactiveEngine, ProactiveRunResult
|
|
from gimme_job.proactive.plan import ProactivePlan
|
|
|
|
def no_candidates(*a, **k):
|
|
raise ValueError("no candidates logged for this date")
|
|
|
|
monkeypatch.setattr(role_audit, "run_audit", no_candidates)
|
|
engine = ProactiveEngine(global_config=None)
|
|
result = ProactiveRunResult()
|
|
engine._run_role_audit(ProactivePlan(), result)
|
|
assert result.role_audit_path is None
|