Andrew Ridgway bec1eaac87
Some checks failed
Test / test (push) Has been cancelled
first pass at the newspaper builder
2026-09-14 11:57:22 +10:00

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