Spaces:
Running
Running
File size: 2,708 Bytes
414dc55 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | """Deterministic verdict scoring.
The verdict is a pure function of the structured accusation and the discovered/cited
evidence - never of any model output. A jailbroken confession in dialogue therefore
cannot change the outcome: the case resolves only on the data.
"""
from __future__ import annotations
from ..schemas.accusation import Accusation, Verdict
from ..schemas.case import CaseFile
from .game_state import GameState
_CULPRIT_POINTS = 50
_WEAPON_POINTS = 15
_MOTIVE_POINTS = 15
_EVIDENCE_POINTS = 20
def score_accusation(case: CaseFile, accusation: Accusation, state: GameState) -> Verdict:
sol = case.solution
culprit_correct = accusation.accused_sus_id == sol.culprit_sus_id
weapon_correct = accusation.weapon_id == sol.weapon_id if accusation.weapon_id else False
motive_correct = accusation.motive_id == sol.motive_id if accusation.motive_id else False
minimal = set(sol.minimal_clue_set)
cited = set(accusation.cited_clue_ids)
discovered = state.discovered_clue_ids
valid_citations = minimal & cited & discovered
evidence_ratio = (len(valid_citations) / len(minimal)) if minimal else 0.0
# Weapon, motive, and evidence credit only count once the right person is named:
# an incorrect accusation scores zero, so a jailbroken confession earns nothing.
culprit_pts = _CULPRIT_POINTS if culprit_correct else 0
weapon_pts = _WEAPON_POINTS if (culprit_correct and weapon_correct) else 0
motive_pts = _MOTIVE_POINTS if (culprit_correct and motive_correct) else 0
evidence_pts = round(_EVIDENCE_POINTS * evidence_ratio) if culprit_correct else 0
score = culprit_pts + weapon_pts + motive_pts + evidence_pts
breakdown = (
("culprit", culprit_pts),
("weapon", weapon_pts),
("motive", motive_pts),
("evidence", evidence_pts),
)
rationale = _rationale(case, culprit_correct, valid_citations, minimal)
return Verdict(
solved=culprit_correct,
culprit_correct=culprit_correct,
weapon_correct=weapon_correct,
motive_correct=motive_correct,
score=score,
breakdown=breakdown,
rationale=rationale,
deduction_chain=sol.deduction_chain,
)
def _rationale(
case: CaseFile, culprit_correct: bool, valid: set[str], minimal: set[str]
) -> str:
culprit_name = case.suspect(case.solution.culprit_sus_id).name
if culprit_correct:
if valid >= minimal and minimal:
return f"Correct, and fully evidenced. {culprit_name} did it, and you proved it."
return f"You named {culprit_name} - the right person - but the case could be tighter."
return f"Not quite. The killer was {culprit_name}."
|