feat(projekt-matching): deterministic misc rules, match calc, description markdown
This commit is contained in:
96
projekt-matching/tests/test_rules.py
Normal file
96
projekt-matching/tests/test_rules.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import datetime as dt
|
||||
|
||||
from projektmatch import rules
|
||||
|
||||
TODAY = dt.date(2026, 7, 9)
|
||||
|
||||
|
||||
def geo_munich(place):
|
||||
return (48.137, 11.575) # ~40 km from Sauerlach
|
||||
|
||||
|
||||
def geo_hamburg(place):
|
||||
return (53.551, 9.994) # ~600 km
|
||||
|
||||
|
||||
def geo_fail(place):
|
||||
raise OSError("network down")
|
||||
|
||||
|
||||
def misc(**kw):
|
||||
base = {"miscType": "other", "startDate": None, "workloadPercent": None,
|
||||
"remotePercent": None, "onsiteLocation": None}
|
||||
base.update(kw)
|
||||
return base
|
||||
|
||||
|
||||
def test_security_is_always_no():
|
||||
assert rules.eval_misc(misc(miscType="security"), TODAY) == rules.NO
|
||||
|
||||
|
||||
def test_start_within_8_weeks_yes_after_no_missing_unknown():
|
||||
assert rules.eval_misc(misc(miscType="start", startDate="2026-08-01"), TODAY) == rules.YES
|
||||
assert rules.eval_misc(misc(miscType="start", startDate="2026-12-01"), TODAY) == rules.NO
|
||||
assert rules.eval_misc(misc(miscType="start"), TODAY) == rules.UNKNOWN
|
||||
assert rules.eval_misc(misc(miscType="start", startDate="asap"), TODAY) == rules.UNKNOWN
|
||||
|
||||
|
||||
def test_workload():
|
||||
assert rules.eval_misc(misc(miscType="workload", workloadPercent=100), TODAY) == rules.YES
|
||||
assert rules.eval_misc(misc(miscType="workload", workloadPercent=50), TODAY) == rules.UNKNOWN
|
||||
assert rules.eval_misc(misc(miscType="workload"), TODAY) == rules.UNKNOWN
|
||||
|
||||
|
||||
def test_duration_always_yes():
|
||||
assert rules.eval_misc(misc(miscType="duration"), TODAY) == rules.YES
|
||||
|
||||
|
||||
def test_location_rules():
|
||||
full_remote = misc(miscType="location", remotePercent=100)
|
||||
assert rules.eval_misc(full_remote, TODAY) == rules.YES
|
||||
hybrid_near = misc(miscType="location", remotePercent=60, onsiteLocation="München")
|
||||
assert rules.eval_misc(hybrid_near, TODAY, geocode_fn=geo_munich) == rules.YES
|
||||
hybrid_far = misc(miscType="location", remotePercent=60, onsiteLocation="Hamburg")
|
||||
assert rules.eval_misc(hybrid_far, TODAY, geocode_fn=geo_hamburg) == rules.NO
|
||||
unclear = misc(miscType="location")
|
||||
assert rules.eval_misc(unclear, TODAY) == rules.NO
|
||||
geo_broken = misc(miscType="location", remotePercent=40, onsiteLocation="Xyzstadt")
|
||||
assert rules.eval_misc(geo_broken, TODAY, geocode_fn=geo_fail) == rules.UNKNOWN
|
||||
|
||||
|
||||
def test_calc_match_and_rounding():
|
||||
rows = [{"kat": "Must", "rating": "yes", "text": "a"},
|
||||
{"kat": "Must", "rating": "yes", "text": "b"},
|
||||
{"kat": "Must", "rating": "unknown", "text": "c"},
|
||||
{"kat": "Nice", "rating": "no", "text": "d"},
|
||||
{"kat": "Misc", "rating": "yes", "text": "e"}]
|
||||
assert rules.calc_match(rows, "Must") == 75 # (100+100+25)/3
|
||||
assert rules.calc_match(rows, "Nice") == 0
|
||||
assert rules.calc_match([], "Nice") is None
|
||||
# commercial rounding, half up (not banker's): (100+25)/2 = 62.5 -> 63
|
||||
two = [{"kat": "Must", "rating": "yes", "text": "a"},
|
||||
{"kat": "Must", "rating": "unknown", "text": "b"}]
|
||||
assert rules.calc_match(two, "Must") == 63
|
||||
|
||||
|
||||
def test_build_description_exact_format():
|
||||
rows = [{"kat": "Misc", "rating": "yes", "text": "Hybrid: 60 % remote"},
|
||||
{"kat": "Must", "rating": "no", "text": "Mehrjährige LLM-Plattform-Erfahrung"},
|
||||
{"kat": "Must", "rating": "yes", "text": "RAG-Systeme"},
|
||||
{"kat": "Nice", "rating": "yes", "text": "vLLM"}]
|
||||
text = rules.build_description(rules.order_rows(rows))
|
||||
assert text == (
|
||||
"Must-have-Match: 50 % · Nice-to-have-Match: 100 %\n"
|
||||
"\n"
|
||||
"| Nr. | Kat. | ❔ | Anforderung |\n"
|
||||
"|---|---|---|---|\n"
|
||||
"| 1 | Must | ❌ | Mehrjährige LLM-Plattform-Erfahrung |\n"
|
||||
"| 2 | Must | ✅ | RAG-Systeme |\n"
|
||||
"| 3 | Nice | ✅ | vLLM |\n"
|
||||
"| 4 | Misc | ✅ | Hybrid: 60 % remote |")
|
||||
|
||||
|
||||
def test_empty_category_dash():
|
||||
rows = [{"kat": "Must", "rating": "yes", "text": "a"}]
|
||||
assert rules.build_description(rows).startswith(
|
||||
"Must-have-Match: 100 % · Nice-to-have-Match: –")
|
||||
Reference in New Issue
Block a user