Python 64.6%
TypeScript 33.7%
CSS 0.8%
1from app.rag.chunking import chunk_text, tokenize2from app.rag.ingest import parse_seance_html3from app.rag.retriever import BM25Index45HTML = """<html><body><main data-title="Séance 5 — Coût de reproduction et coût de remplacement">6<article>7<h2 id="s-5-1" class="h-section"><span class="h-num">5.1</span><span class="h-text">Cadrage</span><a class="h-anchor" href="#">#</a></h2>8<p>La formule fondamentale <span class="katex"><span class="katex-mathml"><math><semantics><mrow></mrow><annotation encoding="application/x-tex">V = V_T + C_N - D</annotation></semantics></math></span></span> structure la méthode du coût. Le terrain ne se déprécie pas.</p>9<div class="box box-definition"><div class="box-head"><span class="box-label">Définition</span><span class="box-title">Coût de reproduction</span></div><div class="box-body"><p>Coût de construire une réplique exacte du bâtiment avec les mêmes matériaux.</p></div></div>10<h2 id="s-5-2" class="h-section"><span class="h-num">5.2</span><span class="h-text">Le coût de remplacement</span></h2>11<p>Le coût de remplacement est le coût d'un bâtiment d'utilité équivalente construit selon les normes actuelles. Il est généralement inférieur au coût de reproduction pour un bâtiment ancien.</p>12</article></main></body></html>"""131415def test_tokenize_normalises_accents_and_plurals() -> None:16 assert tokenize("Dépréciations physiques") == tokenize("dépréciation physique")17 assert "le" not in tokenize("le coût de la valeur")181920def test_chunk_text_splits_long_text() -> None:21 text = "\n\n".join(f"Paragraphe {i} " + "mot " * 200 for i in range(20))22 chunks = chunk_text(text, module="M", section="S")23 assert len(chunks) > 124 assert all(c.module == "M" for c in chunks)252627def test_parse_seance_html_sections_and_katex() -> None:28 module, chunks = parse_seance_html(HTML, "IMM1033", "05")29 assert module.startswith("Séance 5")30 assert len(chunks) == 231 first = chunks[0]32 assert first.section.startswith("5.1")33 assert "$V = V_T + C_N - D$" in first.content34 assert "**Définition — Coût de reproduction**" in first.content35 assert first.url.endswith("/seance/05/#s-5-1")363738def test_bm25_search_ranks_relevant_section() -> None:39 _, chunks = parse_seance_html(HTML, "IMM1033", "05")4041 class Row:42 def __init__(self, i, c): # noqa: ANN00143 self.id, self.course_code, self.module, self.section = str(i), "IMM1033", c.module, c.section44 self.page, self.url, self.content, self.visibility, self.embedding = c.page, c.url, c.content, "students", None4546 idx = BM25Index()47 idx.build([Row(i, c) for i, c in enumerate(chunks)])48 hits = idx.search("coût de remplacement normes actuelles", top_k=2)49 assert hits and hits[0].section.startswith("5.2")50 assert idx.search("coût", courses={"IMM1003"}) == []51