# /// script # requires-python = ">=3.11" # dependencies = [] # /// # ─── How to run ─── # .venv/bin/python -m pytest -q tests/test_dynamic_rectification_scoring.py """Public dynamic-rectification packet and deterministic scoring entrypoints.""" from __future__ import annotations from collections.abc import Sequence from typing import Literal, TypedDict from uuid import NAMESPACE_URL, uuid5 from scripts.dynamic_rectification_opportunities import ( ALGORITHM_VERSION, OPPORTUNITY_MODEL_VERSION, SUPPORTED_DIMENSIONS, candidate_times, candidate_window_rows, canonical_hash, compute_candidate_model, experience_windows, historical_event_fingerprint, opportunities, validate_candidate_model, ) Confidence = Literal["low", "medium", "high"] _candidate_times = candidate_times _candidate_window_rows = candidate_window_rows _canonical_hash = canonical_hash _experience_windows = experience_windows _opportunities = opportunities _validate_candidate_model = validate_candidate_model class ChoiceRow(TypedDict): time: str score: float class WinningSegment(TypedDict): start_time: str end_time: str representative_time: str width_minutes: int def _compute_candidate_model(request: dict) -> dict: return compute_candidate_model(request, _candidate_window_rows) def build_difference_packet(request: dict) -> dict: """Build reusable candidate activations and unused high-gain opportunities.""" candidates = _candidate_times( request["birth_date"], request["start_time"], request["end_time"] ) _validated_choice_evidence(request.get("evidence"), candidates) persisted = request.get("candidate_model") model = ( _compute_candidate_model(request) if persisted is None or persisted.get("opportunity_model_version") != OPPORTUNITY_MODEL_VERSION or persisted.get("historical_event_fingerprint") != historical_event_fingerprint(request) else _validate_candidate_model(persisted, request) ) dismissed = set(request.get("dismissed_opportunity_ids", [])) fingerprints = set(request.get("partition_fingerprints", [])) unused = [ item for item in _opportunities(model) if item["opportunity_id"] not in dismissed and item["candidate_partition_fingerprint"] not in fingerprints ] return { "case_id": request["case_id"], "scoring_version": ALGORITHM_VERSION, "current_range": { "start_time": request["start_time"], "end_time": request["end_time"] }, "opportunities": unused, "asked_question_fingerprints": list(request.get("question_fingerprints", [])), "candidate_partition_fingerprints": list(request.get("partition_fingerprints", [])), "recent_range_history": list(request.get("recent_ranges", [])), "candidate_model": model, } def _minute_value(value: str) -> int: hour, minute = value.split(":", maxsplit=1) return int(hour) * 60 + int(minute) def _winning_segments(rows: Sequence[ChoiceRow], top_score: float) -> list[list[ChoiceRow]]: segments: list[list[ChoiceRow]] = [] for row in rows: if row["score"] != top_score: continue follows = bool(segments) and ( _minute_value(row["time"]) - _minute_value(segments[-1][-1]["time"]) ) % 1_440 == 1 if follows: segments[-1].append(row) else: segments.append([row]) return segments def adjudicate_choice_rows( rows: Sequence[ChoiceRow], *, effective_answer_count: int, dimension_count: int, missing_layers: Sequence[str], request_fingerprint: str = "", ) -> dict: """Apply v2 confidence gates while preserving submitted candidate chronology.""" ranked = list(rows) scores = sorted({row["score"] for row in ranked}, reverse=True) top_score = scores[0] if scores else 0.0 second_score = scores[1] if len(scores) > 1 else top_score segments = _winning_segments(ranked, top_score) if ranked else [] winning_rows = segments[0] if len(segments) == 1 else [] segment: WinningSegment | None = None if winning_rows: segment = { "start_time": winning_rows[0]["time"], "end_time": winning_rows[-1]["time"], "representative_time": winning_rows[(len(winning_rows) - 1) // 2]["time"], "width_minutes": len(winning_rows), } margin = round((top_score - second_score) / max(abs(top_score), 1.0) * 100, 2) blocked = len(segments) != 1 or bool(missing_layers) high = ( not blocked and effective_answer_count >= 4 and dimension_count >= 3 and segment is not None and segment["width_minutes"] <= 5 and margin >= 20 ) medium = ( not blocked and effective_answer_count >= 3 and dimension_count >= 2 and segment is not None and segment["width_minutes"] <= 15 and margin >= 10 ) confidence: Confidence = "high" if high else "medium" if medium else "low" reasons = [] if len(segments) != 1: reasons.append("tied_leader" if segments else "no_candidate_rows") if missing_layers: reasons.append("missing_mandatory_layers") if confidence == "low" and effective_answer_count < 3: reasons.append("insufficient_effective_evidence") fingerprint = request_fingerprint or _canonical_hash(ranked) return { "result_id": str(uuid5(NAMESPACE_URL, f"{ALGORITHM_VERSION}:{fingerprint}")), "confidence": confidence, "can_apply": confidence == "high", "winning_segment": segment, "event_count": effective_answer_count, "domain_count": dimension_count, "top_score": top_score, "second_score": second_score, "margin_percent": margin, "reasons": reasons, "evidence": [], "algorithm_version": ALGORITHM_VERSION, "evidence_mode": "dynamic_choice", "effective_answer_count": effective_answer_count, "dimension_count": dimension_count, } def _validated_choice_evidence( evidence_rows: list | None, candidates: Sequence[str], ) -> tuple[list[dict], set[str]]: if not isinstance(evidence_rows, list): raise ValueError("choice evidence must contain partition evidence") if len(evidence_rows) > 10: raise ValueError("choice evidence may contain at most 10 rows") question_ids: set[str] = set() dimensions: set[str] = set() required = { "question_id", "opportunity_id", "partition_id", "dimension_code", "candidate_scores", "information_gain", } for evidence in evidence_rows: if not isinstance(evidence, dict) or set(evidence) != required: field = ( "option_id" if isinstance(evidence, dict) and "option_id" in evidence else "partition evidence" ) raise ValueError(f"choice evidence contains invalid {field}") question_id = evidence["question_id"].strip() if isinstance(evidence["question_id"], str) else "" if not question_id: raise ValueError("partition evidence question identifier must be non-empty") if question_id in question_ids: raise ValueError("duplicate question evidence is not allowed") if any( not isinstance(evidence[key], str) or not evidence[key].strip() for key in ("opportunity_id", "partition_id") ): raise ValueError("partition evidence identifier must be a non-empty string") _validate_scores(evidence, candidates) if evidence["dimension_code"] not in SUPPORTED_DIMENSIONS: raise ValueError("choice evidence dimension is unsupported") question_ids.add(question_id) dimensions.add(evidence["dimension_code"]) return evidence_rows, dimensions def _validate_scores(evidence: dict, candidates: Sequence[str]) -> None: import math scores = evidence["candidate_scores"] gain = evidence["information_gain"] valid_scores = isinstance(scores, dict) and set(scores) == set(candidates) and all( not isinstance(score, bool) and isinstance(score, int | float) and math.isfinite(score) and score >= 0 for score in scores.values() ) if not valid_scores: raise ValueError("candidate scores must exactly match the submitted range") if ( isinstance(gain, bool) or not isinstance(gain, int | float) or not math.isfinite(gain) or gain < 0 ): raise ValueError("choice evidence information gain must be finite") def score_choice_evidence(request: dict) -> dict: """Sum only strict server-resolved primary evidence, then adjudicate it.""" candidates = _candidate_times( request["birth_date"], request["start_time"], request["end_time"] ) evidence_rows, dimensions = _validated_choice_evidence( request.get("choice_evidence"), candidates ) totals = {candidate: 0.0 for candidate in candidates} for evidence in evidence_rows: for candidate in candidates: totals[candidate] += ( float(evidence["candidate_scores"][candidate]) * float(evidence["information_gain"]) ) rows: list[ChoiceRow] = [ {"time": candidate, "score": round(score, 6)} for candidate, score in totals.items() ] return adjudicate_choice_rows( rows, effective_answer_count=len(evidence_rows), dimension_count=len(dimensions), missing_layers=[], request_fingerprint=_canonical_hash(request), )