case0 / src /case_zero /schemas /timeline.py
HusseinEid's picture
Case Zero - initial public release (fully local: Qwen2.5-1.5B via llama.cpp + Supertonic, custom pixel-noir SPA via gradio.Server)
414dc55
"""Spatial and temporal primitives: locations, time windows, and whereabouts.
Times are integer minutes since midnight to keep alibi reasoning exact and cheap.
"""
from __future__ import annotations
from pydantic import BaseModel, ConfigDict, Field, model_validator
from ..constants import DAY_MINUTES
class Location(BaseModel):
model_config = ConfigDict(frozen=True)
loc_id: str
name: str
description: str = ""
adjacent_to: tuple[str, ...] = ()
class TimeWindow(BaseModel):
"""A closed interval [start_min, end_min] within a single day."""
model_config = ConfigDict(frozen=True)
start_min: int = Field(ge=0, le=DAY_MINUTES)
end_min: int = Field(ge=0, le=DAY_MINUTES)
@model_validator(mode="after")
def _ordered(self) -> TimeWindow:
if self.end_min < self.start_min:
raise ValueError(f"end_min {self.end_min} precedes start_min {self.start_min}")
return self
def contains(self, minute: int) -> bool:
return self.start_min <= minute <= self.end_min
def covers(self, other: TimeWindow) -> bool:
return self.start_min <= other.start_min and other.end_min <= self.end_min
def overlaps(self, other: TimeWindow) -> bool:
return self.start_min <= other.end_min and other.start_min <= self.end_min
class WhereaboutsSegment(BaseModel):
"""Where a person actually was during one slice of the murder window."""
model_config = ConfigDict(frozen=True)
window: TimeWindow
loc_id: str
activity: str = ""
co_present_sus_ids: tuple[str, ...] = ()
class AlibiSegment(BaseModel):
"""Where a suspect *claims* to have been, with any claimed witnesses."""
model_config = ConfigDict(frozen=True)
window: TimeWindow
loc_id: str
witness_sus_ids: tuple[str, ...] = ()
class StatedAlibi(BaseModel):
model_config = ConfigDict(frozen=True)
claim_text: str
claimed_segments: tuple[AlibiSegment, ...] = ()