"""PlayerCaseView - the case as the player may see it. Everything that would spoil the mystery (the solution, ``is_culprit`` flags, anchored lies, true whereabouts, private secrets) is stripped here. The full CaseFile is never handed to the UI process state; only this projection and discovered clues are. """ from __future__ import annotations from pydantic import BaseModel, ConfigDict from ..schemas.case import CaseFile from ..schemas.timeline import Location, TimeWindow from ..schemas.visual import VisualDescriptor class PublicSuspect(BaseModel): model_config = ConfigDict(frozen=True) sus_id: str name: str role: str persona_summary: str visual: VisualDescriptor | None = None class PublicVictim(BaseModel): model_config = ConfigDict(frozen=True) name: str role: str found_at: str cause_of_death: str time_of_death: TimeWindow class PlayerCaseView(BaseModel): model_config = ConfigDict(frozen=True) case_id: str title: str briefing: str setting_name: str setting_description: str locations: tuple[Location, ...] victim: PublicVictim suspects: tuple[PublicSuspect, ...] def build_player_view(case: CaseFile) -> PlayerCaseView: loc_names = {loc.loc_id: loc.name for loc in case.setting.locations} return PlayerCaseView( case_id=case.case_id, title=case.title, briefing=case.briefing, setting_name=case.setting.name, setting_description=case.setting.description, locations=case.setting.locations, victim=PublicVictim( name=case.victim.name, role=case.victim.role, found_at=loc_names.get(case.victim.found_at_loc_id, case.victim.found_at_loc_id), cause_of_death=case.victim.cause_of_death, time_of_death=case.victim.time_of_death, ), suspects=tuple( PublicSuspect( sus_id=s.sus_id, name=s.name, role=s.role, persona_summary=s.persona_summary, visual=s.visual, ) for s in case.suspects ), )