"""Typed entries + the envelope/category rules. Pure data; no I/O. The npc interactive layer vs the person world-record is distinguished by id NAMESPACE (prefix), not by `type` (both are type 'person').""" from dataclasses import dataclass, field from .errors import BuildError from .parse import RawEntry CANON_TYPES = {"town", "place", "region", "faction", "person", "rule"} KNOWLEDGE_TYPES = {"rumor", "fact", "secret"} LEGAL_NAMESPACES = {"town", "place", "region", "faction", "person", "rule", "rumor", "fact", "secret", "npc"} STATUSES = {"candidate", "canon"} @dataclass class KnowsLink: fact_id: str gate: str @dataclass class Entry: id: str type: str status: str secrecy: int related: list body: str | None source: str start_disposition: str | None = None knows: list = field(default_factory=list) # list[KnowsLink] disposition_notes: str | None = None rungs: list | None = None @property def namespace(self) -> str: return self.id.split(".", 1)[0] @property def slug(self) -> str: parts = self.id.split(".", 1) return parts[1] if len(parts) == 2 else parts[0] @property def is_npc_layer(self) -> bool: return self.namespace == "npc" @property def is_knowledge(self) -> bool: return self.type in KNOWLEDGE_TYPES @property def is_canon_entity(self) -> bool: return (not self.is_npc_layer) and self.type in CANON_TYPES def entry_from_raw(raw: RawEntry) -> Entry: d = raw.data for key in ("id", "type", "status", "secrecy", "related"): if key not in d: raise BuildError(f"missing required field '{key}'", source=raw.source, entry_id=d.get("id", "")) knows: list[KnowsLink] = [] for k in d.get("knows", []): if "fact" not in k or "gate" not in k: raise BuildError("knows entry needs 'fact' and 'gate'", source=raw.source, entry_id=d.get("id", "")) knows.append(KnowsLink(fact_id=k["fact"], gate=k["gate"])) return Entry( id=d["id"], type=d["type"], status=d["status"], secrecy=d["secrecy"], related=list(d["related"]), body=d.get("body"), source=raw.source, start_disposition=d.get("start_disposition"), knows=knows, disposition_notes=d.get("disposition_notes"), rungs=d.get("rungs"), )