Python 64.6%
TypeScript 33.7%
CSS 0.8%
1"""Text chunking (≈600–900 tokens, overlap 120) that respects headings and paragraphs."""23from __future__ import annotations45import re6import unicodedata7from dataclasses import dataclass, field89CHARS_PER_TOKEN = 410TARGET = 750 * CHARS_PER_TOKEN11MAX = 900 * CHARS_PER_TOKEN12OVERLAP = 120 * CHARS_PER_TOKEN131415@dataclass16class Chunk:17 content: str18 module: str = ""19 section: str = ""20 page: str = ""21 url: str = ""22 metadata: dict = field(default_factory=dict)232425def split_paragraphs(text: str) -> list[str]:26 parts = re.split(r"\n\s*\n", text.strip())27 return [p.strip() for p in parts if p.strip()]282930def chunk_text(text: str, **meta: str) -> list[Chunk]:31 """Greedy paragraph packing with overlap; oversize paragraphs are hard-split."""32 paras = split_paragraphs(text)33 chunks: list[Chunk] = []34 buf = ""35 for p in paras:36 if len(p) > MAX:37 if buf:38 chunks.append(Chunk(buf.strip(), **meta))39 buf = ""40 for i in range(0, len(p), TARGET - OVERLAP):41 chunks.append(Chunk(p[i:i + TARGET].strip(), **meta))42 continue43 if len(buf) + len(p) + 2 > TARGET and buf:44 chunks.append(Chunk(buf.strip(), **meta))45 buf = buf[-OVERLAP:] + "\n\n" + p if OVERLAP else p46 else:47 buf = f"{buf}\n\n{p}" if buf else p48 if buf.strip():49 chunks.append(Chunk(buf.strip(), **meta))50 return [c for c in chunks if len(c.content) > 40]515253# ------------------------------------------------------------------ tokenisation (BM25)54STOPWORDS = set("""55le la les l un une des du de d et ou où mais donc or ni car à au aux en dans par pour sur sous56avec sans ce cet cette ces se sa son ses leur leurs mon ma mes ton ta tes notre nos votre vos57qui que quoi dont il elle ils elles on nous vous je tu y ne pas plus moins très est sont été58être avoir a ont fait faire peut peuvent doit doivent comme si the of and to in is are for59""".split())606162def strip_accents(s: str) -> str:63 return "".join(c for c in unicodedata.normalize("NFD", s) if unicodedata.category(c) != "Mn")646566def tokenize(text: str) -> list[str]:67 text = strip_accents(text.lower())68 words = re.findall(r"[a-z0-9]+", text)69 out = []70 for w in words:71 if w in STOPWORDS or len(w) < 2:72 continue73 # light French stemming: plural / feminine endings74 for suf in ("ements", "ement", "tions", "tion", "ees", "es", "s", "x", "e"):75 if len(w) > 5 and w.endswith(suf):76 w = w[: -len(suf)]77 break78 out.append(w)79 return out80