"""Strict, evidence-grounded, review-only opportunity assessment normalization.""" from __future__ import annotations from typing import Any, Callable ASSESSMENT_SCHEMA_VERSION = "opportunity-assessment-v3" DETERMINISTIC_ASSESSMENT_THRESHOLD = 70 RECOMMENDATIONS = frozenset({"contact", "review", "low_priority", "do_not_contact", "insufficient_evidence"}) PRIORITIES = frozenset({"high", "medium", "low"}) WEBSITE_STATUSES = frozenset({"healthy", "outdated", "broken", "missing", "parked", "unknown"}) DOMAIN_STATUSES = frozenset({"registered", "missing", "likely_available", "unknown"}) CONTACT_TYPES = frozenset({"none", "general_business", "named_business", "free_mail", "unknown"}) _ALLOWED_FIELDS = frozenset({ "opportunity_score", "confidence_score", "recommendation", "priority", "reasons", "missing_evidence", "website_assessment", "domain_assessment", "contactability", "recommended_services", "human_review_required", "evidence_references", }) def _score(value: Any, *, confidence: bool = False) -> int: if isinstance(value, bool) or not isinstance(value, (int, float)): return 0 numeric = float(value) if numeric != numeric or numeric in (float("inf"), float("-inf")): return 0 if confidence and 0 <= numeric <= 1: numeric *= 100 return max(0, min(100, int(round(numeric)))) def _enum(value: Any, allowed: frozenset[str], default: str) -> str: item = value.strip().lower() if isinstance(value, str) else "" return item if item in allowed else default def _text_list(value: Any) -> list[str]: if value is None: return [] if not isinstance(value, list) or len(value) > 20 or any(not isinstance(item, str) for item in value): raise ValueError("invalid_assessment_list") result: list[str] = [] for item in value: item = item.strip() if not item or len(item) > 300 or item in result: continue result.append(item) return result def _website(value: Any) -> dict[str, Any]: default = {"status": "unknown", "broken": False, "outdated": False, "mobile_issue": False, "https_issue": False, "performance_issue": False} if value is None: return default if not isinstance(value, dict) or set(value) - set(default): raise ValueError("invalid_assessment_schema") result = dict(default) result["status"] = _enum(value.get("status"), WEBSITE_STATUSES, "unknown") for key in set(default) - {"status"}: if key in value: if not isinstance(value[key], bool): raise ValueError("invalid_assessment_schema") result[key] = value[key] return result def _domain(value: Any) -> dict[str, str]: if value is None: return {"status": "unknown"} if not isinstance(value, dict) or set(value) != {"status"}: raise ValueError("invalid_assessment_schema") return {"status": _enum(value.get("status"), DOMAIN_STATUSES, "unknown")} def _contactability(value: Any) -> dict[str, Any]: default = {"public_business_contact_found": False, "contact_type": "unknown"} if value is None: return default if not isinstance(value, dict) or set(value) - set(default): raise ValueError("invalid_assessment_schema") result = dict(default) if "public_business_contact_found" in value: if not isinstance(value["public_business_contact_found"], bool): raise ValueError("invalid_assessment_schema") result["public_business_contact_found"] = value["public_business_contact_found"] result["contact_type"] = _enum(value.get("contact_type"), CONTACT_TYPES, "unknown") return result def normalize_assessment(raw: dict[str, Any] | None, known_evidence_ids: set[int], *, suppressed: bool = False) -> dict[str, Any]: """Return exactly the assessment contract; reject invented evidence IDs.""" raw = {} if raw is None else raw if not isinstance(raw, dict) or set(raw) - _ALLOWED_FIELDS: raise ValueError("invalid_assessment_schema") references = raw.get("evidence_references", []) if not isinstance(references, list) or len(references) > 100: raise ValueError("invalid_evidence_references") evidence_references: list[int] = [] for reference in references: if isinstance(reference, bool) or not isinstance(reference, int): raise ValueError("invalid_evidence_reference") if reference not in known_evidence_ids: raise ValueError("unknown_evidence_reference") if reference not in evidence_references: evidence_references.append(reference) evidence_references.sort() confidence_score = _score(raw.get("confidence_score"), confidence=True) recommendation = _enum(raw.get("recommendation"), RECOMMENDATIONS, "insufficient_evidence") weak_evidence = len(evidence_references) < 2 or confidence_score < 70 or recommendation == "insufficient_evidence" contactability = _contactability(raw.get("contactability")) if suppressed: recommendation = "do_not_contact" contactability = {"public_business_contact_found": False, "contact_type": "none"} return { "opportunity_score": _score(raw.get("opportunity_score")), "confidence_score": confidence_score, "recommendation": recommendation, "priority": _enum(raw.get("priority"), PRIORITIES, "low"), "reasons": _text_list(raw.get("reasons")), "missing_evidence": _text_list(raw.get("missing_evidence")), "website_assessment": _website(raw.get("website_assessment")), "domain_assessment": _domain(raw.get("domain_assessment")), "contactability": contactability, "recommended_services": _text_list(raw.get("recommended_services")), "human_review_required": bool(suppressed or weak_evidence or raw.get("human_review_required", False)), "evidence_references": evidence_references, } def deterministic_assessment(business: dict[str, Any], evidence: list[dict[str, Any]]) -> dict[str, Any]: try: score = max(0, min(100, int(business.get("score", 0) or 0))) except (TypeError, ValueError): score = 0 references = [item["id"] for item in evidence if isinstance(item.get("id"), int)][:100] has_website = bool(business.get("website") or business.get("website_domain")) website_class = str(business.get("website_class", "")).lower() website_status = "missing" if not has_website else website_class if website_class in WEBSITE_STATUSES else "unknown" has_contact = bool(business.get("email") or business.get("phone")) return { "opportunity_score": score, "confidence_score": min(95, 35 + 20 * len(references)), "recommendation": "review" if references and score >= DETERMINISTIC_ASSESSMENT_THRESHOLD else "insufficient_evidence", "priority": "high" if score >= 70 else "medium" if score >= 40 else "low", "reasons": ["Stored evidence requires human review."] if references else [], "missing_evidence": [item for item, present in (("website evidence", has_website), ("corroborating evidence", len(references) >= 2)) if not present], "website_assessment": {"status": website_status, "broken": website_status == "broken", "outdated": website_status == "outdated", "mobile_issue": False, "https_issue": has_website and not str(business.get("website", "")).startswith("https://"), "performance_issue": False}, "domain_assessment": {"status": "registered" if business.get("website_domain") else "missing"}, "contactability": {"public_business_contact_found": has_contact, "contact_type": "general_business" if has_contact else "none"}, "recommended_services": ["website" if not has_website else "website_repair"], "human_review_required": True, "evidence_references": references, } def assess_opportunity(business: dict[str, Any], evidence: list[dict[str, Any]], *, suppressed: bool = False, provider: Callable[[dict[str, Any], list[dict[str, Any]],], dict[str, Any]] | None = None) -> dict[str, Any]: raw = provider(business, evidence) if provider else deterministic_assessment(business, evidence) return normalize_assessment(raw, {item["id"] for item in evidence if isinstance(item.get("id"), int)}, suppressed=suppressed)