stubborn-otter/otter-boards

Otter Boards, a team task board you can run from the web or from Claude and ChatGPT. FastAPI + DataK3 + appid + Models + MCP.

A remix template · 1 remixes · Remix stubborn-otter/otter-boards

app/boards/application/ports.py

"""What the use cases need from the outside world. infrastructure/ provides it; tests fake it."""
from typing import Optional, Protocol

from boards.domain.model import Board, BoardList, Card, Event


class AssistantUnavailable(Exception):
    """Dodil Models could not answer. Never fatal: the board keeps working without AI."""


class BoardRepository(Protocol):
    def get(self, board_id: str) -> Optional[Board]: ...
    def boards_for(self, email: str) -> list: ...
    def save_board(self, board: Board) -> None: ...
    def remove_member(self, board_id: str, email: str) -> None: ...

    def lists_on(self, board_id: str) -> list:
        """The board's live lists, in display order."""
        ...

    def save_list(self, board_list: BoardList) -> None: ...

    def get_card(self, card_id: str) -> Optional[Card]: ...

    def cards_on(self, board_id: str) -> list:
        """The board's live cards, in display order within each list."""
        ...

    def last_card_number(self, board_id: str) -> int:
        """The highest number ever used on the board, counting archived cards. 0 if none."""
        ...

    def insert_card(self, card: Card) -> bool:
        """Claim the card's id. False if another card already took it: nothing is overwritten."""
        ...

    def save_card(self, card: Card) -> None: ...

    def log(self, event: Event) -> None: ...

    def history(self, card_id: str) -> list:
        """A card's events, oldest first."""
        ...

    def activity(self, board_id: str, limit: int) -> list:
        """The board's latest events, newest first."""
        ...

    def embeddings(self, board_id: str) -> dict:
        """card id -> (hash of the text it was made from, vector)."""
        ...

    def save_embedding(self, board_id: str, card_id: str, text_hash: str, vector: list) -> None: ...


class Assistant(Protocol):
    def checklist(self, title: str, description: str = "") -> list: ...
    def embed(self, texts: list) -> list: ...
    def summary(self, facts: str) -> str: ...