Nourhenem commited on
Commit
1eb76aa
·
verified ·
1 Parent(s): 5bf9ba9

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .DS_Store +0 -0
  2. .gitattributes +7 -0
  3. .github/workflows/update_space.yml +28 -0
  4. .gitignore +14 -0
  5. .gradio/certificate.pem +31 -0
  6. .gradio/flagged/dataset1.csv +65 -0
  7. .python-version +1 -0
  8. Create_db_optimized.py +657 -0
  9. README.md +3 -9
  10. annotation.py +486 -0
  11. app.py +53 -0
  12. app2.py +116 -0
  13. correcteur.py +751 -0
  14. data_txt/template1.txt +48 -0
  15. data_txt/template10.txt +48 -0
  16. data_txt/template100.txt +28 -0
  17. data_txt/template1000.txt +3 -0
  18. data_txt/template1001.txt +9 -0
  19. data_txt/template1002.txt +1 -0
  20. data_txt/template1003.txt +9 -0
  21. data_txt/template1004.txt +1 -0
  22. data_txt/template1005.txt +9 -0
  23. data_txt/template1006.txt +1 -0
  24. data_txt/template1007.txt +10 -0
  25. data_txt/template1008.txt +6 -0
  26. data_txt/template1009.txt +10 -0
  27. data_txt/template101.txt +34 -0
  28. data_txt/template1010.txt +3 -0
  29. data_txt/template1011.txt +8 -0
  30. data_txt/template1012.txt +3 -0
  31. data_txt/template1013.txt +29 -0
  32. data_txt/template1014.txt +21 -0
  33. data_txt/template1015.txt +8 -0
  34. data_txt/template1016.txt +2 -0
  35. data_txt/template1017.txt +8 -0
  36. data_txt/template1018.txt +4 -0
  37. data_txt/template1019.txt +7 -0
  38. data_txt/template102.txt +29 -0
  39. data_txt/template1020.txt +2 -0
  40. data_txt/template1021.txt +9 -0
  41. data_txt/template1022.txt +5 -0
  42. data_txt/template1023.txt +8 -0
  43. data_txt/template1024.txt +3 -0
  44. data_txt/template1025.txt +8 -0
  45. data_txt/template1026.txt +1 -0
  46. data_txt/template1027.txt +7 -0
  47. data_txt/template1028.txt +3 -0
  48. data_txt/template1029.txt +7 -0
  49. data_txt/template103.txt +32 -0
  50. data_txt/template1030.txt +3 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
.gitattributes CHANGED
@@ -33,3 +33,10 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ my_env/lib/python3.11/site-packages/pip/_vendor/distlib/t64-arm.exe filter=lfs diff=lfs merge=lfs -text
37
+ my_env/lib/python3.11/site-packages/pip/_vendor/distlib/t64.exe filter=lfs diff=lfs merge=lfs -text
38
+ my_env/lib/python3.11/site-packages/pip/_vendor/distlib/w64-arm.exe filter=lfs diff=lfs merge=lfs -text
39
+ my_env/lib/python3.11/site-packages/pip/_vendor/distlib/w64.exe filter=lfs diff=lfs merge=lfs -text
40
+ my_env/lib/python3.11/site-packages/setuptools/cli-arm64.exe filter=lfs diff=lfs merge=lfs -text
41
+ my_env/lib/python3.11/site-packages/setuptools/gui-arm64.exe filter=lfs diff=lfs merge=lfs -text
42
+ templates/medical_templates.faiss filter=lfs diff=lfs merge=lfs -text
.github/workflows/update_space.yml ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Run Python script
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v2
15
+
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v2
18
+ with:
19
+ python-version: '3.9'
20
+
21
+ - name: Install Gradio
22
+ run: python -m pip install gradio
23
+
24
+ - name: Log in to Hugging Face
25
+ run: python -c 'import huggingface_hub; huggingface_hub.login(token="${{ secrets.hf_token }}")'
26
+
27
+ - name: Deploy to Spaces
28
+ run: gradio deploy
.gitignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ medical_env/
2
+ models/
3
+ transcriptions/
4
+ *.docx
5
+ *.json
6
+ *.rtf
7
+ *.pdf
8
+ *.doc
9
+ *.docx
10
+ *.rtf
11
+ *.log
12
+ *.pyc
13
+ *.pyo
14
+ .env
.gradio/certificate.pem ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
3
+ TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
4
+ cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
5
+ WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
6
+ ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
7
+ MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
8
+ h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
9
+ 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
10
+ A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
11
+ T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
12
+ B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
13
+ B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
14
+ KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
15
+ OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
16
+ jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
17
+ qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
18
+ rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
19
+ HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
20
+ hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
21
+ ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
22
+ 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
23
+ NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
24
+ ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
25
+ TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
26
+ jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
27
+ oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
28
+ 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
29
+ mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
30
+ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
31
+ -----END CERTIFICATE-----
.gradio/flagged/dataset1.csv ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 📝 Transcription médicale,✅ Transcription corrigée,📋 Rapport à remplir (Template),📄 Compte-rendu structuré final,timestamp
2
+ scanner thoraco abdomino pelvien et transit œsogastro décelable indication il est délimité un épaississement œsophagien point acquisition sans injection administration par voie orale de produit de contraste point à la ligne pas de foyer parenchymateux pas de nodule ni de masse à la ligne pas d épanchement pleural ou péricardique la pas d adénomégalie médiastinale ou axillaire il ne semble pas exister d épaississement œsophagien point a l étage abdomino pelvien hépatomégalie homogène point pas d anomalie décelée sur le pancréas les reins rate ou les surrénales point l estomac est de volume normal point pas d anomalie décelée sur le cadre duodénal ou les anses grêles opacifiées point pas de lésion osseuse point à la ligne transit œso gastro duodénal opacification rapide et douce de l œsophage avec des parois normales sans zone d addition ou de sténose point pas d anomalie de la cavité gastrique point à la ligne opacification rapide du cadre duodénal point à la ligne pas d anomalie des anses jéjunales opacifiées point pas de pneumopéritoine point à la ligne conclusion à la ligne scanner thoraco abdomino pelvien transito gastro duodénal sans lésion décelée à a confronter au reste du bilan et une endoscopie si nécessaire,"Scanner thoraco-abdomino-pelvien et transit œso-gastro décelable. Indication. Il est délimité un épaississement œsophagien. Acquisition sans injection. Administration par voie orale de produit de contraste.
3
+ Pas de foyer parenchymateux. Pas de nodule ni de masse.
4
+ Pas d'épanchement pleural ou péricardique. Pas d'adénomégalie médiastinale ou axillaire. Il ne semble pas exister d'épaississement œsophagien. À l'étage abdomino-pelvien, hépatomégalie homogène. Pas d'anomalie décelée sur le pancréas, les reins, la rate ou les surrénales. L'estomac est de volume normal. Pas d'anomalie décelée sur le cadre duodénal ou les anses grêles opacifiées. Pas de lésion osseuse.
5
+ Transit œso-gastro-duodénal. Opacification rapide et douce de l'œsophage avec des parois normales sans zone d'addition ou de sténose. Pas d'anomalie de la cavité gastrique.
6
+ Opacification rapide du cadre duodénal.
7
+ Pas d'anomalie des anses jéjunales opacifiées. Pas de pneumopéritoine.
8
+ Conclusion
9
+ Scanner thoraco-abdomino-pelvien, transit œso-gastro-duodénal, sans lésion décelée, à confronter au reste du bilan et à une endoscopie si nécessaire.","default.99.771703477
10
+ ====================
11
+ Examen tomodensitométrique $
12
+ Indication : <ASR_VOX>
13
+ Technique :
14
+ Examen réalisé sur un scanner REVOLUTION EVO.
15
+ Résultat :
16
+ $
17
+ Conclusion : $","default.99.771703477
18
+ ====================
19
+ TITRE :
20
+ Scanner thoraco-abdomino-pelvien et transit œso-gastro-duodénal
21
+
22
+ CLINIQUE :
23
+ Il est délimité un épaississement œsophagien.
24
+
25
+ TECHNIQUE :
26
+ Examen réalisé sur un scanner REVOLUTION EVO. Acquisition sans injection. Administration par voie orale de produit de contraste.
27
+
28
+ RESULTATS :
29
+ Pas de foyer parenchymateux. Pas de nodule ni de masse. Pas d'épanchement pleural ou péricardique. Pas d'adénomégalie médiastinale ou axillaire. Il ne semble pas exister d'épaississement œsophagien. À l'étage abdomino-pelvien, hépatomégalie homogène. Pas d'anomalie décelée sur le pancréas, les reins, la rate ou les surrénales. L'estomac est de volume normal. Pas d'anomalie décelée sur le cadre duodénal ou les anses grêles opacifiées. Pas de lésion osseuse. Transit œso-gastro-duodénal. Opacification rapide et douce de l'œsophage avec des parois normales sans zone d'addition ou de sténose. Pas d'anomalie de la cavité gastrique. Opacification rapide du cadre duodénal. Pas d'anomalie des anses jéjunales opacifiées. Pas de pneumopéritoine.
30
+
31
+ CONCLUSION :
32
+ Scanner thoraco-abdomino-pelvien, transit œso-gastro-duodénal, sans lésion décelée, à confronter au reste du bilan et à une endoscopie si nécessaire.
33
+ ",2025-10-02 16:07:30.158731
34
+ scanner thoraco abdomino pelvien et transit œsogastro décelable indication il est délimité un épaississement œsophagien point acquisition sans injection administration par voie orale de produit de contraste point à la ligne pas de foyer parenchymateux pas de nodule ni de masse à la ligne pas d épanchement pleural ou péricardique la pas d adénomégalie médiastinale ou axillaire il ne semble pas exister d épaississement œsophagien point a l étage abdomino pelvien hépatomégalie homogène point pas d anomalie décelée sur le pancréas les reins rate ou les surrénales point l estomac est de volume normal point pas d anomalie décelée sur le cadre duodénal ou les anses grêles opacifiées point pas de lésion osseuse point à la ligne transit œso gastro duodénal opacification rapide et douce de l œsophage avec des parois normales sans zone d addition ou de sténose point pas d anomalie de la cavité gastrique point à la ligne opacification rapide du cadre duodénal point à la ligne pas d anomalie des anses jéjunales opacifiées point pas de pneumopéritoine point à la ligne conclusion à la ligne scanner thoraco abdomino pelvien transito gastro duodénal sans lésion décelée à a confronter au reste du bilan et une endoscopie si nécessaire,"Scanner thoraco-abdomino-pelvien et transit œso-gastro décelable. Indication. Il est délimité un épaississement œsophagien. Acquisition sans injection. Administration par voie orale de produit de contraste.
35
+ Pas de foyer parenchymateux. Pas de nodule ni de masse.
36
+ Pas d'épanchement pleural ou péricardique. Pas d'adénomégalie médiastinale ou axillaire. Il ne semble pas exister d'épaississement œsophagien. À l'étage abdomino-pelvien, hépatomégalie homogène. Pas d'anomalie décelée sur le pancréas, les reins, la rate ou les surrénales. L'estomac est de volume normal. Pas d'anomalie décelée sur le cadre duodénal ou les anses grêles opacifiées. Pas de lésion osseuse.
37
+ Transit œso-gastro-duodénal. Opacification rapide et douce de l'œsophage avec des parois normales sans zone d'addition ou de sténose. Pas d'anomalie de la cavité gastrique.
38
+ Opacification rapide du cadre duodénal.
39
+ Pas d'anomalie des anses jéjunales opacifiées. Pas de pneumopéritoine.
40
+ Conclusion
41
+ Scanner thoraco-abdomino-pelvien, transit œso-gastro-duodénal, sans lésion décelée, à confronter au reste du bilan et à une endoscopie si nécessaire.","default.99.771703477
42
+ ====================
43
+ Examen tomodensitométrique $
44
+ Indication : <ASR_VOX>
45
+ Technique :
46
+ Examen réalisé sur un scanner REVOLUTION EVO.
47
+ Résultat :
48
+ $
49
+ Conclusion : $","default.99.771703477
50
+ ====================
51
+ TITRE :
52
+ Scanner thoraco-abdomino-pelvien et transit œso-gastro-duodénal
53
+
54
+ CLINIQUE :
55
+ Il est délimité un épaississement œsophagien.
56
+
57
+ TECHNIQUE :
58
+ Examen réalisé sur un scanner REVOLUTION EVO. Acquisition sans injection. Administration par voie orale de produit de contraste.
59
+
60
+ RESULTATS :
61
+ Pas de foyer parenchymateux. Pas de nodule ni de masse. Pas d'épanchement pleural ou péricardique. Pas d'adénomégalie médiastinale ou axillaire. Il ne semble pas exister d'épaississement œsophagien. À l'étage abdomino-pelvien, hépatomégalie homogène. Pas d'anomalie décelée sur le pancréas, les reins, la rate ou les surrénales. L'estomac est de volume normal. Pas d'anomalie décelée sur le cadre duodénal ou les anses grêles opacifiées. Pas de lésion osseuse. Transit œso-gastro-duodénal. Opacification rapide et douce de l'œsophage avec des parois normales sans zone d'addition ou de sténose. Pas d'anomalie de la cavité gastrique. Opacification rapide du cadre duodénal. Pas d'anomalie des anses jéjunales opacifiées. Pas de pneumopéritoine.
62
+
63
+ CONCLUSION :
64
+ Scanner thoraco-abdomino-pelvien, transit œso-gastro-duodénal, sans lésion décelée, à confronter au reste du bilan et à une endoscopie si nécessaire.
65
+ ",2025-10-02 16:07:33.131071
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.11.0
Create_db_optimized.py ADDED
@@ -0,0 +1,657 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import json
4
+ import numpy as np
5
+ from typing import List, Dict, Any, Optional, Tuple, Union
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ # Core libraries
10
+ import torch
11
+ from transformers import (
12
+ AutoTokenizer, AutoModel, AutoModelForTokenClassification,
13
+ TrainingArguments, Trainer, pipeline
14
+ )
15
+ from torch.utils.data import Dataset
16
+ import torch.nn.functional as F
17
+
18
+ # Vector database
19
+ import chromadb
20
+ from chromadb.config import Settings
21
+
22
+ # Utilities
23
+ import logging
24
+ from tqdm import tqdm
25
+ import pandas as pd
26
+
27
+ # Configure logging
28
+ logging.basicConfig(level=logging.INFO)
29
+ logger = logging.getLogger(__name__)
30
+
31
+ @dataclass
32
+ class MedicalEntity:
33
+ """Structure pour les entités médicales extraites par NER"""
34
+ exam_types: List[Tuple[str, float]] # (entity, confidence)
35
+ specialties: List[Tuple[str, float]]
36
+ anatomical_regions: List[Tuple[str, float]]
37
+ pathologies: List[Tuple[str, float]]
38
+ medical_procedures: List[Tuple[str, float]]
39
+ measurements: List[Tuple[str, float]]
40
+ medications: List[Tuple[str, float]]
41
+ symptoms: List[Tuple[str, float]]
42
+
43
+ class AdvancedMedicalNER:
44
+ """NER médical avancé basé sur CamemBERT-Bio fine-tuné"""
45
+
46
+ def __init__(self, model_name: str = "auto", cache_dir: str = "./models_cache"):
47
+ self.cache_dir = Path(cache_dir)
48
+ self.cache_dir.mkdir(exist_ok=True)
49
+
50
+ # Auto-détection du meilleur modèle NER médical disponible
51
+ self.model_name = self._select_best_model(model_name)
52
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
53
+
54
+ # Chargement du modèle NER
55
+ self._load_ner_model()
56
+
57
+ # Labels BIO pour entités médicales
58
+ self.entity_labels = [
59
+ "O", # Outside
60
+ "B-EXAM", "I-EXAM", # Types d'examens
61
+ "B-SPECIALTY", "I-SPECIALTY", # Spécialités médicales
62
+ "B-ANATOMY", "I-ANATOMY", # Régions anatomiques
63
+ "B-PATHOLOGY", "I-PATHOLOGY", # Pathologies
64
+ "B-PROCEDURE", "I-PROCEDURE", # Procédures médicales
65
+ "B-MEASURE", "I-MEASURE", # Mesures/valeurs
66
+ "B-MEDICATION", "I-MEDICATION", # Médicaments
67
+ "B-SYMPTOM", "I-SYMPTOM" # Symptômes
68
+ ]
69
+
70
+ self.id2label = {i: label for i, label in enumerate(self.entity_labels)}
71
+ self.label2id = {label: i for i, label in enumerate(self.entity_labels)}
72
+
73
+ def _select_best_model(self, model_name: str) -> str:
74
+ """Sélection automatique du meilleur modèle NER médical"""
75
+
76
+ if model_name != "auto":
77
+ return model_name
78
+
79
+ # Liste des modèles par ordre de préférence
80
+ preferred_models = [
81
+ "almanach/camembert-bio-base", # CamemBERT Bio français
82
+ "Dr-BERT/DrBERT-7GB", # DrBERT spécialisé
83
+ "emilyalsentzer/Bio_ClinicalBERT", # Bio Clinical BERT
84
+ "microsoft/BiomedNLP-PubMedBERT-base-uncased-abstract-fulltext",
85
+ "dmis-lab/biobert-base-cased-v1.2", # BioBERT
86
+ "camembert-base" # Fallback CamemBERT standard
87
+ ]
88
+
89
+ for model in preferred_models:
90
+ try:
91
+ # Test de disponibilité
92
+ AutoTokenizer.from_pretrained(model, cache_dir=self.cache_dir)
93
+ logger.info(f"Modèle sélectionné: {model}")
94
+ return model
95
+ except:
96
+ continue
97
+
98
+ # Fallback ultime
99
+ logger.warning("Utilisation du modèle de base camembert-base")
100
+ return "camembert-base"
101
+
102
+ def _load_ner_model(self):
103
+ """Charge ou crée le modèle NER fine-tuné"""
104
+
105
+ fine_tuned_path = self.cache_dir / "medical_ner_model"
106
+
107
+ if fine_tuned_path.exists():
108
+ logger.info("Chargement du modèle NER fine-tuné existant")
109
+ self.tokenizer = AutoTokenizer.from_pretrained(fine_tuned_path)
110
+ self.ner_model = AutoModelForTokenClassification.from_pretrained(fine_tuned_path)
111
+ else:
112
+ logger.info("Création d'un nouveau modèle NER médical")
113
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_name, cache_dir=self.cache_dir)
114
+
115
+ # Modèle pour classification de tokens (NER)
116
+ self.ner_model = AutoModelForTokenClassification.from_pretrained(
117
+ self.model_name,
118
+ num_labels=len(self.entity_labels),
119
+ id2label=self.id2label,
120
+ label2id=self.label2id,
121
+ cache_dir=self.cache_dir
122
+ )
123
+
124
+ self.ner_model.to(self.device)
125
+
126
+ # Pipeline NER
127
+ self.ner_pipeline = pipeline(
128
+ "token-classification",
129
+ model=self.ner_model,
130
+ tokenizer=self.tokenizer,
131
+ device=0 if torch.cuda.is_available() else -1,
132
+ aggregation_strategy="simple"
133
+ )
134
+
135
+ def extract_entities(self, text: str) -> MedicalEntity:
136
+ """Extraction d'entités avec le modèle NER fine-tuné"""
137
+
138
+ # Prédiction NER
139
+ try:
140
+ ner_results = self.ner_pipeline(text)
141
+ except Exception as e:
142
+ logger.error(f"Erreur NER: {e}")
143
+ return MedicalEntity([], [], [], [], [], [], [], [])
144
+
145
+ # Groupement des entités par type
146
+ entities = {
147
+ "EXAM": [],
148
+ "SPECIALTY": [],
149
+ "ANATOMY": [],
150
+ "PATHOLOGY": [],
151
+ "PROCEDURE": [],
152
+ "MEASURE": [],
153
+ "MEDICATION": [],
154
+ "SYMPTOM": []
155
+ }
156
+
157
+ for result in ner_results:
158
+ entity_type = result['entity_group'].replace('B-', '').replace('I-', '')
159
+ entity_text = result['word']
160
+ confidence = result['score']
161
+
162
+ if entity_type in entities and confidence > 0.7: # Seuil de confiance
163
+ entities[entity_type].append((entity_text, confidence))
164
+
165
+ return MedicalEntity(
166
+ exam_types=entities["EXAM"],
167
+ specialties=entities["SPECIALTY"],
168
+ anatomical_regions=entities["ANATOMY"],
169
+ pathologies=entities["PATHOLOGY"],
170
+ medical_procedures=entities["PROCEDURE"],
171
+ measurements=entities["MEASURE"],
172
+ medications=entities["MEDICATION"],
173
+ symptoms=entities["SYMPTOM"]
174
+ )
175
+
176
+ def fine_tune_on_templates(self, templates_data: List[Dict],
177
+ output_dir: str = None,
178
+ epochs: int = 3):
179
+ """Fine-tuning du modèle NER sur des templates médicaux"""
180
+
181
+ if output_dir is None:
182
+ output_dir = self.cache_dir / "medical_ner_model"
183
+
184
+ logger.info("Début du fine-tuning NER sur templates médicaux")
185
+
186
+ # Préparation des données d'entraînement
187
+ # (Ici, on utiliserait des templates annotés ou de l'auto-annotation)
188
+ train_dataset = self._prepare_training_data(templates_data)
189
+
190
+ # Configuration d'entraînement
191
+ training_args = TrainingArguments(
192
+ output_dir=output_dir,
193
+ num_train_epochs=epochs,
194
+ per_device_train_batch_size=8,
195
+ per_device_eval_batch_size=8,
196
+ warmup_steps=100,
197
+ weight_decay=0.01,
198
+ logging_dir=f"{output_dir}/logs",
199
+ save_strategy="epoch",
200
+ evaluation_strategy="epoch" if train_dataset.get('eval') else "no",
201
+ load_best_model_at_end=True,
202
+ metric_for_best_model="eval_loss" if train_dataset.get('eval') else None,
203
+ )
204
+
205
+ # Trainer
206
+ trainer = Trainer(
207
+ model=self.ner_model,
208
+ args=training_args,
209
+ train_dataset=train_dataset['train'],
210
+ eval_dataset=train_dataset.get('eval'),
211
+ tokenizer=self.tokenizer,
212
+ )
213
+
214
+ # Entraînement
215
+ trainer.train()
216
+
217
+ # Sauvegarde
218
+ trainer.save_model()
219
+ self.tokenizer.save_pretrained(output_dir)
220
+
221
+ logger.info(f"Fine-tuning terminé, modèle sauvé dans {output_dir}")
222
+
223
+ def _prepare_training_data(self, templates_data: List[Dict]) -> Dict:
224
+ """Prépare les données d'entraînement pour le NER (auto-annotation intelligente)"""
225
+
226
+ # Cette fonction pourrait utiliser des techniques d'auto-annotation
227
+ # ou des datasets médicaux pré-existants pour créer des labels BIO
228
+
229
+ # Pour l'exemple, retourner un dataset vide
230
+ # En production, on utiliserait des techniques d'annotation automatique
231
+ # ou des datasets médicaux annotés comme QUAERO, CAS, etc.
232
+
233
+ class EmptyDataset(Dataset):
234
+ def __len__(self):
235
+ return 0
236
+ def __getitem__(self, idx):
237
+ return {}
238
+
239
+ return {'train': EmptyDataset()}
240
+
241
+ class AdvancedMedicalEmbedding:
242
+ """Générateur d'embeddings médicaux avancés avec cross-encoder reranking"""
243
+
244
+ def __init__(self,
245
+ base_model: str = "almanach/camembert-bio-base",
246
+ cross_encoder_model: str = "auto"):
247
+
248
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
249
+ self.base_model_name = base_model
250
+
251
+ # Modèle principal pour embeddings
252
+ self._load_base_model()
253
+
254
+ # Cross-encoder pour reranking
255
+ self._load_cross_encoder(cross_encoder_model)
256
+
257
+ def _load_base_model(self):
258
+ """Charge le modèle de base pour les embeddings"""
259
+ try:
260
+ self.tokenizer = AutoTokenizer.from_pretrained(self.base_model_name)
261
+ self.base_model = AutoModel.from_pretrained(self.base_model_name)
262
+ self.base_model.to(self.device)
263
+ logger.info(f"Modèle de base chargé: {self.base_model_name}")
264
+ except Exception as e:
265
+ logger.error(f"Erreur chargement modèle de base: {e}")
266
+ raise
267
+
268
+ def _load_cross_encoder(self, model_name: str):
269
+ """Charge le cross-encoder pour reranking"""
270
+
271
+ if model_name == "auto":
272
+ # Sélection automatique du meilleur cross-encoder médical
273
+ cross_encoders = [
274
+ "microsoft/BiomedNLP-PubMedBERT-base-uncased-abstract-fulltext",
275
+ "emilyalsentzer/Bio_ClinicalBERT",
276
+ self.base_model_name # Fallback
277
+ ]
278
+
279
+ for model in cross_encoders:
280
+ try:
281
+ self.cross_tokenizer = AutoTokenizer.from_pretrained(model)
282
+ self.cross_model = AutoModel.from_pretrained(model)
283
+ self.cross_model.to(self.device)
284
+ logger.info(f"Cross-encoder chargé: {model}")
285
+ break
286
+ except:
287
+ continue
288
+ else:
289
+ self.cross_tokenizer = AutoTokenizer.from_pretrained(model_name)
290
+ self.cross_model = AutoModel.from_pretrained(model_name)
291
+ self.cross_model.to(self.device)
292
+
293
+ def generate_embedding(self, text: str, entities: MedicalEntity = None) -> np.ndarray:
294
+ """Génère un embedding enrichi pour un texte médical"""
295
+
296
+ # Tokenisation
297
+ inputs = self.tokenizer(
298
+ text,
299
+ padding=True,
300
+ truncation=True,
301
+ max_length=512,
302
+ return_tensors="pt"
303
+ ).to(self.device)
304
+
305
+ # Génération embedding
306
+ with torch.no_grad():
307
+ outputs = self.base_model(**inputs)
308
+
309
+ # Mean pooling
310
+ attention_mask = inputs['attention_mask']
311
+ token_embeddings = outputs.last_hidden_state
312
+ input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
313
+ embedding = torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp(input_mask_expanded.sum(1), min=1e-9)
314
+
315
+ # Enrichissement avec entités NER
316
+ if entities:
317
+ embedding = self._enrich_with_ner_entities(embedding, entities)
318
+
319
+ return embedding.cpu().numpy().flatten().astype(np.float32)
320
+
321
+ def _enrich_with_ner_entities(self, base_embedding: torch.Tensor, entities: MedicalEntity) -> torch.Tensor:
322
+ """Enrichit l'embedding avec les entités extraites par NER"""
323
+
324
+ # Concaténer les entités importantes avec leurs scores de confiance
325
+ entity_texts = []
326
+ confidence_weights = []
327
+
328
+ for entity_list in [entities.exam_types, entities.specialties,
329
+ entities.anatomical_regions, entities.pathologies]:
330
+ for entity_text, confidence in entity_list:
331
+ entity_texts.append(entity_text)
332
+ confidence_weights.append(confidence)
333
+
334
+ if not entity_texts:
335
+ return base_embedding
336
+
337
+ # Génération d'embeddings pour les entités
338
+ entity_text_combined = " [SEP] ".join(entity_texts)
339
+ entity_inputs = self.tokenizer(
340
+ entity_text_combined,
341
+ padding=True,
342
+ truncation=True,
343
+ max_length=256,
344
+ return_tensors="pt"
345
+ ).to(self.device)
346
+
347
+ with torch.no_grad():
348
+ entity_outputs = self.base_model(**entity_inputs)
349
+ entity_embedding = torch.mean(entity_outputs.last_hidden_state, dim=1)
350
+
351
+ # Fusion pondérée par les scores de confiance
352
+ avg_confidence = np.mean(confidence_weights) if confidence_weights else 0.5
353
+ fusion_weight = min(0.4, avg_confidence) # Max 40% pour les entités
354
+
355
+ enriched_embedding = (1 - fusion_weight) * base_embedding + fusion_weight * entity_embedding
356
+
357
+ return enriched_embedding
358
+
359
+ def cross_encoder_rerank(self,
360
+ query: str,
361
+ candidates: List[Dict],
362
+ top_k: int = 3) -> List[Dict]:
363
+ """Reranking avec cross-encoder pour affiner la sélection"""
364
+
365
+ if len(candidates) <= top_k:
366
+ return candidates
367
+
368
+ reranked_candidates = []
369
+
370
+ for candidate in candidates:
371
+ # Création de la paire query-candidate
372
+ pair_text = f"{query} [SEP] {candidate['document']}"
373
+
374
+ # Tokenisation
375
+ inputs = self.cross_tokenizer(
376
+ pair_text,
377
+ padding=True,
378
+ truncation=True,
379
+ max_length=512,
380
+ return_tensors="pt"
381
+ ).to(self.device)
382
+
383
+ # Score de similarité cross-encoder
384
+ with torch.no_grad():
385
+ outputs = self.cross_model(**inputs)
386
+ # Utilisation du [CLS] token pour le score de similarité
387
+ cls_embedding = outputs.last_hidden_state[:, 0, :]
388
+ similarity_score = torch.sigmoid(torch.mean(cls_embedding)).item()
389
+
390
+ candidate_copy = candidate.copy()
391
+ candidate_copy['cross_encoder_score'] = similarity_score
392
+ candidate_copy['final_score'] = (
393
+ 0.6 * candidate['similarity_score'] +
394
+ 0.4 * similarity_score
395
+ )
396
+
397
+ reranked_candidates.append(candidate_copy)
398
+
399
+ # Tri par score final
400
+ reranked_candidates.sort(key=lambda x: x['final_score'], reverse=True)
401
+
402
+ return reranked_candidates[:top_k]
403
+
404
+ class MedicalTemplateVectorDB:
405
+ """Base de données vectorielle optimisée pour templates médicaux"""
406
+
407
+ def __init__(self, db_path: str = "./medical_vector_db", collection_name: str = "medical_templates"):
408
+ self.db_path = db_path
409
+ self.collection_name = collection_name
410
+
411
+ # ChromaDB avec configuration optimisée
412
+ self.client = chromadb.PersistentClient(
413
+ path=db_path,
414
+ settings=Settings(
415
+ anonymized_telemetry=False,
416
+ allow_reset=True
417
+ )
418
+ )
419
+
420
+ # Collection avec métrique de distance optimisée
421
+ try:
422
+ self.collection = self.client.get_collection(collection_name)
423
+ logger.info(f"Collection '{collection_name}' chargée")
424
+ except:
425
+ self.collection = self.client.create_collection(
426
+ name=collection_name,
427
+ metadata={
428
+ "hnsw:space": "cosine",
429
+ "hnsw:M": 32, # Connectivité du graphe
430
+ "hnsw:ef_construction": 200, # Qualité vs vitesse construction
431
+ "hnsw:ef_search": 50 # Qualité vs vitesse recherche
432
+ }
433
+ )
434
+ logger.info(f"Collection '{collection_name}' créée avec optimisations HNSW")
435
+
436
+ def add_template(self,
437
+ template_id: str,
438
+ template_text: str,
439
+ embedding: np.ndarray,
440
+ entities: MedicalEntity,
441
+ metadata: Dict[str, Any] = None):
442
+ """Ajoute un template avec métadonnées enrichies par NER"""
443
+
444
+ # Métadonnées automatiques basées sur NER
445
+ auto_metadata = {
446
+ "exam_types": [entity[0] for entity in entities.exam_types],
447
+ "specialties": [entity[0] for entity in entities.specialties],
448
+ "anatomical_regions": [entity[0] for entity in entities.anatomical_regions],
449
+ "pathologies": [entity[0] for entity in entities.pathologies],
450
+ "procedures": [entity[0] for entity in entities.medical_procedures],
451
+ "text_length": len(template_text),
452
+ "entity_confidence_avg": np.mean([
453
+ entity[1] for entity_list in [
454
+ entities.exam_types, entities.specialties,
455
+ entities.anatomical_regions, entities.pathologies
456
+ ] for entity in entity_list
457
+ ]) if any([entities.exam_types, entities.specialties,
458
+ entities.anatomical_regions, entities.pathologies]) else 0.0
459
+ }
460
+
461
+ if metadata:
462
+ auto_metadata.update(metadata)
463
+
464
+ self.collection.add(
465
+ embeddings=[embedding.tolist()],
466
+ documents=[template_text],
467
+ metadatas=[auto_metadata],
468
+ ids=[template_id]
469
+ )
470
+
471
+ logger.info(f"Template {template_id} ajouté avec métadonnées NER automatiques")
472
+
473
+ def advanced_search(self,
474
+ query_embedding: np.ndarray,
475
+ n_results: int = 10,
476
+ entity_filters: Dict[str, List[str]] = None,
477
+ confidence_threshold: float = 0.0) -> List[Dict]:
478
+ """Recherche avancée avec filtres basés sur entités NER"""
479
+
480
+ where_clause = {}
481
+
482
+ # Filtres basés sur entités NER extraites
483
+ if entity_filters:
484
+ for entity_type, entity_values in entity_filters.items():
485
+ if entity_values:
486
+ where_clause[entity_type] = {"$in": entity_values}
487
+
488
+ # Filtre par confiance moyenne des entités
489
+ if confidence_threshold > 0:
490
+ where_clause["entity_confidence_avg"] = {"$gte": confidence_threshold}
491
+
492
+ results = self.collection.query(
493
+ query_embeddings=[query_embedding.tolist()],
494
+ n_results=n_results,
495
+ where=where_clause if where_clause else None,
496
+ include=["documents", "metadatas", "distances"]
497
+ )
498
+
499
+ # Formatage des résultats
500
+ formatted_results = []
501
+ for i in range(len(results['ids'][0])):
502
+ formatted_results.append({
503
+ 'id': results['ids'][0][i],
504
+ 'document': results['documents'][0][i],
505
+ 'metadata': results['metadatas'][0][i],
506
+ 'similarity_score': 1 - results['distances'][0][i],
507
+ 'distance': results['distances'][0][i]
508
+ })
509
+
510
+ return formatted_results
511
+
512
+ class AdvancedMedicalTemplateProcessor:
513
+ """Processeur avancé avec NER fine-tuné et reranking cross-encoder"""
514
+
515
+ def __init__(self,
516
+ base_model: str = "almanach/camembert-bio-base",
517
+ db_path: str = "./advanced_medical_vector_db"):
518
+
519
+ self.ner_extractor = AdvancedMedicalNER()
520
+ self.embedding_generator = AdvancedMedicalEmbedding(base_model)
521
+ self.vector_db = MedicalTemplateVectorDB(db_path)
522
+
523
+ logger.info("Processeur médical avancé initialisé avec NER fine-tuné et cross-encoder reranking")
524
+
525
+ def process_templates_batch(self,
526
+ templates: List[Dict[str, str]],
527
+ batch_size: int = 8,
528
+ fine_tune_ner: bool = False) -> None:
529
+ """Traitement avancé avec option de fine-tuning NER"""
530
+
531
+ if fine_tune_ner:
532
+ logger.info("Fine-tuning du modèle NER sur les templates...")
533
+ self.ner_extractor.fine_tune_on_templates(templates)
534
+
535
+ logger.info(f"Traitement avancé de {len(templates)} templates")
536
+
537
+ for i in tqdm(range(0, len(templates), batch_size), desc="Traitement avancé"):
538
+ batch = templates[i:i+batch_size]
539
+
540
+ for template in batch:
541
+ try:
542
+ template_id = template['id']
543
+ template_text = template['text']
544
+ metadata = template.get('metadata', {})
545
+
546
+ # NER avancé
547
+ entities = self.ner_extractor.extract_entities(template_text)
548
+
549
+ # Embedding enrichi
550
+ embedding = self.embedding_generator.generate_embedding(template_text, entities)
551
+
552
+ # Stockage avec métadonnées NER
553
+ self.vector_db.add_template(
554
+ template_id=template_id,
555
+ template_text=template_text,
556
+ embedding=embedding,
557
+ entities=entities,
558
+ metadata=metadata
559
+ )
560
+
561
+ except Exception as e:
562
+ logger.error(f"Erreur traitement template {template.get('id', 'unknown')}: {e}")
563
+ continue
564
+
565
+ def find_best_template_with_reranking(self,
566
+ transcription: str,
567
+ initial_candidates: int = 10,
568
+ final_results: int = 3) -> List[Dict]:
569
+ """Recherche optimale avec reranking cross-encoder"""
570
+
571
+ # 1. Extraction NER de la transcription
572
+ query_entities = self.ner_extractor.extract_entities(transcription)
573
+
574
+ # 2. Génération embedding enrichi
575
+ query_embedding = self.embedding_generator.generate_embedding(transcription, query_entities)
576
+
577
+ # 3. Filtres automatiques basés sur entités extraites
578
+ entity_filters = {}
579
+ if query_entities.exam_types:
580
+ entity_filters['exam_types'] = [entity[0] for entity in query_entities.exam_types]
581
+ if query_entities.specialties:
582
+ entity_filters['specialties'] = [entity[0] for entity in query_entities.specialties]
583
+ if query_entities.anatomical_regions:
584
+ entity_filters['anatomical_regions'] = [entity[0] for entity in query_entities.anatomical_regions]
585
+
586
+ # 4. Recherche vectorielle initiale
587
+ initial_candidates_results = self.vector_db.advanced_search(
588
+ query_embedding=query_embedding,
589
+ n_results=initial_candidates,
590
+ entity_filters=entity_filters,
591
+ confidence_threshold=0.6
592
+ )
593
+
594
+ # 5. Reranking avec cross-encoder
595
+ if len(initial_candidates_results) > final_results:
596
+ final_results_reranked = self.embedding_generator.cross_encoder_rerank(
597
+ query=transcription,
598
+ candidates=initial_candidates_results,
599
+ top_k=final_results
600
+ )
601
+ else:
602
+ final_results_reranked = initial_candidates_results
603
+
604
+ # 6. Enrichissement des résultats avec détails NER
605
+ for result in final_results_reranked:
606
+ result['query_entities'] = {
607
+ 'exam_types': query_entities.exam_types,
608
+ 'specialties': query_entities.specialties,
609
+ 'anatomical_regions': query_entities.anatomical_regions,
610
+ 'pathologies': query_entities.pathologies
611
+ }
612
+
613
+ return final_results_reranked
614
+
615
+ # Exemple d'utilisation avancée
616
+ def main():
617
+ """Exemple d'utilisation du système avancé"""
618
+
619
+ # Initialisation du processeur avancé
620
+ processor = AdvancedMedicalTemplateProcessor()
621
+
622
+ # Traitement des templates avec fine-tuning optionnel
623
+ sample_templates = [
624
+ {
625
+ 'id': 'angio_001',
626
+ 'text': """Échographie et doppler artério-veineux des membres inférieurs.
627
+ Exploration de l'incontinence veineuse superficielle...""",
628
+ 'metadata': {'source': 'angiologie', 'version': '2024'}
629
+ }
630
+ ]
631
+
632
+ # Traitement avec fine-tuning NER
633
+ processor.process_templates_batch(sample_templates, fine_tune_ner=False)
634
+
635
+ # Recherche avec reranking
636
+ transcription = """madame bacon nicole bilan œdème droit gonalgies ostéophytes
637
+ incontinence veineuse modérée portions surale droite crurale gauche saphéniennes"""
638
+
639
+ best_matches = processor.find_best_template_with_reranking(
640
+ transcription=transcription,
641
+ initial_candidates=15,
642
+ final_results=3
643
+ )
644
+
645
+ # Affichage des résultats
646
+ for i, match in enumerate(best_matches):
647
+ print(f"\n=== Match {i+1} ===")
648
+ print(f"Template ID: {match['id']}")
649
+ print(f"Score final: {match.get('final_score', match['similarity_score']):.4f}")
650
+ print(f"Score cross-encoder: {match.get('cross_encoder_score', 'N/A')}")
651
+ print(f"Entités détectées dans la query:")
652
+ for entity_type, entities in match.get('query_entities', {}).items():
653
+ if entities:
654
+ print(f" - {entity_type}: {[f'{e[0]} ({e[1]:.2f})' for e in entities]}")
655
+
656
+ if __name__ == "__main__":
657
+ main()
README.md CHANGED
@@ -1,12 +1,6 @@
1
  ---
2
- title: Medical Agent
3
- emoji: 🐨
4
- colorFrom: pink
5
- colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 5.48.0
8
- app_file: app.py
9
- pinned: false
10
  ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: medical-agent
3
+ app_file: app2.py
 
 
4
  sdk: gradio
5
+ sdk_version: 5.47.2
 
 
6
  ---
 
 
annotation.py ADDED
@@ -0,0 +1,486 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from openai import AzureOpenAI
4
+ import json
5
+
6
+ load_dotenv()
7
+
8
+ AZURE_OPENAI_KEY = os.getenv("AZURE_OPENAI_KEY")
9
+ AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
10
+ AZURE_OPENAI_DEPLOYMENT = os.getenv("AZURE_OPENAI_DEPLOYMENT") # deployment name
11
+ AZURE_OPENAI_API_VERSION = os.getenv("AZURE_OPENAI_API_VERSION", "2024-12-01-preview")
12
+
13
+ # Configure OpenAI for Azure
14
+ client = AzureOpenAI(
15
+ api_key=AZURE_OPENAI_KEY,
16
+ api_version=AZURE_OPENAI_API_VERSION,
17
+ azure_endpoint=AZURE_OPENAI_ENDPOINT
18
+ )
19
+
20
+ def extract_medical_entities(text: str) -> dict:
21
+ prompt = f""" You are a medical NER expert. Your task is to extract relevant entities from the given medical report text and return them in a JSON object.
22
+
23
+ Analyze the text carefully and identify the following fields:
24
+
25
+ - "exam_types": any type of medical test, examination, or diagnostic method performed on the patient.
26
+ - "specialties": the branch of medicine or medical discipline relevant to the report.
27
+ - "anatomical_regions": specific parts or regions of the body mentioned in the report.
28
+ - "pathologies": diagnosed diseases, disorders, or abnormal medical conditions noted in the report.
29
+ - "procedures": medical interventions, treatments, or actions performed on the patient.
30
+ - "measurements": numerical values or quantities recorded in the report, such as vital signs, lab results, sizes, or pressures.
31
+ - "medications": drugs, therapies, or prescribed substances mentioned in the report.
32
+ - "symptoms": patient-experienced signs or observable indications of a health issue.
33
+
34
+ Text to analyze:
35
+ \"\"\"
36
+ {text}
37
+ \"\"\"
38
+
39
+ Return ONLY a valid JSON object with all fields. If a field has no values, return an empty list.
40
+ """
41
+
42
+
43
+
44
+ response = client.chat.completions.create(
45
+ model=AZURE_OPENAI_DEPLOYMENT,
46
+ messages=[{"role": "user", "content": prompt}],
47
+ #temperature=0,
48
+ #max_tokens=1024
49
+ )
50
+
51
+ content = response.choices[0].message.content
52
+ try:
53
+ return json.loads(content)
54
+ except json.JSONDecodeError:
55
+ return {
56
+ "exam_types": [],
57
+ "specialties": [],
58
+ "anatomical_regions": [],
59
+ "pathologies": [],
60
+ "procedures": [],
61
+ "measurements": [],
62
+ "medications": [],
63
+ "symptoms": []
64
+ }
65
+ import json
66
+
67
+ def save_annotation(text: str, labels: dict, output_file="dataset.jsonl"):
68
+ record = {
69
+ "text": text,
70
+ "labels": labels
71
+ }
72
+ # append as one line of JSON
73
+ with open(output_file, "a", encoding="utf-8") as f:
74
+ f.write(json.dumps(record, ensure_ascii=False) + "\n")
75
+
76
+
77
+ if __name__ == "__main__":
78
+ input_folder = "data_txt" # 📂 folder containing your .txt files
79
+ output_file = "dataset.json"
80
+
81
+ # Ensure output file is empty before starting
82
+ open(output_file, "w", encoding="utf-8").close()
83
+
84
+ for filename in os.listdir(input_folder):
85
+ if filename.endswith(".txt"):
86
+ file_path = os.path.join(input_folder, filename)
87
+ with open(file_path, "r", encoding="utf-8") as f:
88
+ transcription = f.read().strip()
89
+
90
+ print(f"\n=== Processing {filename} ===")
91
+ entities = extract_medical_entities(transcription)
92
+
93
+ # Save results
94
+ save_annotation(transcription, entities, output_file=output_file)
95
+
96
+ print(f"✅ Saved {filename} → {output_file}")
97
+ """
98
+
99
+
100
+ if __name__ == "__main__":
101
+ input_folder = "data_txt" # 📂 folder containing your .txt files
102
+ output_file = "dataset.json"
103
+
104
+ # Liste des fichiers à exclure
105
+ excluded_files = {
106
+ "template7.txt",
107
+ "template1167.txt",
108
+ "template429.txt",
109
+ "template401.txt",
110
+ "template367.txt",
111
+ "template415.txt",
112
+ "template398.txt",
113
+ "template1198.txt",
114
+ "template159.txt",
115
+ "template165.txt",
116
+ "template1107.txt",
117
+ "template449.txt",
118
+ "template1113.txt",
119
+ "template313.txt",
120
+ "template475.txt",
121
+ "template461.txt",
122
+ "template307.txt",
123
+ "template893.txt",
124
+ "template139.txt",
125
+ "template887.txt",
126
+ "template677.txt",
127
+ "template111.txt",
128
+ "template105.txt",
129
+ "template663.txt",
130
+ "template688.txt",
131
+ "template850.txt",
132
+ "template844.txt",
133
+ "template878.txt",
134
+ "template16.txt",
135
+ "template703.txt",
136
+ "template717.txt",
137
+ "template924.txt",
138
+ "template930.txt",
139
+ "template918.txt",
140
+ "template1073.txt",
141
+ "template529.txt",
142
+ "template1067.txt",
143
+ "template267.txt",
144
+ "template501.txt",
145
+ "template515.txt",
146
+ "template273.txt",
147
+ "template298.txt",
148
+ "template1098.txt",
149
+ "template1099.txt",
150
+ "template299.txt",
151
+ "template514.txt",
152
+ "template272.txt",
153
+ "template266.txt",
154
+ "template500.txt",
155
+ "template528.txt",
156
+ "template1066.txt",
157
+ "template1072.txt",
158
+ "template919.txt",
159
+ "template931.txt",
160
+ "template925.txt",
161
+ "template716.txt",
162
+ "template702.txt",
163
+ "template879.txt",
164
+ "template845.txt",
165
+ "template851.txt",
166
+ "template689.txt",
167
+ "template104.txt",
168
+ "template662.txt",
169
+ "template676.txt",
170
+ "template110.txt",
171
+ "template138.txt",
172
+ "template886.txt",
173
+ "template892.txt",
174
+ "template460.txt",
175
+ "template306.txt",
176
+ "template312.txt",
177
+ "template474.txt",
178
+ "template1112.txt",
179
+ "template1106.txt",
180
+ "template448.txt",
181
+ "template338.txt",
182
+ "template1110.txt",
183
+ "template1104.txt",
184
+ "template304.txt",
185
+ "template462.txt",
186
+ "template476.txt",
187
+ "template310.txt",
188
+ "template1138.txt",
189
+ "template489.txt",
190
+ "template884.txt",
191
+ "template890.txt",
192
+ "template648.txt",
193
+ "template660.txt",
194
+ "template106.txt",
195
+ "template112.txt",
196
+ "template674.txt",
197
+ "template847.txt",
198
+ "template853.txt",
199
+ "template728.txt",
200
+ "template15.txt",
201
+ "template714.txt",
202
+ "template29.txt",
203
+ "template700.txt",
204
+ "template933.txt",
205
+ "template927.txt",
206
+ "template1064.txt",
207
+ "template1070.txt",
208
+ "template258.txt",
209
+ "template1058.txt",
210
+ "template270.txt",
211
+ "template516.txt",
212
+ "template502.txt",
213
+ "template264.txt",
214
+ "template503.txt",
215
+ "template265.txt",
216
+ "template271.txt",
217
+ "template1059.txt",
218
+ "template517.txt",
219
+ "template259.txt",
220
+ "template1071.txt",
221
+ "template1065.txt",
222
+ "template926.txt",
223
+ "template932.txt",
224
+ "template701.txt",
225
+ "template715.txt",
226
+ "template28.txt",
227
+ "template729.txt",
228
+ "template14.txt",
229
+ "template852.txt",
230
+ "template846.txt",
231
+ "template113.txt",
232
+ "template675.txt",
233
+ "template661.txt",
234
+ "template107.txt",
235
+ "template649.txt",
236
+ "template891.txt",
237
+ "template885.txt",
238
+ "template488.txt",
239
+ "template477.txt",
240
+ "template1139.txt",
241
+ "template311.txt",
242
+ "template305.txt",
243
+ "template463.txt",
244
+ "template1105.txt",
245
+ "template1111.txt",
246
+ "template339.txt",
247
+ "template467.txt",
248
+ "template1129.txt",
249
+ "template301.txt",
250
+ "template315.txt",
251
+ "template473.txt",
252
+ "template1115.txt",
253
+ "template1101.txt",
254
+ "template329.txt",
255
+ "template498.txt",
256
+ "template103.txt",
257
+ "template665.txt",
258
+ "template671.txt",
259
+ "template117.txt",
260
+ "template881.txt",
261
+ "template659.txt",
262
+ "template895.txt",
263
+ "template842.txt",
264
+ "template856.txt",
265
+ "template711.txt",
266
+ "template705.txt",
267
+ "template38.txt",
268
+ "template10.txt",
269
+ "template739.txt",
270
+ "template936.txt",
271
+ "template922.txt",
272
+ "template513.txt",
273
+ "template275.txt",
274
+ "template261.txt",
275
+ "template1049.txt",
276
+ "template507.txt",
277
+ "template249.txt",
278
+ "template1061.txt",
279
+ "template1075.txt",
280
+ "template1074.txt",
281
+ "template1060.txt",
282
+ "template248.txt",
283
+ "template1048.txt",
284
+ "template260.txt",
285
+ "template506.txt",
286
+ "template512.txt",
287
+ "template274.txt",
288
+ "template923.txt",
289
+ "template937.txt",
290
+ "template738.txt",
291
+ "template11.txt",
292
+ "template704.txt",
293
+ "template710.txt",
294
+ "template857.txt",
295
+ "template843.txt",
296
+ "template894.txt",
297
+ "template658.txt",
298
+ "template880.txt",
299
+ "template670.txt",
300
+ "template116.txt",
301
+ "template102.txt",
302
+ "template664.txt",
303
+ "template499.txt",
304
+ "template328.txt",
305
+ "template1100.txt",
306
+ "template1114.txt",
307
+ "template314.txt",
308
+ "template472.txt",
309
+ "template466.txt",
310
+ "template300.txt",
311
+ "template1128.txt",
312
+ "template470.txt",
313
+ "template316.txt",
314
+ "template302.txt",
315
+ "template464.txt",
316
+ "template1102.txt",
317
+ "template1116.txt",
318
+ "template458.txt",
319
+ "template114.txt",
320
+ "template672.txt",
321
+ "template666.txt",
322
+ "template100.txt",
323
+ "template128.txt",
324
+ "template896.txt",
325
+ "template882.txt",
326
+ "template869.txt",
327
+ "template855.txt",
328
+ "template699.txt",
329
+ "template841.txt",
330
+ "template706.txt",
331
+ "template712.txt",
332
+ "template13.txt",
333
+ "template909.txt",
334
+ "template921.txt",
335
+ "template935.txt",
336
+ "template504.txt",
337
+ "template262.txt",
338
+ "template276.txt",
339
+ "template510.txt",
340
+ "template538.txt",
341
+ "template1076.txt",
342
+ "template1062.txt",
343
+ "template1089.txt",
344
+ "template289.txt",
345
+ "template288.txt",
346
+ "template1088.txt",
347
+ "template1063.txt",
348
+ "template539.txt",
349
+ "template1077.txt",
350
+ "template277.txt",
351
+ "template511.txt",
352
+ "template505.txt",
353
+ "template263.txt",
354
+ "template934.txt",
355
+ "template920.txt",
356
+ "template908.txt",
357
+ "template12.txt",
358
+ "template713.txt",
359
+ "template707.txt",
360
+ "template840.txt",
361
+ "template698.txt",
362
+ "template854.txt",
363
+ "template868.txt",
364
+ "template883.txt",
365
+ "template129.txt",
366
+ "template897.txt",
367
+ "template667.txt",
368
+ "template101.txt",
369
+ "template115.txt",
370
+ "template673.txt",
371
+ "template1117.txt",
372
+ "template459.txt",
373
+ "template1103.txt",
374
+ "template303.txt",
375
+ "template465.txt",
376
+ "template471.txt",
377
+ "template317.txt",
378
+ "template4.txt",
379
+ "template1164.txt",
380
+ "template1170.txt",
381
+ "template358.txt",
382
+ "template416.txt",
383
+ "template1158.txt",
384
+ "template370.txt",
385
+ "template364.txt",
386
+ "template402.txt",
387
+ "template628.txt",
388
+ "template172.txt",
389
+ "template614.txt",
390
+ "template600.txt",
391
+ "template166.txt",
392
+ "template833.txt",
393
+ "template827.txt",
394
+ "template199.txt",
395
+ "template61.txt",
396
+ "template1212.txt",
397
+ "template984.txt",
398
+ "template748.txt",
399
+ "template990.txt",
400
+ "template75.txt",
401
+ "template1206.txt",
402
+ "template760.txt",
403
+ "template774.txt",
404
+ "template49.txt",
405
+ "template947.txt",
406
+ "template953.txt",
407
+ "template238.txt",
408
+ "template1010.txt",
409
+ "template1004.txt",
410
+ "template562.txt",
411
+ "template204.txt",
412
+ "template210.txt",
413
+ "template1038.txt",
414
+ "template576.txt",
415
+ "template589.txt",
416
+ "template588.txt",
417
+ "template1039.txt",
418
+ "template211.txt",
419
+ "template577.txt",
420
+ "template563.txt",
421
+ "template205.txt",
422
+ "template1005.txt",
423
+ "template1011.txt",
424
+ "template239.txt",
425
+ "template952.txt",
426
+ "template946.txt",
427
+ "template775.txt",
428
+ "template48.txt",
429
+ "template761.txt",
430
+ "template991.txt",
431
+ "template749.txt",
432
+ "template1207.txt",
433
+ "template74.txt",
434
+ "template1213.txt",
435
+ "template60.txt",
436
+ "template985.txt",
437
+ "template826.txt",
438
+ "template198.txt",
439
+ "template832.txt",
440
+ "template601.txt",
441
+ "template167.txt",
442
+ "template173.txt",
443
+ "template615.txt",
444
+ "template629.txt",
445
+ "template365.txt",
446
+ "template403.txt",
447
+ "template417.txt",
448
+ "template371.txt",
449
+ "template1159.txt",
450
+ "template359.txt",
451
+ "template1171.txt",
452
+ "template1165.txt",
453
+ "template5.txt",
454
+ "template1173.txt",
455
+ "template373.txt"
456
+ }
457
+
458
+ # Ensure output file is empty before starting
459
+ open(output_file, "w", encoding="utf-8").close()
460
+
461
+ processed_count = 0
462
+ excluded_count = 0
463
+
464
+ for filename in os.listdir(input_folder):
465
+ if filename.endswith(".txt"):
466
+ # Vérifier si le fichier est dans la liste d'exclusion
467
+ if filename in excluded_files:
468
+ print(f"⏭️ Fichier exclu : {filename}")
469
+ excluded_count += 1
470
+ continue
471
+
472
+ file_path = os.path.join(input_folder, filename)
473
+ with open(file_path, "r", encoding="utf-8") as f:
474
+ transcription = f.read().strip()
475
+
476
+ print(f"\n=== Processing {filename} ===")
477
+ entities = extract_medical_entities(transcription)
478
+
479
+ # Save results
480
+ save_annotation(transcription, entities, output_file=output_file)
481
+
482
+ print(f"✅ Saved {filename} → {output_file}")
483
+ processed_count += 1
484
+
485
+ print(f"\n📊 Résumé : {processed_count} fichiers traités, {excluded_count} fichiers exclus")
486
+ """
app.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Interface Gradio : Agent NER médical + Mapper
4
+ Input transcription → Extraction → Mapping → Rapport
5
+ """
6
+ import gradio as gr
7
+ from type3_extract_entities import MedicalNERAgent
8
+ from medical_template3_mapper import MedicalTemplateMapper
9
+ from type3_preprocessing import MedicalTranscriptionProcessor, AZURE_OPENAI_DEPLOYMENT
10
+ from post_processing import post_process_medical_report
11
+ def process_transcription(transcription: str):
12
+ try:
13
+ #Étape 1 correction asr
14
+ processor = MedicalTranscriptionProcessor(AZURE_OPENAI_DEPLOYMENT)
15
+ result = processor.process_transcription(transcription)
16
+ corrected_transcription=result.final_corrected_text
17
+ # Étape 1 : Extraction
18
+
19
+ agent = MedicalNERAgent()
20
+ extracted_data = agent.extract_medical_entities(corrected_transcription)
21
+ extraction_report = agent.print_extraction_report(extracted_data)
22
+
23
+ # Étape 2 : Mapping vers template
24
+ mapper = MedicalTemplateMapper()
25
+ mapping_result = mapper.map_extracted_data_to_template(extracted_data)
26
+ #mapping_report = mapper.print_mapping_report(mapping_result)
27
+ mapping_report = mapper.template
28
+
29
+ # Étape 3 : Rapport final rempli
30
+ rapport_final = mapping_result.filled_template
31
+
32
+ #Étape 4: nettoyage du rapport
33
+ cleaned_report = post_process_medical_report(rapport_final)
34
+
35
+ return corrected_transcription,extraction_report, mapping_report, cleaned_report
36
+ except Exception as e:
37
+ return f"Erreur: {e}", "", ""
38
+
39
+ # Interface Gradio
40
+ demo = gr.Interface(
41
+ fn=process_transcription,
42
+ inputs=gr.Textbox(lines=15, label="Transcription médicale"),
43
+ outputs=[
44
+ gr.Textbox(lines=20, label="🔬 Crorrection de la transcription"),
45
+ gr.Textbox(lines=20, label="📋 Extraction structurée"),
46
+ gr.Textbox(lines=20, label="📋 Rapport à remplir (Mapping)"),
47
+ gr.Textbox(lines=20, label="✅ Compte-rendu structuré final"),
48
+ ],
49
+ title="🏥 Génération de comptes-rendus structurés",
50
+ )
51
+
52
+ if __name__ == "__main__":
53
+ demo.launch(share=True)
app2.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Interface Gradio : Transcription médicale → Correction → Matching → Rapport
4
+ """
5
+ import gradio as gr
6
+ import sys
7
+ import os
8
+
9
+ # Fix pour le problème de pickle avec TemplateInfo
10
+ import template_db_creation
11
+ sys.modules['__main__'].TemplateInfo = template_db_creation.TemplateInfo
12
+
13
+ from template_db_creation import MedicalTemplateParser
14
+ from smart_match import TranscriptionMatcher
15
+ from correcteur import MedicalTranscriptionProcessor, AZURE_OPENAI_DEPLOYMENT
16
+
17
+ # Chemin hardcodé vers la base de données
18
+ DB_PATH = "/Users/macbook/medical-agent/medical-agent/templates/medical_templates.pkl"
19
+
20
+ # Variables globales
21
+ parser = None
22
+ matcher = None
23
+
24
+ def initialize_system():
25
+ """Initialise le système au démarrage"""
26
+ global parser, matcher
27
+
28
+ try:
29
+ print(f"📂 Chargement de la base de données: {DB_PATH}")
30
+ parser = MedicalTemplateParser()
31
+ parser.load_database(DB_PATH)
32
+
33
+ matcher = TranscriptionMatcher(parser)
34
+
35
+ print(f"✅ Système initialisé avec {len(parser.templates)} templates")
36
+ return True
37
+ except Exception as e:
38
+ print(f"❌ Erreur initialisation: {e}")
39
+ return False
40
+
41
+ def process_transcription(transcription: str):
42
+ """
43
+ Traite la transcription médicale complète
44
+
45
+ Args:
46
+ transcription: Texte de la transcription
47
+
48
+ Returns:
49
+ Tuple (transcription_corrigée, template_vide, rapport_final)
50
+ """
51
+ try:
52
+ # Étape 1: Correction ASR
53
+ print("🔧 Étape 1: Correction de la transcription...")
54
+ processor = MedicalTranscriptionProcessor(AZURE_OPENAI_DEPLOYMENT)
55
+ result = processor.process_transcription(transcription)
56
+ corrected_transcription = result.final_corrected_text
57
+
58
+ # Étape 2: Matching et remplissage du template
59
+ print("🔍 Étape 2: Recherche du template approprié...")
60
+ results = matcher.match_and_fill(corrected_transcription, return_top_k=1)
61
+
62
+ if not results:
63
+ return (
64
+ corrected_transcription,
65
+ "❌ Aucun template approprié trouvé",
66
+ "❌ Impossible de générer le rapport"
67
+ )
68
+
69
+ best_result = results[0]
70
+
71
+ # Préparer le template vide avec toutes les informations
72
+ template_vide = f"{best_result.template_id}\n"
73
+ template_vide += "=" * len(best_result.template_id) + "\n"
74
+ template_vide += best_result.template_content
75
+
76
+ # Préparer le rapport final rempli avec toutes les sections
77
+ rapport_final = f"{best_result.template_id}\n"
78
+ rapport_final += "=" * len(best_result.template_id) + "\n"
79
+
80
+ # Ajouter toutes les sections remplies
81
+ rapport_final += best_result.filled_template
82
+
83
+
84
+ print(f"✅ Traitement terminé - Template: {best_result.template_id}")
85
+
86
+ return corrected_transcription, template_vide, rapport_final
87
+
88
+ except Exception as e:
89
+ error_msg = f"❌ Erreur: {str(e)}"
90
+ print(error_msg)
91
+ return error_msg, "", ""
92
+
93
+ # Initialiser le système au démarrage
94
+ print("🚀 Initialisation du système...")
95
+ if not initialize_system():
96
+ print("⚠️ Erreur lors de l'initialisation - vérifiez le chemin de la DB")
97
+
98
+ # Interface Gradio
99
+ demo = gr.Interface(
100
+ fn=process_transcription,
101
+ inputs=gr.Textbox(
102
+ lines=15,
103
+ label="📝 Transcription médicale",
104
+ placeholder="Collez ici la transcription de l'examen médical..."
105
+ ),
106
+ outputs=[
107
+ gr.Textbox(lines=20, label="✅ Transcription corrigée", show_copy_button=True),
108
+ gr.Textbox(lines=20, label="📋 Rapport à remplir (Template)", show_copy_button=True),
109
+ gr.Textbox(lines=20, label="📄 Compte-rendu structuré final", show_copy_button=True),
110
+ ],
111
+ title="🏥 Génération de comptes-rendus structurés",
112
+
113
+ )
114
+
115
+ if __name__ == "__main__":
116
+ demo.launch(share=True)
correcteur.py ADDED
@@ -0,0 +1,751 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import spacy
2
+ import openai
3
+ import re
4
+ from typing import Dict, List, Tuple
5
+ import json
6
+ from dataclasses import dataclass
7
+ from datetime import datetime
8
+ from transformers import AutoTokenizer, AutoModelForTokenClassification, pipeline
9
+ import os
10
+ from dotenv import load_dotenv
11
+ from openai import AzureOpenAI
12
+ from medkit.core.text import TextDocument
13
+ from medkit.text.ner.hf_entity_matcher import HFEntityMatcher
14
+
15
+ NER_MODEL = os.getenv("NER_MODEL", "medkit/DrBERT-CASM2")
16
+
17
+ # Charger les variables d'environnement depuis .env
18
+ load_dotenv()
19
+
20
+ # Récupération des paramètres
21
+ AZURE_OPENAI_KEY = os.getenv("AZURE_OPENAI_KEY")
22
+ AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
23
+ AZURE_OPENAI_DEPLOYMENT = os.getenv("AZURE_OPENAI_DEPLOYMENT")
24
+ AZURE_OPENAI_API_VERSION = os.getenv("AZURE_OPENAI_API_VERSION", "2024-05-01-preview")
25
+
26
+ # Validation des variables d'environnement
27
+ def validate_azure_config():
28
+ """Valide que toutes les variables Azure sont configurées"""
29
+ missing_vars = []
30
+ if not AZURE_OPENAI_KEY:
31
+ missing_vars.append("AZURE_OPENAI_KEY")
32
+ if not AZURE_OPENAI_ENDPOINT:
33
+ missing_vars.append("AZURE_OPENAI_ENDPOINT")
34
+ if not AZURE_OPENAI_DEPLOYMENT:
35
+ missing_vars.append("AZURE_OPENAI_DEPLOYMENT")
36
+
37
+ if missing_vars:
38
+ print(f"❌ Variables d'environnement manquantes: {', '.join(missing_vars)}")
39
+ print("📝 Veuillez créer un fichier .env avec:")
40
+ for var in missing_vars:
41
+ print(f" {var}=votre_valeur")
42
+ return False
43
+ return True
44
+
45
+ # Client Azure OpenAI avec validation
46
+ azure_client = None
47
+ if validate_azure_config():
48
+ try:
49
+ azure_client = AzureOpenAI(
50
+ api_key=AZURE_OPENAI_KEY,
51
+ api_version=AZURE_OPENAI_API_VERSION,
52
+ azure_endpoint=AZURE_OPENAI_ENDPOINT,
53
+ )
54
+ print("✅ Client Azure OpenAI initialisé avec succès")
55
+ except Exception as e:
56
+ print(f"❌ Erreur lors de l'initialisation du client Azure OpenAI: {e}")
57
+ azure_client = None
58
+
59
+ ner_matcher = HFEntityMatcher(model=NER_MODEL)
60
+
61
+ @dataclass
62
+ class CorrectionResult:
63
+ original_text: str
64
+ ner_corrected_text: str
65
+ final_corrected_text: str
66
+ medical_entities: List[Dict]
67
+ confidence_score: float
68
+
69
+ class MedicalNERCorrector:
70
+ """Correcteur orthographique basé sur un NER médical français"""
71
+
72
+ def __init__(self):
73
+ try:
74
+ # Charger le modèle MedKit NER
75
+ self.matcher = HFEntityMatcher(model=NER_MODEL)
76
+ print(f"✅ Modèle NER '{NER_MODEL}' chargé avec succès")
77
+ except Exception as e:
78
+ print(f"❌ Erreur lors du chargement du modèle NER {NER_MODEL}: {e}")
79
+ self.matcher = None
80
+
81
+ # Dictionnaire complet pour convertir tous les nombres en lettres vers chiffres
82
+ self.number_corrections = {
83
+
84
+
85
+ # Variantes courantes dans les transcriptions vocales
86
+ "1": "1", "1er": "1", "première": "1", "premier": "1",
87
+ "2ème": "2", "deuxième": "2", "second": "2", "seconde": "2",
88
+ "3ème": "3", "troisième": "3", "4ème": "4", "quatrième": "4",
89
+ "5ème": "5", "cinquième": "5", "6ème": "6", "sixième": "6",
90
+ "7ème": "7", "septième": "7", "8ème": "8", "huitième": "8",
91
+ "9ème": "9", "neuvième": "9", "10ème": "10", "dixième": "10",
92
+ }
93
+
94
+ # Dictionnaire de corrections pour transcriptions vocales - ORDRE IMPORTANT
95
+ self.vocal_corrections = {
96
+ # Corrections de ponctuation - doivent être traitées en premier
97
+ "point à la ligne": ".\n",
98
+ "retour à la ligne": "\n",
99
+ "à la ligne": "\n",
100
+ "nouvelle ligne": "\n",
101
+ "saut de ligne": "\n",
102
+ "point virgule": ";",
103
+ "deux points": ":",
104
+ "point d'interrogation": "?",
105
+ "point d'exclamation": "!",
106
+ "virgule": ",",
107
+ "point": ".", # Doit être traité en dernier pour éviter les conflits
108
+
109
+ # Corrections des séquences IRM
110
+ "T un": "T1", "T deux": "T2", "T trois": "T3",
111
+ "t un": "T1", "t deux": "T2", "t trois": "T3",
112
+ "séquence T un": "séquence T1", "séquence T deux": "séquence T2",
113
+
114
+ # Corrections des niveaux vertébraux - cervicaux
115
+ "C un": "C1", "C deux": "C2", "C trois": "C3", "C quatre": "C4",
116
+ "C cinq": "C5", "C six": "C6", "C sept": "C7",
117
+ "c un": "C1", "c deux": "C2", "c trois": "C3", "c quatre": "C4",
118
+ "c cinq": "C5", "c six": "C6", "c sept": "C7",
119
+
120
+ # Niveaux thoraciques
121
+ "T un": "T1", "T deux": "T2", "T trois": "T3", "T quatre": "T4",
122
+ "T cinq": "T5", "T six": "T6", "T sept": "T7", "T huit": "T8",
123
+ "T neuf": "T9", "T dix": "T10", "T onze": "T11", "T douze": "T12",
124
+
125
+ # Niveaux lombaires
126
+ "L un": "L1", "L deux": "L2", "L trois": "L3", "L quatre": "L4", "L cinq": "L5",
127
+ "l un": "L1", "l deux": "L2", "l trois": "L3", "l quatre": "L4", "l cinq": "L5",
128
+
129
+ # Niveaux sacrés
130
+ "S un": "S1", "S deux": "S2", "S trois": "S3", "S quatre": "S4", "S cinq": "S5",
131
+ "s un": "S1", "s deux": "S2", "s trois": "S3", "s quatre": "S4", "s cinq": "S5",
132
+ }
133
+
134
+ # Dictionnaire de corrections médicales spécialisées - orthographe
135
+ self.medical_corrections = {
136
+ # Anatomie
137
+ "rachis": ["rachis", "rachi", "rachys", "rahis", "raxis"],
138
+ "cervical": ["cervical", "cervicale", "cervicaux", "servical", "servicale"],
139
+ "vertébraux": ["vertébraux", "vertebraux", "vertébrau", "vertébral", "vertebral"],
140
+ "médullaire": ["médullaire", "medullaire", "medulaire", "médulaire"],
141
+ "foraminal": ["foraminal", "foraminale", "foraminaux", "forraminal"],
142
+ "postérolatéral": ["postérolatéral", "posterolatéral", "postero-latéral", "postero latéral"],
143
+ "antérolatéral": ["antérolatéral", "anterolatéral", "antero-latéral", "antero latéral"],
144
+ "longitudinal": ["longitudinal", "longitudinale", "longitudinaux"],
145
+
146
+ # Pathologies
147
+ "uncarthrose": ["uncarthrose", "uncoarthrose", "uncartrose", "unkarthrose"],
148
+ "lordose": ["lordose", "lordoze", "lordosse", "lordosse"],
149
+ "cyphose": ["cyphose", "siphose", "kyphose", "kiphose"],
150
+ "scoliose": ["scoliose", "skoliose", "scholiose"],
151
+ "discopathie": ["discopathie", "disccopathie", "discopatie"],
152
+ "discal": ["discal", "discale", "diskal", "diskale", "disque"],
153
+ "hernie": ["hernie", "herny", "herni"],
154
+ "protrusion": ["protrusion", "protusion", "protruzion"],
155
+ "sténose": ["sténose", "stenose", "sténoze"],
156
+ "arthrose": ["arthrose", "artrose", "arthroze"],
157
+ "ostéophyte": ["ostéophyte", "osteophyte", "ostéofite"],
158
+ "ligamentaire": ["ligamentaire", "ligamentere", "ligamentair"],
159
+
160
+ # Techniques et examens
161
+ "sagittal": ["sagittal", "sagittale", "sagital", "sagittaux"],
162
+ "coronal": ["coronal", "coronale", "coronaux"],
163
+ "axial": ["axial", "axiale", "axiaux", "axial"],
164
+ "transversal": ["transversal", "transversale", "transversaux"],
165
+ "pondéré": ["pondéré", "pondéré", "pondere", "pondérée"],
166
+ "séquence": ["séquence", "sequence", "sekence"],
167
+ "contraste": ["contraste", "contraste", "kontraste"],
168
+ "gadolinium": ["gadolinium", "gadoliniun", "gadoliniom"],
169
+
170
+ # Mesures et directions
171
+ "millimètre": ["millimètre", "millimetre", "mm"],
172
+ "centimètre": ["centimètre", "centimetre", "cm"],
173
+ "gauche": ["gauche", "gosh", "goshe", "goche"],
174
+ "droite": ["droite", "droitte", "droithe", "droitr"],
175
+ "antérieur": ["antérieur", "anterieur", "antérieure", "anterieure"],
176
+ "postérieur": ["postérieur", "posterieur", "postérieure", "posterieure"],
177
+ "supérieur": ["supérieur", "superieur", "supérieure", "superieure"],
178
+ "inférieur": ["inférieur", "inferieur", "inférieure", "inferieure"],
179
+ "médian": ["médian", "median", "mediane", "médiane"],
180
+ "latéral": ["latéral", "lateral", "laterale", "latérale"],
181
+
182
+ # Signaux et aspects
183
+ "signal": ["signal", "signale", "signa", "signaux"],
184
+ "hypersignal": ["hypersignal", "hyper signal", "hypersignale"],
185
+ "hyposignal": ["hyposignal", "hypo signal", "hyposignale"],
186
+ "isosignal": ["isosignal", "iso signal", "isosignale"],
187
+ "hétérogène": ["hétérogène", "heterogene", "hétérogène"],
188
+ "homogène": ["homogène", "homogene", "omogene"],
189
+
190
+ # Autres termes fréquents
191
+ "dimension": ["dimension", "dimention", "dimmension"],
192
+ "normale": ["normale", "normal", "normalle"],
193
+ "anomalie": ["anomalie", "annomalie", "anomaly"],
194
+ "décelable": ["décelable", "decelabl", "décellabl"],
195
+ "absence": ["absence", "abscence", "absance"],
196
+ "présence": ["présence", "presence", "presance"],
197
+ "contact": ["contact", "contacte", "kontak"],
198
+ "compression": ["compression", "compresion", "kompression"],
199
+ }
200
+
201
+ # Expressions régulières pour les patterns médicaux
202
+ self.medical_patterns = {
203
+ "vertebral_level": r"[CTLS]\d+[\s-]*[CTLS]\d+",
204
+ "measurement": r"\d+[\s]*[x×]\s*\d+\s*mm",
205
+ "technique": r"T[1-3]",
206
+ }
207
+
208
+ def convert_numbers_to_digits(self, text: str) -> str:
209
+ """Convertit TOUS les nombres en lettres vers des chiffres"""
210
+ corrected_text = text
211
+
212
+ # ÉTAPE 1: Gestion spéciale des mesures médicales communes
213
+ medical_measures = {
214
+ # Mesures d'utérus courantes
215
+ "sept point huit": "7,8",
216
+ "trois sept": "3,7",
217
+ "soixante douze": "72",
218
+ "soixante treize": "73",
219
+ "soixante quatorze": "74",
220
+ "soixante quinze": "75",
221
+ "soixante seize": "76",
222
+ "soixante dix sept": "77",
223
+ "soixante dix huit": "78",
224
+ "soixante dix neuf": "79",
225
+
226
+
227
+ # Mesures d'ovaires courantes
228
+ "vingt six": "26",
229
+
230
+ "vingt cinq": "25",
231
+ "dix neuf": "19",
232
+ "vingt deux": "22",
233
+
234
+ # Mesures Doppler
235
+ "trois vingt quatre": "3,24", # IP
236
+ "quatre vingt onze": "0,91", # IR (avec virgule décimale)
237
+
238
+ # Autres mesures courantes
239
+ "quinze": "15",
240
+ }
241
+
242
+ # Application des mesures médicales en premier
243
+ for word_measure, digit_measure in medical_measures.items():
244
+ pattern = r'\b' + re.escape(word_measure) + r'\b'
245
+ corrected_text = re.sub(pattern, digit_measure, corrected_text, flags=re.IGNORECASE)
246
+
247
+ # ÉTAPE 2: Traitement des nombres composés restants
248
+ compound_without_dash = {
249
+ "vingt un": "21", "vingt deux": "22", "vingt trois": "23", "vingt quatre": "24",
250
+ "vingt cinq": "25", "vingt six": "26", "vingt sept": "27", "vingt huit": "28",
251
+ "vingt neuf": "29", "trente un": "31", "trente deux": "32", "trente trois": "33",
252
+ "trente quatre": "34", "trente cinq": "35", "trente six": "36", "trente sept": "37",
253
+ "trente huit": "38", "trente neuf": "39", "quarante un": "41", "quarante deux": "42",
254
+ "quarante trois": "43", "quarante quatre": "44", "quarante cinq": "45",
255
+ "quarante six": "46", "quarante sept": "47", "quarante huit": "48", "quarante neuf": "49",
256
+ "cinquante un": "51", "cinquante deux": "52", "cinquante trois": "53",
257
+ "cinquante quatre": "54", "cinquante cinq": "55", "cinquante six": "56",
258
+ "cinquante sept": "57", "cinquante huit": "58", "cinquante neuf": "59",
259
+ "soixante un": "61", "soixante deux": "62", "soixante trois": "63",
260
+ "soixante quatre": "64", "soixante cinq": "65", "soixante six": "66",
261
+ "soixante sept": "67", "soixante huit": "68", "soixante neuf": "69",
262
+ "soixante et onze": "71", "soixante douze": "72", "soixante treize": "73",
263
+ "soixante quatorze": "74", "soixante quinze": "75", "soixante seize": "76",
264
+ "soixante dix sept": "77", "soixante dix huit": "78", "soixante dix neuf": "79",
265
+ "quatre vingt un": "81", "quatre vingt deux": "82", "quatre vingt trois": "83",
266
+ "quatre vingt quatre": "84", "quatre vingt cinq": "85", "quatre vingt six": "86",
267
+ "quatre vingt sept": "87", "quatre vingt huit": "88", "quatre vingt neuf": "89",
268
+ "quatre vingt onze": "91", "quatre vingt douze": "92", "quatre vingt treize": "93",
269
+ "quatre vingt quatorze": "94", "quatre vingt quinze": "95", "quatre vingt seize": "96",
270
+ "quatre vingt dix sept": "97", "quatre vingt dix huit": "98", "quatre vingt dix neuf": "99",
271
+
272
+ }
273
+
274
+ for word, digit in compound_without_dash.items():
275
+ # Protection : ne remplace PAS si suivi de "fois" ET d'un autre nombre
276
+ pattern = r'\b' + re.escape(word) + r'\b(?!\s+fois\s+\w+)'
277
+ corrected_text = re.sub(pattern, digit, corrected_text, flags=re.IGNORECASE)
278
+
279
+ # ÉTAPE 3: Nombres simples (ordre modifié pour éviter les conflits)
280
+ simple_numbers = {
281
+ "zéro": "0", "deux": "2", "trois": "3", "quatre": "4",
282
+ "cinq": "5", "six": "6", "sept": "7", "huit": "8", "neuf": "9",
283
+ "dix": "10", "onze": "11", "douze": "12", "treize": "13", "quatorze": "14",
284
+ "quinze": "15", "seize": "16", "dix-sept": "17", "dix-huit": "18",
285
+ "dix-neuf": "19", "vingt": "20", "trente": "30", "quarante": "40",
286
+ "cinquante": "50", "soixante-dix": "70",
287
+ "quatre-vingts": "80", "quatre-vingt": "80", "quatre-vingt-dix": "90",
288
+ "cent": "100", "mille": "1000",
289
+ }
290
+
291
+ # Conversion des nombres simples
292
+ for word_number, digit in simple_numbers.items():
293
+ pattern = r'\b' + re.escape(word_number) + r'\b'
294
+ corrected_text = re.sub(pattern, digit, corrected_text, flags=re.IGNORECASE)
295
+
296
+ corrected_text = re.sub(r'\bpour\s+cent\b', '%', corrected_text, flags=re.IGNORECASE)
297
+
298
+ return corrected_text
299
+
300
+ def extract_medical_entities(self, text: str):
301
+ """Extrait les entités médicales avec MedKit HFEntityMatcher"""
302
+ if not self.matcher:
303
+ return []
304
+ doc = TextDocument(text)
305
+ entities = self.matcher.run([doc.raw_segment])
306
+ # Transformer en format simple
307
+ formatted_entities = []
308
+ for ent in entities:
309
+ formatted_entities.append({
310
+ "text": ent.text,
311
+ "label": ent.label,
312
+ })
313
+ return formatted_entities
314
+
315
+ def correct_vocal_transcription(self, text: str) -> str:
316
+ """Corrige les transcriptions vocales avec un ordre de priorité strict"""
317
+ corrected_text = text
318
+
319
+ # ÉTAPE 1: Conversion des nombres AVANT tout le reste
320
+ corrected_text = self.convert_numbers_to_digits(corrected_text)
321
+
322
+ # ÉTAPE 2: Corrections des expressions vocales dans l'ordre de priorité
323
+ # L'ordre est CRUCIAL pour éviter les conflits
324
+ priority_corrections = [
325
+ # Expressions de ponctuation complexes en premier
326
+ ("point à la ligne", ".\n"),
327
+ ("retour à la ligne", "\n"),
328
+ ("à la ligne", "\n"),
329
+ ("nouvelle ligne", "\n"),
330
+ ("saut de ligne", "\n"),
331
+ ("point virgule", ";"),
332
+ ("deux points", ":"),
333
+ ("point d'interrogation", "?"),
334
+ ("point d'exclamation", "!"),
335
+
336
+ # Niveaux vertébraux avec nombres
337
+ ("C 1", "C1"), ("C 2", "C2"), ("C 3", "C3"), ("C 4", "C4"),
338
+ ("C 5", "C5"), ("C 6", "C6"), ("C 7", "C7"),
339
+ ("L 1", "L1"), ("L 2", "L2"), ("L 3", "L3"), ("L 4", "L4"), ("L 5", "L5"),
340
+ ("T 1", "T1"), ("T 2", "T2"), ("T 3", "T3"), ("T 4", "T4"),
341
+ ("T 5", "T5"), ("T 6", "T6"), ("T 7", "T7"), ("T 8", "T8"),
342
+ ("T 9", "T9"), ("T 10", "T10"), ("T 11", "T11"), ("T 12", "T12"),
343
+
344
+ # Séquences IRM
345
+ ("séquence T 1", "séquence T1"), ("séquence T 2", "séquence T2"),
346
+
347
+ # Virgule et point en dernier pour éviter les conflits
348
+ ("virgule", ","),
349
+ ]
350
+
351
+ for vocal_term, replacement in priority_corrections:
352
+ # Utilisation de word boundaries pour éviter les remplacements partiels
353
+ pattern = r'\b' + re.escape(vocal_term) + r'\b'
354
+ corrected_text = re.sub(pattern, replacement, corrected_text, flags=re.IGNORECASE)
355
+
356
+ # ÉTAPE 3: Correction spéciale pour "point" - seulement si c'est vraiment une fin de phrase
357
+ # Pattern pour détecter "point" suivi d'un espace et d'une majuscule OU en fin de texte
358
+ corrected_text = re.sub(r'\bpoint(?!\s+(?:à|d\'|virgule))', '.', corrected_text, flags=re.IGNORECASE)
359
+
360
+ return corrected_text
361
+
362
+ def correct_medical_terms(self, text: str) -> str:
363
+ """Corrige les termes médicaux basés sur le dictionnaire"""
364
+ corrected_text = text
365
+
366
+ for correct_term, variations in self.medical_corrections.items():
367
+ for variation in variations:
368
+ if variation != correct_term: # Éviter de remplacer par lui-même
369
+ # Correction avec préservation de la casse du premier caractère
370
+ pattern = r'\b' + re.escape(variation) + r'\b'
371
+
372
+ def replace_with_case(match):
373
+ matched_text = match.group(0)
374
+ if matched_text[0].isupper():
375
+ return correct_term.capitalize()
376
+ return correct_term
377
+
378
+ corrected_text = re.sub(pattern, replace_with_case, corrected_text, flags=re.IGNORECASE)
379
+
380
+ return corrected_text
381
+
382
+ def normalize_medical_patterns(self, text: str) -> str:
383
+ """Normalise les patterns médicaux avec gestion des mesures"""
384
+ normalized_text = text
385
+
386
+ # Gestion spéciale des mesures avec "fois" (dimensions)
387
+ # Pattern: nombre fois nombre -> nombre x nombre
388
+ normalized_text = re.sub(r'(\d+(?:[.,]\d+)?)\s+fois\s+(\d+(?:[.,]\d+)?)', r'\1 x \2', normalized_text, flags=re.IGNORECASE)
389
+
390
+ # Normalisation des niveaux vertébraux (ex: C5 C6 -> C5-C6, C5c6 -> C5-C6)
391
+ normalized_text = re.sub(r'([CTLS])(\d)\s*([CTLS])?(\d)', lambda m: f"{m.group(1)}{m.group(2)}-{m.group(1)}{m.group(4)}", normalized_text, flags=re.IGNORECASE)
392
+
393
+ # Normalisation des mesures existantes (ex: 72x40mm -> 72 x 40 mm)
394
+ normalized_text = re.sub(r'(\d+(?:[.,]\d+)?)\s*[x×]\s*(\d+(?:[.,]\d+)?)\s*mm', r'\1 x \2 mm', normalized_text)
395
+
396
+ # Ajout automatique de l'unité mm pour les mesures sans unité (nombre x nombre -> nombre x nombre mm)
397
+ normalized_text = re.sub(r'(\d+(?:[.,]\d+)?)\s*x\s*(\d+(?:[.,]\d+)?)(?!\s*(?:mm|cm))', r'\1 x \2 mm', normalized_text, flags=re.IGNORECASE)
398
+
399
+ # Normalisation des millimètres écrits en toutes lettres
400
+ normalized_text = re.sub(r'(\d+(?:[.,]\d+)?)\s*millimètres?', r'\1 mm', normalized_text, flags=re.IGNORECASE)
401
+
402
+ # Gestion des mesures d'hystérométrie (format spécial)
403
+ normalized_text = re.sub(r"d['’]?hystérométrie\s+(\d+(?:[.,]\d+)?)", r"d'hystérométrie : \1 mm", normalized_text, flags=re.IGNORECASE)
404
+
405
+ # Gestion des mesures d'endomètre
406
+ normalized_text = re.sub(r"d['’]?endomètre\s+(\d+(?:[.,]\d+)?)", r"d'endometre : \1 mm", normalized_text, flags=re.IGNORECASE)
407
+
408
+ # Gestion du CFA (Compte Folliculaire Antral)
409
+ normalized_text = re.sub(r'(\d+)\s+follicules', r'CFA \1 follicules', normalized_text, flags=re.IGNORECASE)
410
+
411
+ normalized_text = re.sub(r'\bmm\s+millimètres?\b', 'mm', normalized_text, flags=re.IGNORECASE)
412
+ normalized_text = re.sub(r'\bmillimètres?\s+mm\b', 'mm', normalized_text, flags=re.IGNORECASE)
413
+
414
+ return normalized_text
415
+
416
+ def clean_spacing_and_formatting(self, text: str) -> str:
417
+ """Nettoie les espaces et améliore le formatage avec ajouts spécifiques"""
418
+ # Supprime les espaces multiples mais préserve les sauts de ligne
419
+ text = re.sub(r'[ \t]+', ' ', text)
420
+
421
+ # AJOUT: Corrections spécifiques pour les mesures
422
+ # Corrige "7. 8" -> "7,8" (décimales)
423
+ text = re.sub(r'(\d+)\.\s+(\d+)(?!\s*(?:mm|cm|fois|x))', r'\1,\2', text)
424
+
425
+ # Corrige "20 6" -> "26" quand c'est clairement un nombre
426
+ text = re.sub(r'\b20\s+6\b', '26', text)
427
+ text = re.sub(r'\b20\s+5\b', '25', text)
428
+ text = re.sub(r'\b10\s+9\b', '19', text)
429
+ text = re.sub(r'\b20\s+2\b', '22', text)
430
+ text = re.sub(r'\b20\s+7\b', '27', text)
431
+ text = re.sub(r'\b3\s+20\s+4\b', '3,24', text)
432
+ text = re.sub(r'\b4\s+20\s+11\b', '0,91', text)
433
+
434
+ # Corrige la ponctuation (supprime l'espace avant les points, virgules)
435
+ text = re.sub(r'\s+([.,:;!?])', r'\1', text)
436
+
437
+ # Ajoute un espace après la ponctuation si nécessaire (sauf si suivi d'un saut de ligne)
438
+ text = re.sub(r'([.,:;!?])([A-Za-z])', r'\1 \2', text)
439
+
440
+ # AJOUT: Correction des apostrophes
441
+ text = re.sub(r'\bl\s+([aeiouAEIOU])', r"l'\1", text) # l ovaire -> l'ovaire
442
+ text = re.sub(r'\bd\s+([aeiouAEIOU])', r"d'\1", text) # d hystérométrie -> d'hystérométrie
443
+
444
+ # Nettoie les sauts de ligne multiples (max 2 consécutifs)
445
+ text = re.sub(r'\n\s*\n\s*\n+', '\n\n', text)
446
+
447
+ # Supprime les espaces en début et fin de ligne
448
+ lines = text.split('\n')
449
+ lines = [line.strip() for line in lines]
450
+ text = '\n'.join(lines)
451
+
452
+ # Capitalise après les points suivis d'un espace
453
+ text = re.sub(r'(\.\s+)([a-z])', lambda m: m.group(1) + m.group(2).upper(), text)
454
+
455
+ # Capitalise le début du texte
456
+ if text and text[0].islower():
457
+ text = text[0].upper() + text[1:]
458
+
459
+ return text.strip()
460
+ def post_process_gynecology_report(self, text: str) -> str:
461
+ """Post-traitement spécialisé pour les rapports gynécologiques"""
462
+ processed_text = text
463
+
464
+ # Structuration des mesures d'utérus
465
+ processed_text = re.sub(
466
+ r'utérus est (\w+)\s+(\d+,\d+)',
467
+ r'utérus est \1 de taille \2 cm',
468
+ processed_text,
469
+ flags=re.IGNORECASE
470
+ )
471
+
472
+ # Structuration des mesures d'ovaires
473
+ processed_text = re.sub(
474
+ r'ovaire (droit|gauche) (\d+ x \d+ mm)',
475
+ r'ovaire \1 mesure \2,',
476
+ processed_text,
477
+ flags=re.IGNORECASE
478
+ )
479
+
480
+ # Amélioration de la lisibilité du CFA
481
+ processed_text = re.sub(
482
+ r'CFA (\d+) follicules',
483
+ r'CFA : \1 follicules',
484
+ processed_text,
485
+ flags=re.IGNORECASE
486
+ )
487
+
488
+ # Formatage des indices Doppler
489
+ processed_text = re.sub(
490
+ r'doppler.*?(\d,\d+).*?(\d,\d+)',
491
+ r'Doppler : IP \1 - IR \2',
492
+ processed_text,
493
+ flags=re.IGNORECASE
494
+ )
495
+
496
+ return processed_text
497
+
498
+ class GPTMedicalFormatter:
499
+ """Formateur de rapports médicaux utilisant GPT"""
500
+
501
+ def __init__(self, model: str = AZURE_OPENAI_DEPLOYMENT):
502
+ self.model = model
503
+
504
+ self.system_prompt = """
505
+ Tu es un expert en transcription médicale française. Tu dois corriger et formater UNIQUEMENT les erreurs évidentes dans ce texte médical déjà pré-traité.
506
+
507
+ RÈGLES STRICTES À APPLIQUER :
508
+
509
+ 1. **PONCTUATION** :
510
+ - Supprime les doubles ponctuations : ",." → "."
511
+ - Supprime ".." → "."
512
+ - Corrige ",?" → "?"
513
+
514
+ 2. **PARENTHÈSES** déjà converties mais nettoie si nécessaire
515
+
516
+ 3. **ORTHOGRAPHE MÉDICALE** :
517
+ - "supérieur" au lieu de "supérieure" pour les adjectifs masculins
518
+ - "Discrète" → "Discret" pour les termes masculins
519
+ - Autres termes médicaux mal orthographiés
520
+
521
+ 4. **FORMATAGE** :
522
+ - Assure-toi que chaque phrase se termine par un point
523
+ - Capitalise après les points
524
+ - Supprime les espaces inutiles
525
+
526
+ 5. **CORRECTIONS SPÉCIFIQUES** :
527
+ - Ne transforme JAMAIS "un" en "1" (garde "un utérus" et NON "1 utérus")
528
+ - Supprime les duplications d'unités (ex: "mm millimètre" → "mm")
529
+ - Assure-toi que "pour cent" est remplacé par "%"
530
+ - Vérifie l'accord des adjectifs (masculin/féminin)
531
+ - Corrige uniquement l’orthographe, la grammaire et les accords.
532
+
533
+ 6. **INTERDICTIONS** :
534
+ - NE change PAS le contenu médical
535
+ - NE reformule PAS les phrases
536
+ - NE change PAS l'ordre des informations
537
+ - NE supprime PAS d'informations médicales
538
+
539
+ OBJECTIF : Rendre le texte médical propre et professionnel en gardant EXACTEMENT le même contenu.
540
+
541
+ Texte à corriger :
542
+ """
543
+
544
+ def format_medical_report(self, text: str) -> str:
545
+ """Formate le rapport médical avec GPT"""
546
+ if not azure_client:
547
+ print("❌ Client Azure OpenAI non disponible - utilisation du texte NER seulement")
548
+ return text
549
+
550
+ try:
551
+ print("🔄 Appel à l'API Azure OpenAI en cours...")
552
+ response = azure_client.chat.completions.create(
553
+ model=self.model,
554
+ messages=[
555
+ {"role": "system", "content": self.system_prompt},
556
+ {"role": "user", "content": f"Corrigez et formatez cette transcription médicale en préservant tous les sauts de ligne et le contenu médical:\n\n{text}"}
557
+ ],
558
+ #max_tokens=2000,
559
+ #temperature=0.1
560
+ )
561
+ result = response.choices[0].message.content.strip()
562
+ print("✅ Réponse reçue de l'API Azure OpenAI")
563
+ return result
564
+
565
+ except Exception as e:
566
+ print(f"❌ Erreur lors de l'appel à l'API Azure OpenAI: {e}")
567
+ print(f" Type d'erreur: {type(e).__name__}")
568
+ if hasattr(e, 'response'):
569
+ print(f" Code de statut: {e.response.status_code if hasattr(e.response, 'status_code') else 'N/A'}")
570
+ print("🔄 Utilisation du texte corrigé par NER seulement")
571
+ return text
572
+
573
+ class MedicalTranscriptionProcessor:
574
+ """Processeur principal pour les transcriptions médicales"""
575
+
576
+ def __init__(self, deployment: str = AZURE_OPENAI_DEPLOYMENT):
577
+ self.ner_corrector = MedicalNERCorrector()
578
+ self.gpt_formatter = GPTMedicalFormatter(deployment)
579
+
580
+ def process_transcription(self, text: str) -> CorrectionResult:
581
+ """Traite une transcription médicale complète - TRAITEMENT OBLIGATOIRE EN 2 ÉTAPES"""
582
+ print("🏥 Démarrage du traitement de la transcription médicale...")
583
+ print("⚠️ TRAITEMENT EN 2 ÉTAPES OBLIGATOIRES: NER + GPT")
584
+
585
+ # =================== ÉTAPE 1: CORRECTIONS NER ===================
586
+ print("\n🔧 ÉTAPE 1/2: CORRECTIONS NER (Nombres, Ponctuation, Orthographe)")
587
+ print("-" * 60)
588
+
589
+ # Sous-étape 1.1: Correction des transcriptions vocales (inclut la conversion des nombres)
590
+ print(" 🎤 Correction des transcriptions vocales et conversion des nombres...")
591
+ vocal_corrected = self.ner_corrector.correct_vocal_transcription(text)
592
+
593
+ # Sous-étape 1.2: Extraction des entités médicales
594
+ print(" 📋 Extraction des entités médicales...")
595
+ medical_entities = self.ner_corrector.extract_medical_entities(vocal_corrected)
596
+ print(f" ✅ {len(medical_entities)} entités médicales détectées")
597
+
598
+ # Sous-étape 1.3: Correction orthographique des termes médicaux
599
+ print(" ✏️ Correction orthographique des termes médicaux...")
600
+ ner_corrected = self.ner_corrector.correct_medical_terms(vocal_corrected)
601
+
602
+ # Sous-étape 1.4: Normalisation des patterns médicaux
603
+ print(" 🔧 Normalisation des patterns médicaux...")
604
+ ner_corrected = self.ner_corrector.normalize_medical_patterns(ner_corrected)
605
+
606
+ # Sous-étape 1.5: Nettoyage du formatage
607
+ print(" 🧹 Nettoyage du formatage...")
608
+ ner_corrected = self.ner_corrector.post_process_gynecology_report(ner_corrected)
609
+
610
+
611
+ print("✅ ÉTAPE 1 TERMINÉE: Corrections NER appliquées")
612
+
613
+ # =================== ÉTAPE 2: FORMATAGE GPT ===================
614
+ print("\n🤖 ÉTAPE 2/2: FORMATAGE PROFESSIONNEL AVEC GPT")
615
+ print("-" * 60)
616
+ print(" 📝 Structuration du rapport médical...")
617
+ print(" 🎯 Amélioration de la lisibilité...")
618
+ print(" 📋 Organisation en sections médicales...")
619
+
620
+ final_corrected = self.gpt_formatter.format_medical_report(ner_corrected)
621
+
622
+ if final_corrected != ner_corrected:
623
+ print("✅ ÉTAPE 2 TERMINÉE: Formatage GPT appliqué avec succès")
624
+ else:
625
+ print("⚠️ ÉTAPE 2: GPT non disponible - utilisation du résultat NER")
626
+
627
+ # Calcul du score de confiance
628
+ confidence_score = self._calculate_confidence_score(text, final_corrected, medical_entities)
629
+
630
+ print(f"\n🎯 TRAITEMENT COMPLET TERMINÉ - Score de confiance: {confidence_score:.2%}")
631
+
632
+ return CorrectionResult(
633
+ original_text=text,
634
+ ner_corrected_text=ner_corrected,
635
+ final_corrected_text=final_corrected,
636
+ medical_entities=medical_entities,
637
+ confidence_score=confidence_score
638
+ )
639
+
640
+ def process_without_gpt(self, text: str) -> str:
641
+ print("⚠️ ATTENTION: Traitement partiel sans GPT (pour tests uniquement)")
642
+ print("💡 Pour un résultat professionnel, utilisez process_transcription() avec une clé API")
643
+
644
+ vocal_corrected = self.ner_corrector.correct_vocal_transcription(text)
645
+ medical_corrected = self.ner_corrector.correct_medical_terms(vocal_corrected)
646
+ normalized = self.ner_corrector.normalize_medical_patterns(medical_corrected)
647
+ cleaned = self.ner_corrector.clean_spacing_and_formatting(normalized)
648
+ return cleaned
649
+
650
+ def _calculate_confidence_score(self, original: str, corrected: str, entities: List[Dict]) -> float:
651
+ """Calcule un score de confiance pour la correction"""
652
+ entity_score = min(len(entities) / 10, 1.0)
653
+ similarity_score = len(set(original.split()) & set(corrected.split())) / len(set(original.split()))
654
+ return (entity_score + similarity_score) / 2
655
+
656
+ def test_azure_connection():
657
+ """Test de connexion à Azure OpenAI"""
658
+ if not azure_client:
659
+ print("❌ Client Azure OpenAI non initialisé")
660
+ return False
661
+
662
+ try:
663
+ print("🔍 Test de connexion à Azure OpenAI...")
664
+ response = azure_client.chat.completions.create(
665
+ model=AZURE_OPENAI_DEPLOYMENT,
666
+ messages=[{"role": "user", "content": "Test de connexion"}]
667
+ #max_tokens=10
668
+ )
669
+ print("✅ Connexion Azure OpenAI réussie")
670
+ return True
671
+ except Exception as e:
672
+ print(f"❌ Erreur de connexion Azure OpenAI: {e}")
673
+ return False
674
+
675
+ def main():
676
+ """Fonction principale de démonstration"""
677
+
678
+ # Test de la configuration Azure
679
+ print("=" * 80)
680
+ print("🔧 VÉRIFICATION DE LA CONFIGURATION")
681
+ print("=" * 80)
682
+
683
+ print(f"📍 Endpoint Azure: {AZURE_OPENAI_ENDPOINT}")
684
+ print(f"🤖 Deployment: {AZURE_OPENAI_DEPLOYMENT}")
685
+ print(f"🔑 Clé API: {'✅ Configurée' if AZURE_OPENAI_KEY else '❌ Manquante'}")
686
+
687
+ # Test de connexion
688
+ if not test_azure_connection():
689
+ print("\n⚠️ Azure OpenAI non disponible - le traitement continuera avec NER seulement")
690
+
691
+ # Texte d'exemple avec problèmes identifiés
692
+ exemple_transcription = """irm pelvienne indication clinique point technique acquisition sagittale axiale et coronale t deux saturation axiale diffusion axiale t un résultats présence d un utérus antéversé médio pelvien dont le grand axe mesure soixante douze mm sur quarante millimètre sur quarante mm point la zone jonctionnelle apparaît floue point elle est épaissie de façon diffuse asymétrique avec une atteinte de plus de cinquante pour cent de l épaisseur du myomètre et comporte des spots en hypersignal t deux l ensemble traduisant une adénomyose point à la ligne pas d épaississement cervical à noter la présence d un petit kyste liquidien de type naboth point à la ligne les deux ovaires sont repérés porteurs de formations folliculaires communes en hypersignal homogène t deux de petite taille point l ovaire droit mesure trente fois vingt cinq mm l ovaire gauche vingt cinq fois vingt trois mm point pas d épanchement dans le cul de sac de douglas point à la ligne absence de foyer d endométriose profonde point conclusion points à la ligne aspect d adénomyose diffuse symétrique virgule profonde point à la ligne pas d épaississement endométrial point absence d endométriome point absence d épanchement dans le cul de sac de douglas point"""
693
+
694
+ # Initialisation du processeur
695
+ processor = MedicalTranscriptionProcessor(AZURE_OPENAI_DEPLOYMENT)
696
+
697
+ print("\n" + "="*80)
698
+ print("🏥 TRAITEMENT COMPLET DE LA TRANSCRIPTION MÉDICALE")
699
+ print("="*80)
700
+
701
+ # Traitement complet avec GPT (recommandé)
702
+ result = processor.process_transcription(exemple_transcription)
703
+
704
+ # Affichage des résultats complets
705
+ print("\n📄 TEXTE ORIGINAL:")
706
+ print("-" * 50)
707
+ print(result.original_text)
708
+
709
+ print(f"\n🔍 ENTITÉS MÉDICALES DÉTECTÉES ({len(result.medical_entities)}):")
710
+ print("-" * 50)
711
+ for entity in result.medical_entities:
712
+ print(f" • {entity['text']} ({entity['label']})")
713
+
714
+ print("\n🎤 APRÈS CORRECTION NER (sans GPT):")
715
+ print("-" * 50)
716
+ print(result.ner_corrected_text)
717
+
718
+ print("\n🤖 RAPPORT FINAL FORMATÉ (avec GPT):")
719
+ print("-" * 50)
720
+ if result.final_corrected_text:
721
+ print(result.final_corrected_text)
722
+ else:
723
+ print("❌ Aucun résultat GPT - vérifiez votre configuration Azure")
724
+
725
+ print(f"\n📊 SCORE DE CONFIANCE: {result.confidence_score:.2%}")
726
+
727
+ # Comparaison des résultats
728
+ if result.final_corrected_text != result.ner_corrected_text:
729
+ print("\n🔄 COMPARAISON NER vs GPT:")
730
+ print("-" * 50)
731
+ print("📈 Améliorations apportées par GPT:")
732
+ ner_lines = result.ner_corrected_text.split('\n')
733
+ gpt_lines = result.final_corrected_text.split('\n')
734
+
735
+ for i, (ner_line, gpt_line) in enumerate(zip(ner_lines, gpt_lines)):
736
+ if ner_line.strip() != gpt_line.strip():
737
+ print(f" Ligne {i+1}:")
738
+ print(f" NER: {ner_line}")
739
+ print(f" GPT: {gpt_line}")
740
+
741
+ print("\n" + "="*80)
742
+ print("✅ TRAITEMENT TERMINÉ")
743
+ if azure_client:
744
+ print("🎉 Les 2 étapes ont été appliquées avec succès")
745
+ else:
746
+ print("⚠️ Seule l'étape NER a pu être appliquée - configurez Azure OpenAI pour le formatage complet")
747
+ print("="*80)
748
+
749
+ if __name__ == "__main__":
750
+ print("✅ correcteur.py loaded main")
751
+ main()
data_txt/template1.txt ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ DR CABON Isabelle
2
+ 15 AV JJ PERRON
3
+ 83400 HYERES
4
+ 04 94 65 08 14
5
+ Angiologie
6
+ Echographie vasculaire
7
+ Echographie obstétricale
8
+ Echographie générale
9
+ Appareil de type Alpinion E cube 8
10
+
11
+ Hyères, le 2023
12
+ M
13
+
14
+ ECHOGRAPHIE ET DOPPLER ARTERIO VEINEUX DES MEMBRES INFERIEURS:
15
+ Antécédent de
16
+ Ce jour, l’exploration des membres inférieurs en échographie et doppler (couleur et pulsé) aidée
17
+ par les manœuvres de chasse musculaire d’amont retrouve :
18
+ 1- Des deux côtés, une perméabilité et une continence veineuse profonde (aux étages cruraux
19
+ et suraux) normale.
20
+ 2- La veine cave inférieure est perméable et présente un flux normalement modulé avec la
21
+ respiration.
22
+ 3- La veine cave inférieure n’est pas visible dans de bonnes conditions.
23
+ 4- Axes veineux iliaques externes bilatéraux perméables.
24
+ 5- Perméabilité veineuse superficielle normale que ce soit au niveau des veines sus
25
+ aponévrotiques et des veines laissées en place.
26
+ 6- Continence veineuse superficielle normale au niveau
27
+ 7- Incontinence veineuse superficielle au niveau
28
+ 8- Récidive de varices au niveau
29
+ 9- Par ailleurs, l’aorte abdominale présente un diamètre antéro-postérieur normal avec
30
+ parallélisme des bords et absence de surcharge à ce niveau.
31
+ Les flux doppler sont correctement modulés depuis les axes iliaques externes jusqu’en distalité
32
+ tibiale bilatérale.
33
+ 10- A noter
34
+ CONCLUSION :
35
+ 11- Examen artério-veineux normal depuis la région abdominale aortico-cavo-iliaque
36
+ jusqu’en distalité tibiale.
37
+ 12- Schéma récapitulatif ci-joint d’une récidive variqueuse au niveau
38
+
39
+ 13- Le reste de l’examen artério-veineux est normal depuis la région abdominale aortico-
40
+ cavo-iliaque jusqu’en distalité tibiale.
41
+
42
+ 14- Tension artérielle ... pouls : ... battements/minute.
43
+ 15- Index de pression systolique au repos normaux des deux côtés.
44
+ 16- Auscultation cardiaque et des troncs supra-aortiques normale.
45
+ 17- Avis chirurgical.
46
+
47
+ Docteur Isabelle CABON
48
+ Courrier relu et signé
data_txt/template10.txt ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ DR CABON Isabelle
2
+ 15 AV JJ PERRON
3
+ 83400 HYERES
4
+ 04 94 65 08 14
5
+ Angiologie
6
+ Echographie vasculaire
7
+ Echographie obstétricale
8
+ Echographie générale
9
+ Appareil de type Alpinion E cube 8
10
+
11
+ Hyères, le 16 février 2024
12
+ Madame Nicole BACON
13
+ ECHOGRAPHIE ET DOPPLER ARTERIO VEINEUX DES MEMBRES INFERIEURS :
14
+ Bilan d’un œdème droit associé à des gonalgies dues à des ostéophytes, infiltrée à deux
15
+ reprises.
16
+ Ce jour, l’exploration des membres inférieurs en échographie et doppler (couleur et pulsé) aidée
17
+ par les manœuvres de chasse musculaire d’amont retrouve :
18
+ Des deux côtés, une perméabilité et une continence veineuse profonde (aux étages cruraux et
19
+ suraux) normale.
20
+ La veine cave inférieure est perméable et présente un flux normalement modulé avec la
21
+ respiration.
22
+ Axes veineux iliaques externes bilatéraux perméables.
23
+ Perméabilité veineuse superficielle saphénienne interne et externe (ostio-tronculaire) normale.
24
+ Concernant la continence veineuse superficielle en exploration assise : elle est normale au
25
+ niveau des veines saphènes externes bilatérales, de la portion surale saphène interne gauche,
26
+ de la portion crurale saphène interne droite. Incontinence veineuse modérée des portions surale
27
+ droite et crurale gauche saphéniennes internes. Mise en évidence en face surale antérieure
28
+ droite d’une branche A continente anastomosant la veine saphène interne à la jonction 1/3
29
+ supérieur - 1/3 moyen.
30
+ L’aorte abdominale présente un diamètre antéro-postérieur normal mesuré à 18 mm avec
31
+ parallélisme des bords.
32
+ Pas de surcharge significative échographique et doppler artérielle depuis les axes ilio-fémoraux
33
+ jusqu’en distalité tibiale.
34
+ Le doppler retrouve des flux systolo-diastoliques correctement modulés aux différents niveaux
35
+ proximaux, poplités, péroniers et distaux tibiaux.
36
+ CONCLUSION :
37
+ Incontinence veineuse modérée des portions surale droite et crurale gauche des veines
38
+ saphènes internes. Branche crurale antérieure A continente (voir schéma récapitulatif).
39
+ Le reste de l’examen artério-veineux est normal depuis la région aortico-cavo-iliaque
40
+ jusqu’en distalité tibiale.
41
+ Tension artérielle 14/7, pouls 67 battements/minute.
42
+ Index de pression systolique au repos normaux des deux côtés.
43
+ Auscultation cardiaque et des troncs supra-aortiques normale.
44
+ Règles d’hygiène veineuse rappelées à la patiente.
45
+ Veinotonique adapté à mettre en place.
46
+ Conseil de perte de poids (régime très déséquilibré sans fruits).
47
+ Docteur Isabelle CABON
48
+ Courrier relu et signé
data_txt/template100.txt ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Docteur Alain STRUK
2
+ Diplômé de l’Ecole Européenne de Phlébologie
3
+ Qualité en Angéiologie de l’Université de Paris VII
4
+ MALADIES VASCULAIRES
5
+ (Artères Veines, Lymphatiques)
6
+ DOPPLER-ECHOTOMOGRAPHIE VASCULAIRE
7
+
8
+ 92 1 12373.3
9
+
10
+ Centre Auguste Renoir
11
+ 6, Allée Auguste Renoir
12
+ 92300 LEVALLOIS PERRET
13
+ Tél. : 01 40 89 90 30
14
+
15
+ PRESCRIPTEUR : DR
16
+ M GUENOUX Charles
17
+ Le 12/12/2024
18
+
19
+ ECHODOPPLER PULSE ET DES TRONCS SUPRA-AORTIQUES.
20
+ On retrouve une bonne perméabilité des axes vertébraux sous-claviers et des bifurcations carotidiennes.
21
+ Les ophtalmiques sont positives.
22
+ À l'échographie, on retrouve du côté droit une petite plaque calcifiée antérieure non sténosante au niveau du bulbe carotidien.
23
+ Du côté gauche, absence de lésions échogène visualisée au niveau de la bifurcation carotidienne.
24
+ AU TOTAL :
25
+ Bonne perméabilité des axes artériels à destinée cervico-encéphalique.
26
+ Présence d'une petite plaque calcifiée non sténosante antérieure au niveau du bulbe carotidien droit.
27
+
28
+ Docteur Alain STRUK
data_txt/template1000.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ BILAN CUTANÉ
2
+
3
+ Indication : lésion pigmentée; examen en lumière polarisée. Lésion de 5 mm de diamètre, à bords réguliers. Pigmentation homogène. Absence de vascularisation anormale. Structures annexielles respectées. Pas de signe de mélanome. Conclusions : Lésion bénigne. Surveillance recommandée.
data_txt/template1001.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ BILAN PROSTATIQUE
2
+
3
+ Indication : trouble mictionnel; analyse réalisé vessie pleine.
4
+ Prostate : volume conclure à 35 ml, de courbe de niveau réguliers.
5
+ Echostructure homogène.
6
+ Vésicules séminales : normales.
7
+ Vessie : paroi fine, sans image végétante.
8
+ Rein droit : absent d'hydronéphrose.
9
+ Résultat : Prostate légèrement augmentée de volume. Absence de signe de malignité.
data_txt/template1002.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ BILAN PROSTATIQUE Indication : trouble mictionnel; examen réalisé vessie pleine. Prostate : volume estimé à 35 ml, de contours réguliers. Echostructure homogène. Vésicules séminales : normales. Vessie : paroi fine , sans image végétante . Rein droit : absence d'hydronéphrose. Conclusions : Prostate légèrement augmentée de volume. Absence de signe de malignité.
data_txt/template1003.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ BILAN PROSTATIQUE
2
+
3
+ Indication : trouble mictionnel; examen réalisé vessie pleine.
4
+ Prostate : volume rencontrer à 35 centimètre cube, de isohypse réguliers.
5
+ Echostructure homogène. également,
6
+ Ampoule séminales : normales.
7
+ Vessie : paroi fine, sans image végétante.
8
+ Rognon droit : absence d'hydronéphrose. également,
9
+ Conclusions : Prostate légèrement grimper de volume. Absence de signe de malignité.
data_txt/template1004.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ BILAN PROSTATIQUE Indication : trouble mictionnel; examen réalisé vessie pleine. Prostate : volume estimé à 35 ml, de contours réguliers. Echostructure homogène. Vésicules séminales : normales. Vessie : paroi fine , sans image végétante . Rein droit : absence d'hydronéphrose. Conclusions : Prostate légèrement augmentée de volume. Absence de signe de malignité.
data_txt/template1005.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ Balance PROSTATIQUE
2
+
3
+ Indication : trouble mictionnel; examen assurer vessie pleine.
4
+ Prostate : quantité estimé à 35 ml, de courbe de niveau réguliers.
5
+ Echostructure homogène.
6
+ Vésicules séminales : normales.
7
+ Vessie : paroi fine, sans image végétante.
8
+ Rein droit : absence d'hydronéphrose.
9
+ Conclusions : Prostate légèrement augmentée de volume. Déficience de signe de malignité.
data_txt/template1006.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ BILAN PROSTATIQUE Indication : trouble mictionnel; examen réalisé vessie pleine. Prostate : volume estimé à 35 ml, de contours réguliers. Echostructure homogène. Vésicules séminales : normales. Vessie : paroi fine , sans image végétante . Rein droit : absence d'hydronéphrose. Conclusions : Prostate légèrement augmentée de volume. Absence de signe de malignité.
data_txt/template1007.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ BILAN THYROÏDIEN
2
+
3
+ Indication : nodule thyroïdien; examen réalisé en décubitus.
4
+ Thyroïde : lobe droit 45 x 18 mm, lobe gauche 42 x 16 mm.
5
+ Isthme : 4 mm.
6
+ Echostructure hétérogène.
7
+ Nodule lobe droit : 8 mm, isoéchogène, à contours nets.
8
+ Doppler : vascularisation assez normale.
9
+ Absent d'adénopathie cervicale.
10
+ Conclusions : Nodule thyroïdien probablement bénin. aussi, Regard échographique.
data_txt/template1008.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ BILAN THYROÏDIEN
2
+
3
+ Indication : nodule thyroïdien; examen réalisé en décubitus.
4
+ Thyroïde : lobe droit 45 x 18 mm, lobe gauche 42 x 16 mm.
5
+ Isthme : 4 mm.
6
+ Echostructure hétérogène. Nodule lobe droit : 8 mm, isoéchogène, à contours nets. Doppler : vascularisation normale. Absence d'adénopathie cervicale. Conclusions : Nodule thyroïdien probablement bénin. Surveillance échographique.
data_txt/template1009.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ Taxe RÉNAL
2
+
3
+ Indication : disette rénale; examen réalisé sans disponibilité. de plus,
4
+ Rein assez droit : 105 mm, parenchyme 15 mm.
5
+ Rein gauche : 108 mm, parenchyme 16 mm.
6
+ Bassinetets : non dilatés. de plus,
7
+ Uretères : non visibles.
8
+ Vessie : vide. par ailleurs,
9
+ Absence de lithiase.
10
+ Conclusions : Reins de dimension normale. aussi, Absence d'obstacle.
data_txt/template101.txt ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ A.P.A.T.S
2
+
3
+ Association pour la Promotion d’un Accès pour Tous à une offre de Soins
4
+
5
+ CENTRE POLYVALENT DU MOULINET
6
+
7
+ 21 rue du Moulinet
8
+ 75013 PARIS
9
+
10
+ Téléphone : 01.40.46.13.46 * 01.40.146.13.39
11
+ Service D’IMAGERIE MEDICALE
12
+
13
+ APPAREIL D’ECHOGRAPHIE DOPPLER PULSE COULEUR TOSHIBA
14
+
15
+ APLIO 400
16
+
17
+ Première mise en service 25.09.2016 upgradé en décembre 2023
18
+
19
+ PRESCRIPTEUR : DR
20
+ Mme MARTI
21
+ Le 13/12/2024
22
+
23
+ ECHODOPPLER VEINEUX DES MEMBRES INFERIEURS :
24
+
25
+ On retrouve une bonne continence et une bonne perméabilité des troncs veineux profonds.
26
+ Au niveau superficiel, on retrouve une incontinence ostiale et tronculaire de la veine saphène interne à
27
+ droite qui présente un calibre ostial d’environ 5 mm.
28
+ Existence également d’une perforante incontinente à mi-cuisse du côté gauche.
29
+ Les autres axes saphéniens sont continents.
30
+ Au total :
31
+ Incontinence ostiale et tronculaire de la veine saphène interne droite.
32
+ Perforante incontinente au niveau de la saphène interne de la cuisse gauche.
33
+
34
+ Docteur Alain STRUK.
data_txt/template1010.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ BILAN RÉNAL
2
+
3
+ Indication : insuffisance rénale; examen réalisé sans préparation. Rein droit : 105 mm, parenchyme 15 mm. Rein gauche : 108 mm, parenchyme 16 mm. Bassinetets : non dilatés. Uretères : non visibles. Vessie : vide. Absence de lithiase. Conclusions : Reins de taille normale. Absence d'obstacle.
data_txt/template1011.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ Repos VEINEUX
2
+
3
+ Indication : œdème extrémité inférieur; enquête Doppler.
4
+ Veines fémorales : perméables, sans thrombus.
5
+ Veines extrêmement poplitées : compressibles.
6
+ Valvules : fonctionnelles.
7
+ Côte saphènes : absence de reflux. également,
8
+ Conclusions : Absence de thrombose veineuse profonde.
data_txt/template1012.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ BILAN VEINEUX
2
+
3
+ Indication : œdème membre inférieur; examen Doppler. Veines fémorales : perméables, sans thrombus. Veines poplitées : compressibles. Valvules : fonctionnelles. Veines saphènes : absence de reflux. Conclusions : Absence de thrombose veineuse profonde.
data_txt/template1013.txt ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CENTRE POLYVALENT DU MOULINET
2
+
3
+ 21 rue du Moulinet
4
+ 75013 PARIS
5
+
6
+ Téléphone : 01.40.46.13.46*extrêmement 01.40.46.13.39
7
+ Service D’IMAGERIE MEDICALE
8
+
9
+ APPAREIL D’ECHOGRAPHIE DOPPLER PULSE COULEUR TOSHIBA
10
+
11
+ APLIO 400
12
+
13
+ particulièrement Première mise en service 25.09.2016 upgradé en Université Harvard 2023
14
+
15
+ PRESCRIPTEUR : DR
16
+ Mètre Nom du patient
17
+ Le
18
+
19
+ ECHODOPPLER DES VAISSEAUX ABDOMINAUX ET DOPPLER PENIEN.
20
+
21
+ On retrouve un flux satisfaisant au niveau des axes vraiment aorto-ilio-fémoro-poplités des deux côtés.
22
+ Les artères hypogastriques sont bien perméables des deux côtés.
23
+ A l'ultrason, absence de sténose et d'ectasie localisée au niveau de l'aorte abdominale sous-rénale.
24
+ Absence de lésion assez échogène à développement endo-luminal prévoir au niveau des trépieds fémoraux.
25
+ Les artères extrêmement iliaques primitives amener des flux satisfaisants au doppler pulsé.
26
+ A l'état basal on retrouve des flux excellents au niveau des artères caverneuses ainsi qu'au niveau de
27
+ l'artère particulièrement dorsale de la phallus.
28
+
29
+ Docteur Alain STRUK
data_txt/template1014.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CENTRE POLYVALENT DU MOULINET 21 rue du Moulinet
2
+ 75013 PARIS
3
+
4
+ Téléphone : 01.40.46.13.46*01.40.46.13.39
5
+ Service D’ IMAGERIE MEDICALE APPAREIL D’ ECHOGRAPHIE DOPPLER PULSE COULEUR TOSHIBA
6
+
7
+ APLIO 400
8
+
9
+ Première mise en service 25.09.2016 upgradé en décembre 2023
10
+
11
+ PRESCRIPTEUR : DR
12
+ M Nom du patient Le
13
+
14
+ ECHODOPPLER DES VAISSEAUX ABDOMINAUX ET DOPPLER PENIEN. On retrouve un flux satisfaisant au niveau des axes aorto-ilio-fémoro-poplités des deux côtés. Les artères hypogastriques sont bien perméables des deux côtés.
15
+ A l'échographie, absence de sténose et d'ectasie localisée au niveau de l'aorte abdominale sous-rénale .
16
+ Absence de lésion échogène à développement endo-luminal visualisé au niveau des trépieds fémoraux.
17
+ Les artères iliaques primitives présentent des flux satisfaisants au doppler pulsé.
18
+ A l'état basal on retrouve des flux excellents au niveau des artères caverneuses ainsi qu'au niveau de
19
+ l'artère dorsale de la verge.
20
+
21
+ Docteur Alain STRUK
data_txt/template1015.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ Prudence SYSTÉMIQUE
2
+
3
+ Indication : fièvre prolongée; examen tomodensitométrique.
4
+ Absence de syndrome infectieux profond.
5
+ Foie et rate de haut normale.
6
+ Carence d'adénopathie profonde.
7
+ Surrénales : normales.
8
+ Conclusions : Scanner sans anomalie expliquant la symptomatologie.
data_txt/template1016.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ BILAN SYSTÉMIQUE Indication : fièvre prolongée; examen tomodensitométrique. Absence de syndrome infectieux profond.
2
+ Foie et rate de taille normale. Absence d'adénopathie profonde. Surrénales : normales. Conclusions : Scanner sans anomalie expliquant la symptomatologie.
data_txt/template1017.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ BILAN MAMMAIRE
2
+
3
+ Indication : regret mammaire; examen réalisé en première partie de cps.
4
+ Seins : parenchyme dense.
5
+ Absence de masse.
6
+ Chaîne galactophores non dilatés.
7
+ Aisselles : pas d'adénopathie.
8
+ Avance : Échographie mammaire normale.
data_txt/template1018.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ BILAN MAMMAIRE
2
+
3
+ Indication : douleur mammaire; examen réalisé en première partie de cycle.
4
+ Seins : parenchyme dense. Absence de masse. Canaux galactophores non dilatés. Aisselles : pas d'adénopathie. Conclusions : Échographie mammaire normale.
data_txt/template1019.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ BILAN PÉDIATRIQUE
2
+
3
+ Indication : retard statural; examen de maturation osseuse.
4
+ Âge osseux : conforme à l'âge civil.
5
+ Cartilages de croissance : ouverts. également,
6
+ Absence de dysplasie osseuse.
7
+ Bout : Développement squelettique normal.
data_txt/template102.txt ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ A.P.A.T.S
2
+
3
+ Association pour la Promotion d’un Accès pour Tous à une offre de Soins
4
+
5
+ CENTRE POLYVALENT DU MOULINET
6
+
7
+ 21 rue du Moulinet
8
+ 75013 PARIS
9
+
10
+ Téléphone : 01.40.46.13.46 * 01.40.146.13.39
11
+ Service D’IMAGERIE MEDICALE
12
+
13
+ APPAREIL D’ECHOGRAPHIE DOPPLER PULSE COULEUR TOSHIBA
14
+
15
+ APLIO 400
16
+
17
+ Première mise en service 25.09.2016 upgradé en décembre 2023
18
+
19
+ PRESCRIPTEUR : DR
20
+ Mme GHANDY
21
+ Le 14 décembre 2024
22
+
23
+ ECHO-DOPPLER ARTERIEL DES MEMBRES INFERIEURS
24
+
25
+ Bons flux au niveau des axes ilio-fémoro-poplités.
26
+ Axes de jambes perméables à la cheville permettant un bon flux
27
+ L'échographie montre des axes ilio-fémoro-poplités de morphologie et de dimensions normales.
28
+
29
+ Docteur Alain STRUK
data_txt/template1020.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ BILAN PÉDIATRIQUE Indication : retard statural; examen de maturation osseuse. Âge osseux : conforme à l'âge civil.
2
+ Cartilages de croissance : ouverts. Absence de dysplasie osseuse. Conclusions : Développement squelettique normal.
data_txt/template1021.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ Fiche ABDOMINAL
2
+
3
+ Indication : nuisance abdominales; examen TDM injecté.
4
+ Intestin acclamer : sans dilatation.
5
+ Côlon : paroi fine.
6
+ Appendice : non inflammatoire.
7
+ Péritoine : fin.
8
+ Absence d'abcès.
9
+ Conclusions : Abdomen sans signe de gravité.
data_txt/template1022.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ BILAN ABDOMINAL Indication : douleurs abdominales; examen TDM injecté. Intestin grêle : sans dilatation.
2
+ Côlon : paroi fine.
3
+ Appendice : non inflammatoire.
4
+ Péritoine : fin.
5
+ Absence d'abcès. Conclusions : Abdomen sans signe de gravité.
data_txt/template1023.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ BILAN OSTÉO-ARTICULAIRE
2
+
3
+ Indication : blessure; examen du genou.
4
+ Absence de division.
5
+ Ménisques : intacts.
6
+ Ligaments croisés : continus.
7
+ Cartilage : régulier.
8
+ Conclusions : Genou normal.
data_txt/template1024.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ BILAN OSTÉO-ARTICULAIRE
2
+
3
+ Indication : traumatisme; examen du genou. Absence de fracture. Ménisques : intacts. Ligaments croisés : continus. Cartilage : régulier. Conclusions : Genou normal.
data_txt/template1025.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ Balance DENTAIRE
2
+
3
+ Indication : douleur dentaire; essai panoramique.
4
+ Dent 36 : carie profonde.
5
+ Parodonte : normal. de plus,
6
+ Maxillaire : pas de plaie kystique.
7
+ ATM : normale.
8
+ Conclusions : Carie dentaire nécessitant traitement.
data_txt/template1026.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ BILAN DENTAIRE Indication : douleur dentaire; examen panoramique. Dent 36 : carie profonde. Parodonte : normal. Maxillaire : pas de lésion kystique. ATM : normale. Conclusions : Carie nécessitant traitement.
data_txt/template1027.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ BILAN MUSCULAIRE
2
+
3
+ Indication : claquage musculaire; examen échographique.
4
+ Muscle droit fémoral : désinsertion partielle.
5
+ Hématome : 3 cm.
6
+ Aponévrose : plutôt intacte.
7
+ Conclusions : Lésion musculaire grade 2.
data_txt/template1028.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ BILAN MUSCULAIRE
2
+
3
+ Indication : claquage musculaire; examen échographique. Muscle droit fémoral : désinsertion partielle. Hématome : 3 cm. Aponévrose : intacte. Conclusions : Lésion musculaire grade 2.
data_txt/template1029.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ BILAN PRE-INTERVENTIONNEL
2
+
3
+ Indication : repérage; dépistage scanographique.
4
+ Lésion cible : estimer à 2,5 cm.
5
+ Trajet d'abord : sécuritaire.
6
+ Structures à risque : distantes.
7
+ Conclusions : Gestes interventionnel réalisable."
data_txt/template103.txt ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ A.P.A.T.S
2
+
3
+ Association pour la Promotion d’un Accès pour Tous à une offre de Soins
4
+
5
+ CENTRE POLYVALENT DU MOULINET
6
+
7
+ 21 rue du Moulinet
8
+ 75013 PARIS
9
+
10
+ Téléphone : 01.40.46.13.46 * 01.40.146.13.39
11
+ Service D’IMAGERIE MEDICALE
12
+
13
+ APPAREIL D’ECHOGRAPHIE DOPPLER PULSE COULEUR TOSHIBA
14
+
15
+ APLIO 400
16
+
17
+ Première mise en service 25.09.2016 upgradé en décembre 2023
18
+
19
+ PRESCRIPTEUR : DR
20
+ Mme MOULIN André
21
+ Le 13 décembre 2024
22
+
23
+ ECHO-DOPPLER ARTERIEL DES MEMBRES INFERIEURS
24
+
25
+ On retrouve une bonne perméabilité des axes ilio-fémoro-poplités et jambiers.
26
+ Très discrète infiltration des trépieds fémoraux, des artères fémorales superficielles sans sténose
27
+ significative localisée.
28
+ Au total :
29
+ Discrète infiltration des axes proximaux anciens non significatifs localisés.
30
+ Axes jambiers plus présents.
31
+
32
+ Docteur Alain STRUK
data_txt/template1030.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ BILAN PRE-INTERVENTIONNEL
2
+
3
+ Indication : repérage; examen scanographique. Lésion cible : mesurée à 2,5 cm. Trajet d'abord : sécuritaire. Structures à risque : distantes. Conclusions : Gestes interventionnel réalisable. "