"""Port interfaces — abstract contracts the domain needs from the outside world. No framework imports allowed. Adapters implement these ABCs. """ from abc import ABC, abstractmethod from typing import Optional from .models import RuleDocument, RuleSearchResult, LLMResponse class RuleRepository(ABC): """Port for storing and searching rules in a vector knowledge base.""" @abstractmethod def add_documents(self, docs: list[RuleDocument]) -> None: ... @abstractmethod def search( self, query: str, top_k: int = 10, section_filter: Optional[str] = None ) -> list[RuleSearchResult]: ... @abstractmethod def count(self) -> int: ... @abstractmethod def clear_all(self) -> None: ... @abstractmethod def get_stats(self) -> dict: ... class LLMPort(ABC): """Port for generating answers from an LLM given rules context.""" @abstractmethod async def generate_response( self, question: str, rules: list[RuleSearchResult], conversation_history: Optional[list[dict[str, str]]] = None, ) -> LLMResponse: ... class ConversationStore(ABC): """Port for persisting conversation state.""" @abstractmethod async def get_or_create_conversation( self, user_id: str, channel_id: str, conversation_id: Optional[str] = None ) -> str: ... @abstractmethod async def add_message( self, conversation_id: str, content: str, is_user: bool, parent_id: Optional[str] = None, ) -> str: ... @abstractmethod async def get_conversation_history( self, conversation_id: str, limit: int = 10 ) -> list[dict[str, str]]: ... class IssueTracker(ABC): """Port for creating issues when questions can't be answered.""" @abstractmethod async def create_unanswered_issue( self, question: str, user_id: str, channel_id: str, attempted_rules: list[str], conversation_id: str, ) -> str: ...