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