253 lines
8.5 KiB
Python
253 lines
8.5 KiB
Python
"""
|
|
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"
|