Ajoute une étape de splitting entre extraction texte et parsing. Chaque chunk est traité indépendamment par le pipeline existant, avec suffixe _partN en sortie. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
219 lines
6.8 KiB
Python
219 lines
6.8 KiB
Python
"""Tests pour le module de découpage multi-dossiers."""
|
|
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
import pytest
|
|
|
|
from src.extraction.document_splitter import split_documents, _split_trackare, _split_crh
|
|
|
|
|
|
# --- Données de test ---
|
|
|
|
TRACKARE_PATIENT_BLOCK = """CENTRE HOSPITALIER COTE BASQUE
|
|
Dossier Patient
|
|
Détails des patients
|
|
Nom de naissance: DUPONT IPP: 01234567
|
|
Nom et Prénom: DUPONT JEAN Date de naissance: 15/03/1960
|
|
Sexe: Masculin Lieu de naissance: BAYONNE
|
|
Adresse: 12 RUE DU PORT Ville de résidence: BAYONNE
|
|
Code Postal: 64100"""
|
|
|
|
TRACKARE_EPISODE_1 = """Détails épisode
|
|
Episode No: 23001111
|
|
Date d'admission: 10/01/2023 Heure d'admission: 14:30
|
|
Date de sortie: 10/01/2023 Heure de sortie: 18:00
|
|
Localisation: URG-ADULTE Médecin courant: DR MARTIN
|
|
Observations médicales
|
|
Note d'évolution DR MARTIN 10/01/2023 15:00
|
|
Douleur abdominale. Bilan normal.
|
|
Diagnostic aux urgences
|
|
Principal actif R10.4 Douleurs abdominales 10/01/2023 15:30"""
|
|
|
|
TRACKARE_EPISODE_2 = """Détails épisode
|
|
Episode No: 23002222
|
|
Date d'admission: 15/03/2023 Heure d'admission: 09:15
|
|
Date de sortie: 15/03/2023 Heure de sortie: 13:00
|
|
Localisation: URG-ADULTE Médecin courant: DR DURAND
|
|
Observations médicales
|
|
Note d'évolution DR DURAND 15/03/2023 10:00
|
|
Chute mécanique. Radio cheville gauche normale.
|
|
Diagnostic aux urgences
|
|
Principal actif S93.4 Entorse de la cheville 15/03/2023 10:30"""
|
|
|
|
TRACKARE_SINGLE = TRACKARE_PATIENT_BLOCK + "\n" + TRACKARE_EPISODE_1
|
|
TRACKARE_MULTI = TRACKARE_PATIENT_BLOCK + "\n" + TRACKARE_EPISODE_1 + "\n" + TRACKARE_EPISODE_2
|
|
|
|
|
|
CRH_SINGLE = """MME MARTIN SOPHIE
|
|
12 RUE DES FLEURS
|
|
64100 BAYONNE
|
|
|
|
Mon cher confrère,
|
|
Votre patiente MARTIN Sophie née le 05/06/1975 a été hospitalisée
|
|
du 20/01/2023 au 25/01/2023 pour le motif suivant:
|
|
Cholécystite aiguë lithiasique.
|
|
|
|
Cordialement,
|
|
Dr DURAND"""
|
|
|
|
CRH_DOC_1 = """MME MARTIN SOPHIE
|
|
12 RUE DES FLEURS
|
|
64100 BAYONNE
|
|
|
|
Mon cher confrère,
|
|
Votre patiente MARTIN Sophie née le 05/06/1975 a été hospitalisée
|
|
du 20/01/2023 au 25/01/2023 pour le motif suivant:
|
|
Cholécystite aiguë lithiasique.
|
|
|
|
Cordialement,
|
|
Dr DURAND
|
|
"""
|
|
|
|
CRH_DOC_2 = """M. BERNARD PIERRE
|
|
5 AVENUE DU MARÉCHAL
|
|
64200 BIARRITZ
|
|
|
|
Mon cher confrère,
|
|
Votre patient BERNARD Pierre né le 12/11/1950 a été hospitalisé
|
|
du 02/02/2023 au 08/02/2023 pour le motif suivant:
|
|
Décompensation cardiaque.
|
|
|
|
Cordialement,
|
|
Dr PUJOS
|
|
"""
|
|
|
|
CRH_MULTI = CRH_DOC_1 + CRH_DOC_2
|
|
|
|
|
|
# --- Tests Trackare ---
|
|
|
|
class TestSplitTrackare:
|
|
def test_single_episode_returns_unchanged(self):
|
|
result = _split_trackare(TRACKARE_SINGLE)
|
|
assert len(result) == 1
|
|
assert result[0] == TRACKARE_SINGLE
|
|
|
|
def test_multi_episode_returns_two_chunks(self):
|
|
result = _split_trackare(TRACKARE_MULTI)
|
|
assert len(result) == 2
|
|
|
|
def test_each_chunk_has_patient_block(self):
|
|
result = _split_trackare(TRACKARE_MULTI)
|
|
for chunk in result:
|
|
assert "DUPONT JEAN" in chunk
|
|
assert "IPP: 01234567" in chunk
|
|
|
|
def test_each_chunk_has_own_episode(self):
|
|
result = _split_trackare(TRACKARE_MULTI)
|
|
assert "Episode No: 23001111" in result[0]
|
|
assert "Episode No: 23002222" in result[1]
|
|
|
|
def test_episodes_are_separated(self):
|
|
result = _split_trackare(TRACKARE_MULTI)
|
|
# Le premier chunk ne doit PAS contenir l'épisode 2
|
|
assert "Episode No: 23002222" not in result[0]
|
|
# Le second chunk ne doit PAS contenir l'épisode 1
|
|
assert "Episode No: 23001111" not in result[1]
|
|
|
|
def test_no_episode_returns_unchanged(self):
|
|
text = "Un texte sans épisode du tout."
|
|
result = _split_trackare(text)
|
|
assert len(result) == 1
|
|
assert result[0] == text
|
|
|
|
def test_fallback_without_details_episode(self):
|
|
"""Si pas de 'Détails épisode', coupe sur 'Episode No:'."""
|
|
text = (
|
|
"Nom de naissance: DUPONT IPP: 01234567\n"
|
|
"Episode No: 111\nContenu épisode 1\n"
|
|
"Episode No: 222\nContenu épisode 2"
|
|
)
|
|
result = _split_trackare(text)
|
|
assert len(result) == 2
|
|
assert "Episode No: 111" in result[0]
|
|
assert "Episode No: 222" in result[1]
|
|
|
|
|
|
# --- Tests CRH ---
|
|
|
|
class TestSplitCRH:
|
|
def test_single_crh_returns_unchanged(self):
|
|
result = _split_crh(CRH_SINGLE)
|
|
assert len(result) == 1
|
|
assert result[0] == CRH_SINGLE
|
|
|
|
def test_multi_crh_returns_two_chunks(self):
|
|
result = _split_crh(CRH_MULTI)
|
|
assert len(result) == 2
|
|
|
|
def test_each_chunk_is_independent(self):
|
|
result = _split_crh(CRH_MULTI)
|
|
assert "MARTIN SOPHIE" in result[0]
|
|
assert "BERNARD PIERRE" in result[1]
|
|
|
|
def test_chunks_contain_medical_content(self):
|
|
result = _split_crh(CRH_MULTI)
|
|
assert "Cholécystite" in result[0]
|
|
assert "Décompensation cardiaque" in result[1]
|
|
|
|
def test_no_header_returns_unchanged(self):
|
|
text = "Un texte médical sans header patient."
|
|
result = _split_crh(text)
|
|
assert len(result) == 1
|
|
|
|
def test_header_without_crh_markers_no_split(self):
|
|
"""Un header patient sans patterns CRH ne déclenche pas de split."""
|
|
text = "MME DUPONT MARIE\nTexte quelconque\nMME MARTIN SOPHIE\nAutre texte"
|
|
result = _split_crh(text)
|
|
assert len(result) == 1
|
|
|
|
|
|
# --- Tests dispatch ---
|
|
|
|
class TestSplitDocuments:
|
|
def test_dispatch_trackare(self):
|
|
result = split_documents(TRACKARE_MULTI, "trackare")
|
|
assert len(result) == 2
|
|
|
|
def test_dispatch_crh(self):
|
|
result = split_documents(CRH_MULTI, "crh")
|
|
assert len(result) == 2
|
|
|
|
def test_dispatch_unknown_type(self):
|
|
result = split_documents("some text", "unknown")
|
|
assert len(result) == 1
|
|
assert result[0] == "some text"
|
|
|
|
def test_single_doc_no_split(self):
|
|
result = split_documents(TRACKARE_SINGLE, "trackare")
|
|
assert len(result) == 1
|
|
|
|
|
|
# --- Test intégration process_pdf ---
|
|
|
|
class TestProcessPdfMulti:
|
|
@patch("src.main.extract_text")
|
|
@patch("src.main.extract_medical_info")
|
|
@patch("src.main._run_edsnlp", return_value=None)
|
|
@patch("src.main._use_edsnlp", False)
|
|
def test_multi_episode_returns_multiple_results(
|
|
self, mock_edsnlp, mock_medical, mock_extract
|
|
):
|
|
from pathlib import Path
|
|
from src.main import process_pdf
|
|
from src.config import DossierMedical, Diagnostic
|
|
|
|
# Mock extract_text retournant un texte multi-épisodes Trackare
|
|
mock_extract.return_value = TRACKARE_MULTI
|
|
|
|
# Mock extract_medical_info retournant un DossierMedical minimal
|
|
mock_medical.return_value = DossierMedical(
|
|
diagnostic_principal=Diagnostic(texte="test"),
|
|
)
|
|
|
|
results = process_pdf(Path("fake.pdf"))
|
|
assert len(results) == 2
|
|
# Chaque résultat est un tuple (text, dossier, report)
|
|
for anon_text, dossier, report in results:
|
|
assert isinstance(dossier, DossierMedical)
|