Files
Jyotisha/scripts/chart_choice/decision.py
T
ClaudeandClaude Opus 5.5 97afd1716a feat: chart choice replaces event-scored rectification page
Event scoring was measured at chance level on the 77 Rodden-AA holdout
cases (BUG-1091). The new flow finds the dividing line nearest the reported
time, describes how the flipped divisional charts read on either side, asks
up to five self-assessment questions, and saves the user's own choice
(provenance user_choice) with the domains to read in both versions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXozbkyFNqbu8pVEtZ7W
2026-11-06 02:06:17 +08:00

58 lines
2.1 KiB
Python

"""Fixed rules that turn self-assessment answers into a chart choice.
No scoring. The rules were agreed with the product owner on 2026-11-06:
* When the reported time is within ``NEAR_LINE_MINUTES`` of the line, or is a
rounded time (minute divisible by 15), the reported time carries no real
preference, so the side with more answers wins. A tie keeps the reported side.
* Otherwise the reported side is kept unless at least ``OVERRIDE_VOTES`` answers
point to the other side and at most one answer points to the reported side.
* Domains whose answer points away from the chosen side are read in both versions.
"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Final, Literal
Side = Literal["before", "after"]
NEAR_LINE_MINUTES: Final = 5
OVERRIDE_VOTES: Final = 4
def reported_is_neutral(reported: datetime, line_at: datetime, *, reported_known: bool) -> bool:
if not reported_known:
return True
if reported.minute % 15 == 0:
return True
return abs((reported - line_at).total_seconds()) < NEAR_LINE_MINUTES * 60
def decide(
answers: dict[str, str],
*,
reported_side: Side,
neutral: bool,
) -> dict[str, Any]:
votes = {"before": 0, "after": 0, "unsure": 0}
for value in answers.values():
votes[value if value in votes else "unsure"] += 1
other: Side = "after" if reported_side == "before" else "before"
if neutral:
if votes[other] > votes[reported_side]:
chosen, rule = other, "majority"
elif votes[reported_side] > votes[other]:
chosen, rule = reported_side, "majority"
else:
chosen, rule = reported_side, "tie_keeps_reported"
elif votes[other] >= OVERRIDE_VOTES and votes[reported_side] <= 1:
chosen, rule = other, "strong_override"
else:
chosen, rule = reported_side, "reported_kept"
both_versions = sorted(domain for domain, value in answers.items() if value in ("before", "after") and value != chosen)
return {
"chosen_side": chosen,
"rule": rule,
"votes": votes,
"both_versions_domains": both_versions,
}