Initial commit
This commit is contained in:
252
tests/test_alphabetical_index_mapping.py
Normal file
252
tests/test_alphabetical_index_mapping.py
Normal file
@@ -0,0 +1,252 @@
|
||||
"""
|
||||
Tests pour le mapping des termes cliniques vers les codes via les index alphabétiques.
|
||||
|
||||
Exigences: 27.3, 27.4
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from pipeline_mco_pmsi.rag.rag_engine import RAGEngine
|
||||
from pipeline_mco_pmsi.rag.referentiels_manager import ReferentielsManager, Chunk
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def rag_engine_with_alpha_index(tmp_path):
|
||||
"""Crée un RAGEngine avec des index alphabétiques de test."""
|
||||
# Créer des chunks d'index alphabétique simulés
|
||||
alpha_chunks_cim10 = [
|
||||
Chunk(
|
||||
chunk_id="cim10_alpha_2026_0",
|
||||
referentiel_type="cim10",
|
||||
referentiel_version="2026",
|
||||
content="""Gastrite - voir K29.7
|
||||
Gastrite aiguë - K29.0
|
||||
Gastrite chronique - K29.5
|
||||
Gastro-entérite - A09""",
|
||||
metadata={"chunk_type": "alphabetical_index", "letter": "G", "codes": "K29.7,K29.0,K29.5,A09"},
|
||||
chunk_index=0
|
||||
),
|
||||
Chunk(
|
||||
chunk_id="cim10_alpha_2026_1",
|
||||
referentiel_type="cim10",
|
||||
referentiel_version="2026",
|
||||
content="""Appendicite - K35
|
||||
Appendicite aiguë - K35.8
|
||||
Appendicite chronique - K36""",
|
||||
metadata={"chunk_type": "alphabetical_index", "letter": "A", "codes": "K35,K35.8,K36"},
|
||||
chunk_index=1
|
||||
)
|
||||
]
|
||||
|
||||
# Créer des chunks de codes analytiques simulés
|
||||
code_chunks_cim10 = [
|
||||
Chunk(
|
||||
chunk_id="cim10_2026_0",
|
||||
referentiel_type="cim10",
|
||||
referentiel_version="2026",
|
||||
content="""K29.0 Gastrite aiguë hémorragique
|
||||
Gastrite aiguë avec hémorragie
|
||||
Exclut: érosion gastrique (K25.-)""",
|
||||
metadata={"chunk_type": "code", "code": "K29.0"},
|
||||
chunk_index=0
|
||||
),
|
||||
Chunk(
|
||||
chunk_id="cim10_2026_1",
|
||||
referentiel_type="cim10",
|
||||
referentiel_version="2026",
|
||||
content="""K29.7 Gastrite, sans précision
|
||||
Gastrite SAI""",
|
||||
metadata={"chunk_type": "code", "code": "K29.7"},
|
||||
chunk_index=1
|
||||
)
|
||||
]
|
||||
|
||||
# Sauvegarder les chunks dans le bon répertoire (data/ et non data/referentiels/)
|
||||
data_dir = tmp_path / "data"
|
||||
data_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
import json
|
||||
all_chunks = alpha_chunks_cim10 + code_chunks_cim10
|
||||
chunks_data = [
|
||||
{
|
||||
"chunk_id": c.chunk_id,
|
||||
"referentiel_type": c.referentiel_type,
|
||||
"referentiel_version": c.referentiel_version,
|
||||
"content": c.content,
|
||||
"metadata": c.metadata,
|
||||
"chunk_index": c.chunk_index
|
||||
}
|
||||
for c in all_chunks
|
||||
]
|
||||
|
||||
# Utiliser le bon nom de fichier dans data/
|
||||
chunks_file = data_dir / "cim10_2026_chunks.json"
|
||||
with open(chunks_file, "w", encoding="utf-8") as f:
|
||||
json.dump(chunks_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# Créer un index FAISS vide (pour les tests)
|
||||
import faiss
|
||||
import numpy as np
|
||||
dimension = 768
|
||||
index = faiss.IndexFlatL2(dimension)
|
||||
|
||||
# Ajouter des vecteurs aléatoires pour chaque chunk
|
||||
vectors = np.random.rand(len(all_chunks), dimension).astype('float32')
|
||||
index.add(vectors)
|
||||
|
||||
index_file = data_dir / "cim10_2026_index.faiss"
|
||||
faiss.write_index(index, str(index_file))
|
||||
|
||||
# Créer le ReferentielsManager et RAGEngine
|
||||
ref_manager = ReferentielsManager(data_dir=str(tmp_path / "data"))
|
||||
engine = RAGEngine(data_dir=str(tmp_path / "data"), referentiels_manager=ref_manager)
|
||||
return engine
|
||||
|
||||
|
||||
def test_map_clinical_term_to_codes_from_alpha_index(rag_engine_with_alpha_index):
|
||||
"""
|
||||
Test que le mapping d'un terme clinique vers des codes fonctionne
|
||||
en utilisant les index alphabétiques.
|
||||
|
||||
Exigence: 27.3
|
||||
"""
|
||||
# Mapper "Gastrite" vers des codes
|
||||
candidates = rag_engine_with_alpha_index.map_clinical_term_to_codes(
|
||||
clinical_term="Gastrite",
|
||||
referentiel_type="cim10",
|
||||
version="2026",
|
||||
top_k=3
|
||||
)
|
||||
|
||||
# Vérifier qu'on a des résultats
|
||||
assert len(candidates) > 0, "Aucun code trouvé pour 'Gastrite'"
|
||||
|
||||
# Vérifier que les codes sont pertinents
|
||||
codes = [c.code for c in candidates]
|
||||
assert any(code.startswith("K29") for code in codes), "Les codes K29.x (gastrite) devraient être trouvés"
|
||||
|
||||
# Vérifier que les résultats de l'index alphabétique ont un boost
|
||||
alpha_results = [c for c in candidates if c.source == "alphabetical_index"]
|
||||
if alpha_results:
|
||||
assert alpha_results[0].similarity_score > 0, "Le score devrait être positif"
|
||||
|
||||
|
||||
def test_map_clinical_term_handles_synonyms(rag_engine_with_alpha_index):
|
||||
"""
|
||||
Test que le mapping gère les synonymes et variations.
|
||||
|
||||
Exigence: 27.4
|
||||
"""
|
||||
# Mapper "Gastrite aiguë" (variation de "Gastrite")
|
||||
candidates = rag_engine_with_alpha_index.map_clinical_term_to_codes(
|
||||
clinical_term="Gastrite aiguë",
|
||||
referentiel_type="cim10",
|
||||
version="2026",
|
||||
top_k=3
|
||||
)
|
||||
|
||||
# Vérifier qu'on trouve le code spécifique
|
||||
codes = [c.code for c in candidates]
|
||||
assert "K29.0" in codes or any(code.startswith("K29") for code in codes), \
|
||||
"Le code K29.0 (gastrite aiguë) devrait être trouvé"
|
||||
|
||||
|
||||
def test_map_clinical_term_returns_top_k(rag_engine_with_alpha_index):
|
||||
"""
|
||||
Test que le mapping retourne au maximum top_k résultats.
|
||||
|
||||
Exigence: 27.3
|
||||
"""
|
||||
top_k = 2
|
||||
candidates = rag_engine_with_alpha_index.map_clinical_term_to_codes(
|
||||
clinical_term="Gastrite",
|
||||
referentiel_type="cim10",
|
||||
version="2026",
|
||||
top_k=top_k
|
||||
)
|
||||
|
||||
# Vérifier qu'on ne dépasse pas top_k
|
||||
assert len(candidates) <= top_k, f"Le nombre de résultats devrait être ≤ {top_k}"
|
||||
|
||||
|
||||
def test_map_clinical_term_deduplicates_codes(rag_engine_with_alpha_index):
|
||||
"""
|
||||
Test que le mapping déduplique les codes trouvés dans plusieurs sources.
|
||||
|
||||
Exigence: 27.3
|
||||
"""
|
||||
candidates = rag_engine_with_alpha_index.map_clinical_term_to_codes(
|
||||
clinical_term="Gastrite",
|
||||
referentiel_type="cim10",
|
||||
version="2026",
|
||||
top_k=10
|
||||
)
|
||||
|
||||
# Vérifier qu'il n'y a pas de doublons
|
||||
codes = [c.code for c in candidates]
|
||||
assert len(codes) == len(set(codes)), "Les codes ne devraient pas être dupliqués"
|
||||
|
||||
|
||||
def test_get_synonyms_and_variations(rag_engine_with_alpha_index):
|
||||
"""
|
||||
Test que la récupération de synonymes fonctionne.
|
||||
|
||||
Exigence: 27.4
|
||||
"""
|
||||
synonyms = rag_engine_with_alpha_index.get_synonyms_and_variations(
|
||||
term="Gastrite",
|
||||
referentiel_type="cim10",
|
||||
version="2026"
|
||||
)
|
||||
|
||||
# Vérifier qu'on a des synonymes
|
||||
# Note: Peut être vide si les chunks de test ne contiennent pas de synonymes
|
||||
assert isinstance(synonyms, list), "Le résultat devrait être une liste"
|
||||
|
||||
|
||||
def test_map_clinical_term_with_no_results(rag_engine_with_alpha_index):
|
||||
"""
|
||||
Test que le mapping gère correctement l'absence de résultats.
|
||||
|
||||
Exigence: 27.3
|
||||
"""
|
||||
candidates = rag_engine_with_alpha_index.map_clinical_term_to_codes(
|
||||
clinical_term="TermeInexistantXYZ123",
|
||||
referentiel_type="cim10",
|
||||
version="2026",
|
||||
top_k=5
|
||||
)
|
||||
|
||||
# Vérifier qu'on retourne une liste vide ou des résultats peu pertinents
|
||||
assert isinstance(candidates, list), "Le résultat devrait être une liste"
|
||||
# Les scores devraient être faibles si des résultats sont retournés
|
||||
if candidates:
|
||||
assert all(c.similarity_score < 0.5 for c in candidates), \
|
||||
"Les scores devraient être faibles pour un terme inexistant"
|
||||
|
||||
|
||||
def test_alphabetical_index_priority_in_reranking(rag_engine_with_alpha_index):
|
||||
"""
|
||||
Test que les résultats de l'index alphabétique sont priorisés dans le reranking.
|
||||
|
||||
Exigence: 27.6 (via Propriété 57)
|
||||
"""
|
||||
candidates = rag_engine_with_alpha_index.map_clinical_term_to_codes(
|
||||
clinical_term="Gastrite",
|
||||
referentiel_type="cim10",
|
||||
version="2026",
|
||||
top_k=5
|
||||
)
|
||||
|
||||
# Séparer les résultats par source
|
||||
alpha_results = [c for c in candidates if c.source == "alphabetical_index"]
|
||||
code_results = [c for c in candidates if c.source == "analytical_code"]
|
||||
|
||||
# Si on a des résultats des deux sources, vérifier la priorisation
|
||||
if alpha_results and code_results:
|
||||
# Les résultats de l'index alphabétique devraient avoir des scores plus élevés
|
||||
max_alpha_score = max(c.similarity_score for c in alpha_results)
|
||||
max_code_score = max(c.similarity_score for c in code_results)
|
||||
|
||||
assert max_alpha_score >= max_code_score, \
|
||||
"Les résultats de l'index alphabétique devraient avoir des scores plus élevés"
|
||||
Reference in New Issue
Block a user