Skip to content

PrevMedOrg/PrevMed

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PyPI version License: AGPL v3

📖 English version available: See README_english.md.

PrevMed (Prévention Médicale)

Plateforme minimaliste permettant à des personnes non techniques de produire des questionnaires cliniques ne stockant aucune information personnelle.

Table des matières

Objectif Principal

PrevMed est conçu pour permettre aux professionnels de santé possédant quelques compétences minimales en informatique de créer facilement des questionnaires d'aide à la décision clinique avec un script (R ou Python) et un fichier .yaml.

Fonctionnement :

  1. Le patient remplit le questionnaire sur l'interface web
  2. Un PDF avec les réponses et résultats est généré instantanément
  3. Le patient vient en consultation avec ce PDF
  4. Aucune donnée personnelle n'est stockée sur le serveur

Ce système fait gagner du temps à tout le monde : le patient prépare ses réponses en amont et le clinicien dispose immédiatement d'informations structurées, voire de scores calculés automatiquement, comme dans l’application ProbaLynch, exemple d’application de PrevMed.

Description Technique

PrevMed permet de créer des questionnaires cliniques interactifs à partir de fichiers de configuration YAML. Le système génère automatiquement une interface web avec Gradio, gère la logique conditionnelle des questions, exécute des scripts de scoring (R ou Python) et produit des rapports PDF.

Caractéristiques principales :

  • ✨ Configuration déclarative via YAML
  • 🔀 Questions conditionnelles (affichage dynamique basé sur les réponses précédentes)
  • 📊 Support de scripts de scoring en R (via rpy2) ou Python
  • 🖥️ Interface web intuitive avec Gradio
  • 📄 Génération automatique de rapports PDF
  • 📝 Logging détaillé avec loguru
  • 🎯 Type hints et documentation NumPy style
  • 🔒 Polices système uniquement — aucune requête vers Google Fonts ou tout autre serveur externe (respect de la vie privée)

Installation

PrevMed peut être installé de plusieurs façons selon vos besoins :

Méthode 1 : Installation depuis PyPI (Recommandée pour les utilisateurs finaux)

La méthode la plus simple pour une utilisation en production :

# Installer PrevMed depuis PyPI
uv pip install PrevMed

# Ou avec pip traditionnel
pip install PrevMed

Prérequis :

  • Python 3.13.5 (ou version compatible)
  • R et rpy2 si vous utilisez des scripts de scoring en R : sudo apt install r-base
  • Sur Ubuntu 22.04, vous pourriez avoir besoin de : sudo apt-get install libtirpc-dev (source)

Note : Vous devrez toujours cloner le dépôt ou télécharger les exemples séparément pour accéder aux fichiers YAML d'exemple et aux scripts de scoring.

Méthode 2 : Installation depuis les sources (Recommandée pour le développement)

Pour le développement ou la personnalisation :

# Cloner le dépôt
git clone https://github.com/PrevMedOrg/PrevMed
cd PrevMed

# Créer et activer un environnement virtuel
uv venv
source .venv/bin/activate  # Sur Windows : .venv\Scripts\activate

# Installer en mode éditable
uv pip install -e .

Prérequis : Identiques à la Méthode 1

Méthode 3 : Docker depuis PyPI (Déploiement en production)

Pour un déploiement en production conteneurisé sans code source local :

# Cloner le dépôt (seulement nécessaire pour docker-compose.yml et les exemples)
git clone https://github.com/PrevMedOrg/PrevMed
cd PrevMed

# Accéder au répertoire docker
cd docker

# Modifier docker-compose.yml pour définir INSTALL_MODE à "pypi"
# Changer la ligne: INSTALL_MODE: local
# En: INSTALL_MODE: pypi

# Optionnellement modifier la section 'command' pour spécifier les arguments souhaités
# Par exemple: --survey-yaml, --scoring-script, --save-user-data, etc.

# Lancer le conteneur en mode détaché (construit et installe depuis PyPI)
sudo docker compose up --build -d

Note : Le répertoire examples sera monté depuis votre clone local. Vous pouvez également fournir vos propres fichiers YAML et scripts de scoring en modifiant les montages de volumes dans docker-compose.yml.

Méthode 4 : Docker depuis les sources (Développement)

Pour un développement conteneurisé avec le code source local :

# Cloner le dépôt
git clone https://github.com/PrevMedOrg/PrevMed
cd PrevMed

# Accéder au répertoire docker
cd docker

# S'assurer que INSTALL_MODE est défini à "local" dans docker-compose.yml (c'est la valeur par défaut)

# Optionnellement modifier la section 'command' pour spécifier les arguments souhaités
# Par exemple: --survey-yaml, --scoring-script, --save-user-data, etc.

# Lancer le conteneur en mode détaché (construit depuis les sources locales)
sudo docker compose up --build -d

Gestion des volumes :

  • Les dossiers logs/ et survey_data/ sont montés comme volumes pour persister les données entre redémarrages
  • Les PDFs sont générés en mémoire par défaut (pas de fichiers sur disque), assurant une confidentialité maximale

Sécurité du conteneur Docker :

  • Le conteneur s'exécute avec l'utilisateur non privilégié nobody (pas de root)
  • Le système de fichiers du conteneur est en lecture seule (read_only: true), sauf pour les volumes montés (logs/ et survey_data/)
  • Ces mesures suivent les bonnes pratiques de sécurité Docker et réduisent la surface d'attaque

Cette configuration permet de bénéficier de l'isolation Docker tout en conservant les logs et données importantes, sans créer de fichiers temporaires sur le disque.

Utilisation

Lancement basique

prevmed --survey-yaml <chemin_yaml> --scoring-script <chemin_script>

Exemple avec ProbaLYNCH

Le projet inclut un exemple complet du questionnaire ProbaLYNCH (cf Références et Crédits) pour l'évaluation du risque de syndrome de Lynch :

prevmed --survey-yaml examples/ProbaLYNCH/ProbaLYNCH.yaml --scoring-script examples/ProbaLYNCH/ProbaLYNCH.R

Ceci lancera une interface Gradio accessible via votre navigateur web.

Options de ligne de commande

PrevMed supporte plusieurs options pour personnaliser le comportement de l'application :

Sauvegarde des données utilisateur

Par défaut, aucune donnée utilisateur n'est sauvegardée. Les rapports PDF sont générés en mémoire et brièvement écrits dans /dev/shm (RAM, chmod 600) uniquement pour le téléchargement par le patient — aucun fichier n'est jamais écrit sur disque, aucune réponse ni résultat n'est loggé.

Pour sauvegarder les données utilisateur de manière permanente (dans le répertoire survey_data/), utilisez l'option --save-user-data :

prevmed --survey-yaml examples/ProbaLYNCH/ProbaLYNCH.yaml \
              --scoring-script examples/ProbaLYNCH/ProbaLYNCH.R \
              --save-user-data

Avec --save-user-data activé, les données suivantes sont sauvegardées :

  • Fichiers JSON compressés (.json.gz) contenant toutes les réponses et résultats
  • Logs CSV centralisés pour analyse rapide
  • Rapports PDF stockés de manière permanente dans survey_data/

Sans cette option (comportement par défaut) :

  • Les PDFs sont générés en mémoire et brièvement écrits dans /dev/shm (RAM, chmod 600) pour le téléchargement — aucun fichier écrit sur disque
  • Aucune donnée n'est loggée dans les fichiers CSV
  • Aucun fichier JSON n'est sauvegardé
  • Respect maximal de la vie privée des patients - empreinte disque nulle

Autres options utiles

# Ouvrir automatiquement le navigateur au démarrage
prevmed --survey-yaml <yaml> --scoring-script <script> --open-browser

# Utiliser un port personnalisé (par défaut: 7860)
prevmed --survey-yaml <yaml> --scoring-script <script> --port 8080

# Activer le logging de niveau debug dans la console
prevmed --survey-yaml <yaml> --scoring-script <script> --debug

# Spécifier l'URL réelle où le questionnaire est hébergé (apparaîtra dans les PDFs)
prevmed --survey-yaml <yaml> --scoring-script <script> --actual-url "https://survey.hopital.fr/probalynch"

# Afficher les mentions légales RGPD en bas de page (élément <details> repliable)
# ⚠️  OBLIGATOIRE : l'application refuse de démarrer sans ce fichier.
# Ce fichier doit contenir au minimum : responsable du traitement, finalités,
# durée de conservation et droits des personnes concernées (RGPD art. 13/14).
prevmed --survey-yaml <yaml> --scoring-script <script> --terms-md mentions_legales.md

Arguments supplémentaires pour demo.launch()

PrevMed permet de passer n'importe quel argument supporté par Gradio directement à demo.launch(). Tous les arguments non reconnus par PrevMed sont automatiquement transmis à Gradio.

Formats supportés :

# Arguments avec valeur (chaîne, int, float)
prevmed --survey-yaml <yaml> --scoring-script <script> --gradio-option value

# Drapeaux booléens (True)
prevmed --survey-yaml <yaml> --scoring-script <script> --enable-feature

# Drapeaux booléens (False)
prevmed --survey-yaml <yaml> --scoring-script <script> --no-disable-feature

Exemples pratiques :

# Désactiver la fermeture automatique du serveur après inactivité
prevmed --survey-yaml <yaml> --scoring-script <script> --prevent-thread-lock

# Activer le mode favicon personnalisé
prevmed --survey-yaml <yaml> --scoring-script <script> --favicon-path /path/to/favicon.ico

# Combiner plusieurs arguments
prevmed --survey-yaml <yaml> --scoring-script <script> \
    --max-file-size 10000000 \
    --allowed-paths /data /images \
    --no-show-error

Note : Consultez la documentation Gradio Blocks pour la liste complète des arguments supportés par demo.launch().

Options de performance

PrevMed inclut des options pour optimiser les performances sous charge importante :

# Augmenter le nombre maximum de threads (par défaut: 40)
prevmed --survey-yaml <yaml> --scoring-script <script> --max-threads 100

# Désactiver la file d'attente des requêtes (activée par défaut)
prevmed --survey-yaml <yaml> --scoring-script <script> --no-queue

Note : La file d'attente (queue) est activée par défaut car elle améliore les performances sous charge. Pour plus d'informations sur l'optimisation des performances de Gradio, consultez le guide officiel : Setting Up a Demo for Maximum Performance.

Analytics (Optionnel)

PrevMed supporte l'intégration de Umami, une solution d'analytics respectueuse de la vie privée, self-hostable, open-source, respectant le RGPD et avec une option gratuite.

Note de confidentialité: Umami est configuré par défaut pour respecter le paramètre Do Not Track (DNT) du navigateur - les utilisateurs qui ont activé DNT dans leur navigateur ne seront pas suivis. Cette option peut être modifiée avec le paramètre --umami-ignore-dnt si nécessaire.

Configuration

Pour activer l'analytics, utilisez les arguments de ligne de commande --umami-website-id et optionnellement --umami-url :

# Option 1: Utiliser le service cloud gratuit Umami (cloud.umami.is)
prevmed --survey-yaml examples/ProbaLYNCH/ProbaLYNCH.yaml \
              --scoring-script examples/ProbaLYNCH/ProbaLYNCH.R \
              --umami-website-id "votre-website-id"

# Option 2: Utiliser votre propre instance Umami auto-hébergée
prevmed --survey-yaml examples/ProbaLYNCH/ProbaLYNCH.yaml \
              --scoring-script examples/ProbaLYNCH/ProbaLYNCH.R \
              --umami-url "https://votre-instance.example.com" \
              --umami-website-id "votre-website-id"

Exemple complet :

# Avec cloud.umami.is (gratuit)
prevmed --survey-yaml examples/ProbaLYNCH/ProbaLYNCH.yaml \
              --scoring-script examples/ProbaLYNCH/ProbaLYNCH.R \
              --umami-website-id "70991a3f-4cc9-49ae-a848-867bc75a1fd1"

# Avec instance auto-hébergée
prevmed --survey-yaml examples/ProbaLYNCH/ProbaLYNCH.yaml \
              --scoring-script examples/ProbaLYNCH/ProbaLYNCH.R \
              --umami-url "https://analytics.monhopital.fr" \
              --umami-website-id "70991a3f-4cc9-49ae-a848-867bc75a1fd1"

# Pour ignorer les préférences Do Not Track du navigateur (non recommandé)
prevmed --survey-yaml examples/ProbaLYNCH/ProbaLYNCH.yaml \
              --scoring-script examples/ProbaLYNCH/ProbaLYNCH.R \
              --umami-website-id "70991a3f-4cc9-49ae-a848-867bc75a1fd1" \
              --umami-ignore-dnt

Options disponibles :

  • --umami-website-id : ID du site web Umami (requis pour activer l'analytics)
  • --umami-url : URL de l'instance Umami (optionnel, par défaut utilise cloud.umami.is)
  • --umami-ignore-dnt : Ignorer les préférences Do Not Track du navigateur (optionnel, non recommandé pour des raisons de confidentialité)

Note : Si aucun argument analytics n'est fourni, l'application fonctionne sans analytics.

Configuration du questionnaire (YAML)

Les questionnaires sont définis dans des fichiers YAML avec la structure suivante :

survey_name: Nom du questionnaire

# Uniquement pour être mentionnée dans les logs etc:
survey_version: 1.0.0
PrevMed_version: 1.0.0

# Optionnel: titre personnalisé affiché en haut à gauche de la page
# Si défini, remplace le survey_name comme titre
# Si commence par # ou <: traité comme du markdown (et donc permettant le HTML aussi)
# Sinon: ajout automatique de `# ` avant de l'afficher comme du markdown.
page_title: Mon questionnaire

# Optionnel: afficher survey_name comme titre en haut de la page (par défaut: true)
# Ignoré si page_title est défini
show_survey_title: true

# Optionnel: afficher la version du questionnaire sur la page web et dans le PDF (par défaut: true)
show_survey_version: true

# Optionnel: afficher la version de la webapp sur la page web et dans le PDF (par défaut: true)
show_webapp_version: true

# Optionnel: contenu Markdown de la page d'accueil.
# Quand défini, une page d'accueil est affichée en premier sur la route /
# avec ce contenu et les mentions légales en bas. L'utilisateur clique sur
# "Commencer" pour accéder au questionnaire.
greetings_md: |
  # Bienvenue
  Description de la page d'accueil...

# Optionnel: chemin de la route pour la page du questionnaire quand
# greetings_md est défini. La page d'accueil est servie sur /,
# le questionnaire sur cette route. Par défaut: survey
# IMPORTANT: ne pas mettre de / dans la valeur (ex: "lynch", pas "/lynch")
survey_route: mon_questionnaire

# Optionnel: texte d'en-tête affiché en haut du questionnaire (format Markdown, avec support HTML)
header: |
  ## À propos de ce questionnaire

  Description du questionnaire...

# Optionnel: texte Markdown affiché juste avant les questions (après le header et les versions)
questions_header: |
  Veuillez répondre aux questions suivantes.

# Optionnel: texte du résumé de la section légale (par défaut: "LEGAL")
# Utilisé comme label du <details><summary> pour les mentions légales (--terms-md)
legal_summary: "Mentions légales et contact"

# Optionnel: contenu Markdown supplémentaire inclus dans le rapport PDF généré
# Supporte: titres (#, ##, ###), gras (**texte**), italique (*texte*), liens [texte](url)
pdf_extra_content: |
  ## Informations importantes

  Ceci est un texte **en gras** et *en italique*.

  Pour plus d'informations, visitez [notre site](https://example.com).

questions:
  - variable: nom_variable
    order: 1
    widget: Radio|Number|Checkbox|Textbox
    widget_args:
      # Arguments spécifiques au widget
      choices: ["Option1", "Option2"]  # Pour Radio
      precision: 0  # Pour Number
      step: 1  # Pour Slider
      label: "Texte du widget"          # Optionnel: par défaut utilise question
    question: "Texte de la question"
    skip_if: "(nom_variable == 2) and (nom_variable > autre_variable)"  # Si l'expression vaut True alors la question n'est pas posée (l'expression doit être en Python et a accès aux variables du reste du script.)

Types de widgets disponibles

Par principe, PrevMed devrait marcher avec n'importe quel widget Gradio. La liste des widgets Gradio est disponible ici. Les widgets suivants sont les plus utilisés:

  • Radio : Boutons radio pour choix unique
    • choices : Liste des options (requis)
  • Number : Champ numérique avec contrôles
  • Checkbox : Case à cocher booléenne
  • Textbox : Champ texte libre

Logique conditionnelle

PrevMed supporte deux types de logique conditionnelle :

1. Affichage conditionnel des questions (skip_if)

Les questions peuvent être affichées ou masquées dynamiquement via le champ skip_if. Les conditions sont des expressions Python évaluées avec les valeurs des variables précédentes :

- variable: age_diagnostic
  skip_if: "not (diagnostic_positif == True)"
  # Cette question ne s'affiche que si diagnostic_positif n'est pas True

- variable: age_crc_proband
  skip_if: "personal_crc_count == 0"
  # Cette question est ignorée si le patient n'a aucun cancer colorectal

Points importants :

  • L'expression retourne True → la question est ignorée
  • L'expression retourne False → la question est affichée
  • Les expressions peuvent utiliser toutes les variables des questions précédentes
  • Les opérateurs Python standards sont supportés (==, !=, >, <, and, or, not, etc.)

2. Validation des réponses (valid_if)

Les réponses peuvent être validées avant de passer à la question suivante via le champ valid_if. Si la validation échoue, un message d'erreur est affiché et l'utilisateur doit corriger sa réponse :

- variable: current_age
  widget: Number
  question: "Âge actuel du patient (en années)"
  valid_if: "current_age >= 15 and current_age <= 120"
  invalid_message: "L'âge doit être compris entre 15 et 120 ans."

- variable: personal_crc_count
  widget: Number
  question: "Combien de cancers colorectaux ?"
  valid_if: "personal_crc_count >= 0"
  invalid_message: "Le nombre de cancers ne peut pas être négatif."

Points importants :

  • L'expression retourne True → la réponse est valide, on peut continuer
  • L'expression retourne False → la réponse est invalide, un avertissement est affiché
  • Le champ invalid_message (optionnel) permet de personnaliser le message d'erreur
  • Si invalid_message n'est pas fourni, un message par défaut est utilisé
  • La validation s'exécute avant de passer à la question suivante

Scripts de scoring

Script R

Le script R doit définir une fonction scoring() qui prend les variables du questionnaire comme arguments nommés et retourne une liste avec 3 éléments :

  1. Une chaîne de caractères contenant du markdown à afficher au patient
  2. Une liste de listes représentant une table (première liste = headers, suivantes = lignes de données)
  3. Une liste nommée avec les options PDF (include_md_in_pdf et include_data_in_pdf)
scoring <- function(variable1, variable2 = NULL, ...) {
  # Logique de calcul
  score_total <- 0.40
  
  # Générer le texte markdown à afficher au patient sur l'interface web
  # Ce texte est TOUJOURS affiché au patient directement dans son navigateur
  markdown_result <- sprintf("## Résultats\n\nVotre score total est: %.1f%%", score_total * 100)
  
  # Créer la table de données (format: liste de listes)
  # Première liste = headers, suivantes = lignes de données
  table_data <- list(
    c("Catégorie", "Probabilité"),  # Headers
    c("Catégorie 1", sprintf("%.2f%%", 0.15 * 100)),
    c("Catégorie 2", sprintf("%.2f%%", 0.25 * 100)),
    c("Total", sprintf("%.2f%%", 0.40 * 100))
  )
  
  # Options de génération PDF
  pdf_options <- list(
    include_md_in_pdf = TRUE,    # Inclure aussi le markdown dans le PDF (il est toujours affiché sur l'interface web)
    include_data_in_pdf = TRUE   # Inclure la table de données dans le PDF
  )
  
  # Retourner une liste avec 3 éléments
  list(
    markdown_result,  # Élément 1: texte markdown
    table_data,       # Élément 2: table de données
    pdf_options       # Élément 3: options PDF
  )
}

Points importants :

  • Les paramètres conditionnels doivent avoir = NULL comme valeur par défaut
  • Retourner une liste avec 3 éléments : markdown, table_data, pdf_options
  • Le premier élément est une chaîne markdown qui est toujours affichée au patient sur l'interface web. L'option include_md_in_pdf contrôle si ce même texte est aussi inclus dans le rapport PDF (supporte le formatage markdown complet - voir ci-dessous)
  • Le deuxième élément est une liste de listes où la première liste contient les headers et les suivantes les lignes de données
  • Le troisième élément contrôle ce qui est inclus dans le PDF (markdown et/ou table) - notez que le markdown est toujours affiché sur la page web indépendamment de ce paramètre
  • Les noms de paramètres doivent correspondre aux noms de variables du YAML

Script Python

Le script Python doit définir une fonction scoring() qui retourne un tuple avec 3 éléments :

  1. Une chaîne contenant du markdown à afficher au patient
  2. Une liste de listes représentant une table (première liste = headers, suivantes = lignes de données)
  3. Un dictionnaire avec les options PDF (include_md_in_pdf et include_data_in_pdf)
def scoring(variable1: str, variable2: int = None, **kwargs) -> tuple[str, list[list[str]], dict[str, bool]]:
    """Calcul du score."""
    # Logique de calcul
    score_total = 0.40
    
    # Générer le texte markdown à afficher au patient sur l'interface web
    # Ce texte est TOUJOURS affiché au patient directement dans son navigateur
    markdown_result = f"## Résultats\n\nVotre score total est: {score_total * 100:.1f}%"
    
    # Créer la table de données (format: liste de listes)
    # Première liste = headers, suivantes = lignes de données
    table_data = [
        ["Catégorie", "Probabilité"],  # Headers
        ["Catégorie 1", f"{0.15 * 100:.2f}%"],
        ["Catégorie 2", f"{0.25 * 100:.2f}%"],
        ["Total", f"{0.40 * 100:.2f}%"]
    ]
    
    # Options de génération PDF
    pdf_options = {
        "include_md_in_pdf": True,    # Inclure aussi le markdown dans le PDF (il est toujours affiché sur l'interface web)
        "include_data_in_pdf": True   # Inclure la table de données dans le PDF
    }
    
    # Retourner un tuple avec 3 éléments
    return (markdown_result, table_data, pdf_options)

Points importants :

  • Retourner un tuple avec 3 éléments : markdown, table_data, pdf_options
  • Le premier élément est une chaîne markdown qui est toujours affichée au patient sur l'interface web. L'option include_md_in_pdf contrôle si ce même texte est aussi inclus dans le rapport PDF (supporte le formatage markdown complet - voir ci-dessous)
  • Le deuxième élément est une liste de listes où la première liste contient les headers et les suivantes les lignes de données
  • Le troisième élément contrôle ce qui est inclus dans le PDF (markdown et/ou table) - notez que le markdown est toujours affiché sur la page web indépendamment de ce paramètre
  • Les valeurs de la table peuvent être de n'importe quel type (elles seront converties en chaînes automatiquement)
  • Les noms de paramètres doivent correspondre aux noms de variables du YAML

Formatage Markdown supporté

Le champ markdown retourné par les scripts de scoring supporte les éléments suivants :

  • Titres : #, ##, ###, etc. (niveaux arbitraires)
  • Gras : **texte**
  • Italique : *texte*
  • Liens : [texte](url) (les liens avec URL vide sont ignorés pour éviter les erreurs)
  • Tables : format pipe standard
  • HTML : les balises HTML sont supportées dans les champs Markdown (ex : <h1 style="text-align: center;">, <details>, <summary>, etc.)

Exemple de table dans le markdown :

| Catégorie | Valeur |
|-----------|--------|
| A         | 10%    |
| B         | 20%    |

Génération de rapports PDF

Les rapports PDF sont générés automatiquement à la fin du questionnaire et incluent :

  • Nom et version du questionnaire
  • Code de référence unique (format XXX-YYY, facile à mémoriser)
  • Horodatage de génération
  • Résultats du scoring (texte formaté + tableau structuré)
  • Toutes les réponses aux questions

Génération de PDFs en mémoire

Approche de stockage des PDFs :

  • Par défaut : les PDFs sont générés en mémoire et servis via un fichier temporaire dans /dev/shm (tmpfs RAM — aucune écriture sur disque)
  • Avec --save-user-data : les PDFs sont sauvegardés de manière permanente dans survey_data/ en plus des données JSON

Fonctionnement de la génération en mémoire :

Lorsque --save-user-data n'est pas activé (comportement par défaut) :

  1. Le PDF est généré directement en mémoire en utilisant le buffer BytesIO de Python
  2. Les octets sont écrits dans un fichier temporaire dans /dev/shm (système de fichiers RAM de Linux — ne touche jamais le disque), avec les permissions 600 (lecture réservée au propriétaire) pour empêcher d'autres utilisateurs d'y accéder
  3. Le DownloadButton de Gradio sert le fichier au navigateur du patient (il requiert un chemin de fichier, pas un objet BytesIO)
  4. Le fichier temporaire est automatiquement supprimé après un court délai une fois la fenêtre de téléchargement passée
  5. Aucun fichier n'est jamais écrit sur disque - protection maximale de la vie privée

Exemple de workflow :

  1. Patient complète le questionnaire à 14h00
  2. PDF généré en mémoire (BytesIO)
  3. PDF écrit dans /dev/shm (RAM uniquement, chmod 600) pour être servi par Gradio
  4. Patient télécharge le PDF
  5. Fichier temporaire supprimé de la RAM — aucune trace ne reste sur le disque

Cette approche assure une empreinte disque nulle et une confidentialité maximale des patients : les données ne résident qu'en RAM.

Stockage des données structurées (si activé)

Lorsque l'option --save-user-data est activée, PrevMed sauvegarde toutes les données sous deux formats complémentaires :

Fichiers JSON compressés (si --save-user-data activé)

Avec --save-user-data, chaque soumission est sauvegardée en JSON compressé (.json.gz) dans survey_data/ avec :

  • Nom du questionnaire et versions
  • Réponses complètes
  • Résultats de scoring
  • Code de référence unique
  • Timestamp Unix
  • Hashes de client (pour détection de doublons anonyme)

Format du nom de fichier : {timestamp}_{reference_code}_{unique_id}.json.gz

Exemple : 1729500000_A2B-3C4_a1b2c3d4.json.gz

Note : Le {unique_id} est un fragment UUID (premiers 8 caractères) qui garantit l'unicité absolue même en cas de collision d'horodatage.

Logs CSV (si --save-user-data activé)

Avec --save-user-data, un fichier CSV centralisé enregistre toutes les soumissions pour analyse rapide :

Emplacement : survey_data/csv/{PrevMed_version}/{survey_name}_{survey_version}/survey_submissions.csv

Exemple : survey_data/csv/0.8.0/ProbaLYNCH_1.0.0/survey_submissions.csv

Caractéristiques du système CSV

Structure des colonnes :

  • Colonnes fixes : reference_code, row_number, timestamp_unix, datetime
  • Colonnes de scoring : une par résultat (ex: p_MLH1, p_MSH2, formatées en pourcentages)
  • Colonnes de hashes : answers_hash + hashes individuels par attribut client (ex: user_agent_hash, ip_address_hash)

Gestion de la concurrence :

  • Utilise filelock pour garantir l'atomicité des écritures
  • Supporte l'accès concurrent depuis plusieurs processus/serveurs
  • Timeout de 10 secondes sur le verrou

Rotation automatique :

  • Le CSV est automatiquement archivé après 1000 lignes
  • Fichier archivé : survey_submissions_{timestamp}.csv (sauvegarde permanente)
  • Nouveau CSV créé automatiquement pour continuer l'enregistrement
  • Objectif : maintenir des performances élevées même avec accès concurrent intensif

Gestion des erreurs :

  • En cas de timeout du verrou (charge très élevée), les données sont sauvegardées dans un fichier de secours
  • Format : survey_submissions_fallback_{timestamp}_{uuid}.csv (où {uuid} est un fragment UUID de 8 caractères)
  • Garantie : aucune perte de données même sous charge extrême

Détection de doublons :

  • answers_hash : hash court (12 caractères) des réponses uniquement
  • Hashes individuels de client : chaque attribut (user-agent, IP, etc.) haché séparément avec le code de référence comme sel
  • Permet l'analyse de doublons tout en préservant la vie privée

Exemple de contenu CSV :

reference_code,row_number,timestamp_unix,datetime,p_MLH1,p_MSH2,p_MSH6,p_PMS2,p_total,answers_hash,user_agent_hash,ip_address_hash,session_hash_hash
A2B-3C4,1,1729500000,2024-10-21 14:20:00,15.23,25.47,8.92,10.38,60.00,a1b2c3d4e5f6,x9y8z7w6v5u4,q1w2e3r4t5y6,m1n2b3v4c5x6
D5E-6F7,2,1729500120,2024-10-21 14:22:00,2.15,3.28,1.45,1.12,8.00,f6e5d4c3b2a1,u4v5w6z7y8x9,y6t5r4e3w2q1,x6c5v4b3n2m1

Avantages de cette architecture :

  • Performance : rotation limite la taille des fichiers pour maintenir vitesse d'accès
  • Fiabilité : système de fallback garantit zéro perte de données
  • Traçabilité : archivage automatique avec timestamps
  • Analyse : format CSV facilite l'analyse statistique rapide
  • Scalabilité : gestion de concurrence permet déploiement multi-processus/multi-serveurs
  • Vie privée : hashes salés permettent détection de doublons sans stocker données personnelles brutes

Structure du projet

PrevMed/
├── src/
│   ├── __init__.py          # Setup du logging
│   ├── __main__.py          # Point d'entrée CLI
│   └── utils/
│       ├── gui/             # Interface Gradio (package)
│       │   ├── __init__.py  # Réexporte create_survey_interface
│       │   ├── greetings.py # Page d'accueil (greetings_md + mentions légales)
│       │   └── survey.py    # Questionnaire (questions, scoring, PDF)
│       ├── css.py           # Le CSS utilisé dans Gradio
│       ├── js.py            # Le js utilisé dans Gradio
│       ├── io.py            # Chargement YAML et scripts
│       ├── logic.py         # Logique conditionnelle
│       ├── pdf.py           # Génération PDF
│       ├── scoring.py       # Exécution scripts R/Python
│       └── settings.py      # Stocke des variables disponibles dans tout le script
├── examples/
│   └── ProbaLYNCH/
│       ├── ProbaLYNCH.yaml      # Configuration ProbaLYNCH
│       └── ProbaLYNCH.R         # Script de scoring ProbaLYNCH
├── logs/                    # Logs rotatifs (créé automatiquement)
├── survey_pdfs/             # Rapports PDF (créé automatiquement)
├── requirements.txt
└── setup.py

Logs

Les logs sont automatiquement sauvegardés dans ./logs/ avec rotation journalière et rétention de 30 jours. Le format inclut :

  • Horodatage avec millisecondes
  • Niveau de log
  • Fichier, fonction et ligne
  • Message

Par défaut, les logs fichiers sont au niveau INFO. Pour activer le niveau DEBUG (plus verbeux), définissez la variable d'environnement PREVMED_LOG_LEVEL :

PREVMED_LOG_LEVEL=DEBUG prevmed --survey-yaml <yaml> --scoring-script <script>

Exemple ProbaLYNCH

Pour illustrer le fonctionnement de PrevMed, nous avons implémenté le questionnaire ProbaLYNCH (preventionfamiliale.fr) (cf. Références et Crédits).

Le questionnaire ProbaLYNCH évalue le risque de mutations dans les gènes MLH1, MSH2, MSH6 et PMS2 (syndrome de Lynch) basé sur :

  • L'histoire personnelle de cancers (colorectal, utérus (« endomètre »), autres)
  • Les âges au diagnostic
  • L'histoire familiale (apparentés proches)

Le modèle ProbaLYNCH utilise un modèle de régression logistique multinomiale avec transformation softmax pour calculer les probabilités de mutation pour chaque gène d'une liste prédéterminée de gènes. Une adaptation en langage R a été écrite en 2025 par Laury NICOLAS, médecin du CHU de Pointe-à-Pitre, quand il était Dr Junior dans le Service GENOAP (Génétique-Oncogénétique-Adulte-Prévention) du CHU de Clermont-Ferrand. Ce script R à ensuite été optimisé par madame Anna Serova-Erard, Conseillère en Génétique et Médecine Prédictive à GENOAP.

Développement

Conventions de code

  • Type hints et docstrings NumPy style partout
  • Commentaires explicites pour les décisions de design
  • Utilisation de loguru pour le logging
  • Code simple et robuste privilégié
  • Utilisation de Ruff comme linter pour assurer la qualité du code

Contributions

Ce projet a été développé avec l'assistance de aider.chat et Claude Code.

Nous accueillons volontiers :

  • 🐛 Les signalements de bugs via les Issues
  • ✨ Les demandes de fonctionnalités
  • 🔧 Les Pull Requests pour améliorer le projet

N'hésitez pas à contribuer !

Licence

Actuellement sous licence GNU Affero General Public License. Cependant, nous sommes flexibles et ouverts à des arrangements de licence alternatifs - n'hésitez pas à nous contacter si vous avez besoin d'une licence différente pour votre cas d'usage.

Références et Crédits

Concernant l'exemple ProbaLYNCH :

  1. Afin de permettre à tout un chacun d’évaluer sa probabilité d’être porteur du risque génétique, l’association de soutien à la génétique épidémiologique ASAGE (loi 1901 n°RNA W751217490, J.O. du 15 décembre 2012), met à disposition libre, gratuite et sans stockage d’aucune donnée personnelle, l’application ProbaLYNCH (sous licence GNU Affero General Public License) : le code informatique montrant l’absence de conservation des données est accessible en ligne et tourne chez un hébergeur français. L’application initiale a été codée en langage informatique R en 2025, quand il était Dr Junior dans le Service GENOAP (Génétique-Oncogénétique-Adulte-Prévention) du CHU de Clermont-Ferrand, par le Dr Laury NICOLAS, actuellement Chef de Clinique de l’Université des Antilles - Assistant des Hôpitaux au CHU de la Guadeloupe. Le script R a été optimisé à GENOAP par Madame Anna SEROVA-ERARD, Conseillère en Génétique et Médecine Prédictive. Les illustrations ont été réalisées par Madame Anne SPECQ, Conseillère en Génétique et Médecine Prédictive, ancienne stagiaire de GENOAP. L'application ProbaLynch a été finalisée par olicorp sur sa plateforme PrevMed, mise à disposition librement.
  2. Kastrinos et al, J Clin Oncol 35, 2165-2172 (2017). DOI: 10.1200/JCO.2016.69.6120. « Development and Validation of the PREMM5 Model for Comprehensive Risk Assessment of Lynch Syndrome ».

About

Une plateforme minimaliste permettant aux utilisateurs non techniques de créer des questionnaires cliniques sans stocker de données personnelles. | A minimalist platform enabling non-technical users to create clinical questionnaires without storing personal data.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors