"""Pure domain models — no framework imports (no FastAPI, SQLAlchemy, httpx, etc.).""" from dataclasses import dataclass, field from datetime import datetime, timezone from typing import Optional @dataclass class RuleDocument: """A rule from the knowledge base with metadata.""" rule_id: str title: str section: str content: str source_file: str parent_rule: Optional[str] = None page_ref: Optional[str] = None def to_metadata(self) -> dict[str, str]: """Flat dict suitable for vector store metadata (no None values).""" return { "rule_id": self.rule_id, "title": self.title, "section": self.section, "parent_rule": self.parent_rule or "", "page_ref": self.page_ref or "", "source_file": self.source_file, } @dataclass class RuleSearchResult: """A rule returned from semantic search with a similarity score.""" rule_id: str title: str content: str section: str similarity: float def __post_init__(self): if not (0.0 <= self.similarity <= 1.0): raise ValueError( f"similarity must be between 0.0 and 1.0, got {self.similarity}" ) @dataclass class Conversation: """A chat session between a user and the bot.""" id: str user_id: str channel_id: str created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) last_activity: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) @dataclass class ChatMessage: """A single message in a conversation.""" id: str conversation_id: str content: str is_user: bool parent_id: Optional[str] = None created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) @dataclass class LLMResponse: """Structured response from the LLM.""" answer: str cited_rules: list[str] = field(default_factory=list) confidence: float = 0.5 needs_human: bool = False @dataclass class ChatResult: """Final result returned by ChatService to inbound adapters.""" response: str conversation_id: str message_id: str cited_rules: list[str] confidence: float needs_human: bool parent_message_id: Optional[str] = None