This commit is contained in:
@@ -7,16 +7,54 @@ from http.client import HTTPConnection
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from app.main import create_server
|
||||
from app.scoring import DEFAULT_RULES, evaluate_score
|
||||
from app.scoring import DEFAULT_RULES, SCORE_VERSION, evaluate_score, signals_for_business
|
||||
|
||||
|
||||
class ScoringEngineTests(unittest.TestCase):
|
||||
def test_opportunity_defaults_are_the_exact_versioned_transparent_model(self):
|
||||
expected = {
|
||||
"no_detected_website": 30, "no_official_domain": 25,
|
||||
"no_functioning_web_service": 25, "broken_website": 25,
|
||||
"parked_default_placeholder": 20, "public_free_mail": 15,
|
||||
"human_reviewed_outdated": 15, "no_working_https": 10,
|
||||
"severe_performance": 10, "active_social": 10,
|
||||
"valid_public_business_phone": 5, "multiple_corroborating_sources": 5,
|
||||
"possibly_closed": -30, "healthy_modern_website": -30,
|
||||
"stale_or_uncertain": -15,
|
||||
}
|
||||
self.assertEqual(SCORE_VERSION, "opportunity-v1")
|
||||
self.assertEqual({r["code"]: r["points"] for r in DEFAULT_RULES}, expected)
|
||||
self.assertTrue(all(r["version"] == 1 and r["enabled"] == 1 for r in DEFAULT_RULES))
|
||||
|
||||
def test_each_opportunity_signal_applies_only_when_present_and_explains_rule_metadata(self):
|
||||
for rule in DEFAULT_RULES:
|
||||
signals = {"state": {"suppressed": False, "merge_status": "active"}, "opportunity": {rule["code"]: True}}
|
||||
result = evaluate_score(signals, [rule])
|
||||
self.assertEqual(result["score"], max(0, rule["points"]), rule["code"])
|
||||
explanation = result["explanations"][0]
|
||||
self.assertEqual({explanation[k] for k in ("code", "name", "points", "version")}, {rule["code"], rule["name"], rule["points"], 1})
|
||||
|
||||
def test_signal_extraction_maps_website_contacts_and_provenance_to_opportunity_signals(self):
|
||||
signals = signals_for_business({"name": "Acme", "website": "http://acme.test", "website_domain": "acme.test", "phone": "+27123456789"},
|
||||
{"classification": "parked", "social_signal": True, "performance": "severe", "human_reviewed_outdated": True},
|
||||
[{"public_business": True, "classification": "free_mail", "suppressed": False, "do_not_contact": False}],
|
||||
{"status": "unknown", "official": False}, False, sources=["directory", "registry"])
|
||||
self.assertTrue(signals["opportunity"]["parked_default_placeholder"])
|
||||
self.assertTrue(signals["opportunity"]["public_free_mail"])
|
||||
self.assertTrue(signals["opportunity"]["active_social"])
|
||||
self.assertTrue(signals["opportunity"]["multiple_corroborating_sources"])
|
||||
|
||||
def test_score_cap_and_negative_signals_are_deterministic(self):
|
||||
signals = {"opportunity": {r["code"]: True for r in DEFAULT_RULES}, "state": {"suppressed": False, "merge_status": "active"}}
|
||||
first = evaluate_score(signals, DEFAULT_RULES)
|
||||
self.assertEqual(first["score"], 100)
|
||||
self.assertEqual(first, evaluate_score(signals, list(reversed(DEFAULT_RULES))))
|
||||
def test_defaults_are_deterministic_and_emit_explanations_and_band(self):
|
||||
signals = {"business": {"name": "Acme", "email": "a@acme.test", "website_domain": "acme.test"}, "website": {"classification": "healthy"}, "state": {"suppressed": False}}
|
||||
first = evaluate_score(signals, DEFAULT_RULES)
|
||||
self.assertEqual(first, evaluate_score(signals, DEFAULT_RULES))
|
||||
self.assertEqual(0 <= first["score"] <= 100, True)
|
||||
self.assertEqual(first["priority_band"], "medium")
|
||||
self.assertEqual(first["priority_band"], "low")
|
||||
self.assertTrue(all("code" in item and "reason" in item for item in first["explanations"]))
|
||||
|
||||
def test_disabled_and_versioned_rules_change_score_without_nondeterminism(self):
|
||||
@@ -26,12 +64,14 @@ class ScoringEngineTests(unittest.TestCase):
|
||||
self.assertEqual(enabled["score"], 30)
|
||||
self.assertEqual(disabled["score"], 0)
|
||||
|
||||
def test_suppression_is_ineligible_and_stale_uncertain_signals_do_not_penalize(self):
|
||||
signals = {"business": {"name": "Acme"}, "website": {"classification": "unknown", "stale": True}, "domain": {"status": "error"}, "state": {"suppressed": True}}
|
||||
def test_suppression_is_ineligible_and_stale_uncertain_signal_is_transparent(self):
|
||||
signals = {"business": {"name": "Acme"}, "website": {"classification": "unknown", "stale": True}, "domain": {"status": "error"}, "opportunity": {"stale_or_uncertain": True}, "state": {"suppressed": True}}
|
||||
result = evaluate_score(signals, DEFAULT_RULES)
|
||||
self.assertFalse(result["eligible"])
|
||||
self.assertEqual(result["priority_band"], "ineligible")
|
||||
self.assertNotIn("negative", json.dumps(result["explanations"]).lower())
|
||||
stale = next(item for item in result["explanations"] if item["code"] == "stale_or_uncertain")
|
||||
self.assertTrue(stale["applied"])
|
||||
self.assertEqual(stale["points"], -15)
|
||||
|
||||
|
||||
class ScoringApiTests(unittest.TestCase):
|
||||
|
||||
@@ -93,6 +93,8 @@ class SourceApiTests(unittest.TestCase):
|
||||
detail = self.req('GET', f"/api/v1/businesses/{businesses[0]['id']}")[1]
|
||||
self.assertTrue(detail['domains']); self.assertTrue(detail['websites']); self.assertTrue(detail['evidence'])
|
||||
self.assertTrue(detail['contacts']); self.assertEqual(detail['review_status'], 'pending')
|
||||
self.assertEqual(detail['score_version'], 'opportunity-v1')
|
||||
self.assertTrue(all({'code', 'name', 'points', 'version'} <= set(item) for item in detail['score_factors']))
|
||||
db = sqlite3.connect(self.tmp.name + '/x.db')
|
||||
self.assertEqual(db.execute('SELECT processing_status FROM source_records').fetchone()[0], 'processed')
|
||||
self.assertEqual(db.execute('SELECT status FROM enrichment_queue').fetchone()[0], 'completed')
|
||||
|
||||
Reference in New Issue
Block a user