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/domain/similarity.py

"""How alike two cards are: by meaning when we have embeddings, by shared words when we don't."""
import hashlib
import math
import re

from boards.domain.model import Card

_STOP = set("a an and are as at be but by for from has have in into is it its of on or that the "
            "this to was we were will with".split())


def card_text(card: Card) -> str:
    return "\n".join(p for p in (card.title, card.description, ", ".join(card.labels)) if p)


def text_hash(text: str) -> str:
    return hashlib.sha256(text.encode()).hexdigest()[:16]


def cosine(a: list, b: list) -> float:
    if len(a) != len(b):
        return 0.0
    top = sum(x * y for x, y in zip(a, b))
    size = math.sqrt(sum(x * x for x in a)) * math.sqrt(sum(y * y for y in b))
    return top / size if size else 0.0


def _words(text: str) -> set:
    return {w for w in re.findall(r"[a-z0-9]+", text.lower()) if w not in _STOP and len(w) > 1}


def keyword_score(a: str, b: str) -> float:
    """The share of words two texts have in common (Jaccard). Crude, cheap and always available."""
    left, right = _words(a), _words(b)
    return len(left & right) / len(left | right) if left and right else 0.0