File size: 1,965 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
71
72
"""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, ...] = ()