85 lines
2.2 KiB
Python
85 lines
2.2 KiB
Python
"""funnies data model.
|
|
|
|
Per functional-design entities (unit: funnies): FunniesBrief, Crossword,
|
|
FindAWord, Cartoon, Comic, FunniesResult. Lightweight plain-data shapes owned
|
|
by the funnies library; Contract 2/4. Status ready/fallback/empty keeps failure
|
|
graceful (Q5=A / BR3.2).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Literal
|
|
|
|
FunniesStatus = Literal["ready", "fallback", "empty"]
|
|
|
|
|
|
@dataclass
|
|
class FunniesBrief:
|
|
"""Content-derived input funnies receives from the generator (Contract 4)."""
|
|
|
|
theme_words: list[str] = field(default_factory=list)
|
|
context: str = ""
|
|
cartoon_source: str = "internet-allow"
|
|
|
|
|
|
@dataclass
|
|
class Crossword:
|
|
"""A single AI-drafted crossword, laid out on a 10x10 grid (Q2=B)."""
|
|
|
|
crossword_id: str
|
|
grid: list[list[str]]
|
|
clues_across: list[str]
|
|
clues_down: list[str]
|
|
solution: list[list[str]]
|
|
|
|
|
|
@dataclass
|
|
class FindAWord:
|
|
"""A find-a-word puzzle built from theme words (BR1.3)."""
|
|
|
|
find_id: str
|
|
words: list[str]
|
|
grid: list[list[str]]
|
|
|
|
|
|
@dataclass
|
|
class Cartoon:
|
|
"""An embedded single-panel cartoon (xkcd or generated fallback, Q3=A)."""
|
|
|
|
cartoon_id: str
|
|
source: str # xkcd | generated | none
|
|
art_ref: str # local/bundled asset (never a remote URL at print; NFR4)
|
|
caption: str = ""
|
|
|
|
|
|
@dataclass
|
|
class Comic:
|
|
"""A comic strip in the funnies section (Q4=A, BR3.1)."""
|
|
|
|
comic_id: str
|
|
source: str
|
|
art_ref: str # local/bundled
|
|
dialogue: list[str] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class FunniesResult:
|
|
"""The container funnies returns to Generator (Contract 2). Graceful
|
|
fallback/empty handling on no viable content (Q5=A / BR3.2)."""
|
|
|
|
funnies_result_id: str
|
|
crossword: Crossword | None = None
|
|
find_a_word: FindAWord | None = None
|
|
cartoons: list[Cartoon] = field(default_factory=list)
|
|
comics: list[Comic] = field(default_factory=list)
|
|
status: FunniesStatus = "ready"
|
|
status_message: str = ""
|
|
|
|
def is_self_contained(self) -> bool:
|
|
"""BR4.1 / NFR4: no remote URL survives in any art_ref."""
|
|
for art in self.cartoons + self.comics:
|
|
if art.art_ref.startswith(("http://", "https://")):
|
|
return False
|
|
return True
|