Chaque hôpital avait ses propres formulaires. Chaque service ses propres champs. Chaque médecin ses propres préférences.
"On peut ajouter un champ pour le poids du bébé ?" "Notre service a besoin d'un formulaire différent pour les suivis." "Ce champ devrait être obligatoire seulement si le patient est diabétique."
Les tickets s'accumulaient. Modifier un template de consultation : 2 heures de dev, tests, déploiement. Multiplier par des dizaines de demandes par semaine. L'équipe technique passait plus de temps sur des modifications de formulaires que sur de vraies features.
La solution qui s'est imposée : donner le contrôle aux métiers.
L'architecture du moteur
Le système repose sur trois concepts : templates, champs, et instances.
Un template définit la structure d'un formulaire : consultation générale, suivi prénatal, résultats de laboratoire. Il appartient à un hôpital spécifique ou au catalogue partagé.
Les champs composent le template : texte, nombre, liste déroulante, signes vitaux, échelle de Glasgow. Chaque champ a ses règles de validation, ses traductions, ses conditions d'affichage.
Une instance est un formulaire rempli. Les données du patient. Les valeurs saisies par le médecin. Liée au template qui l'a générée.
class Template(models.Model):
name = models.CharField(max_length=255)
medical_act_type = models.CharField(choices=MEDICAL_ACT_TYPES)
hospital = models.ForeignKey(Hospital, null=True) # null = catalogue partagé
version = models.IntegerField(default=1)
is_active = models.BooleanField(default=True)
class TemplateField(models.Model):
template = models.ForeignKey(Template, related_name='fields')
name = models.CharField(max_length=255) # Nom technique
label_fr = models.CharField(max_length=255)
label_en = models.CharField(max_length=255)
field_type = models.CharField(choices=FIELD_TYPES)
required = models.BooleanField(default=False)
step = models.IntegerField(default=1) # Pour les formulaires multi-étapes
order = models.IntegerField(default=0)
validation_rules = models.JSONField(null=True)
conditional_rules = models.JSONField(null=True)
Les types de champs
Le système supporte plus de 30 types de champs. Des basiques aux très spécialisés.
Champs basiques : texte, textarea, nombre, décimal, date, datetime, checkbox, select, multiselect.
Champs médicaux : signes vitaux (un champ = tension, pouls, température, saturation), échelle de douleur, score de Glasgow, résultats de laboratoire avec valeurs de référence.
Champs avancés : tableaux dynamiques, groupes répétables (pour les naissances multiples), prise de rendez-vous intégrée.
Chaque type a sa validation native. Un champ vital_signs valide automatiquement que la tension systolique est entre 60 et 250 mmHg. Un champ glasgow_coma_scale calcule le score total à partir des trois composantes.
FIELD_TYPES = [
('text', 'Texte court'),
('textarea', 'Texte long'),
('number', 'Nombre entier'),
('decimal', 'Nombre décimal'),
('date', 'Date'),
('select', 'Liste déroulante'),
('vital_signs', 'Signes vitaux'),
('glasgow_coma_scale', 'Échelle de Glasgow'),
('lab_test_result', 'Résultat de laboratoire'),
('table', 'Tableau dynamique'),
# ... 20+ autres types
]
La logique conditionnelle
C'est là que le système devient puissant. Les champs peuvent apparaître ou devenir obligatoires selon les valeurs d'autres champs.
{
"conditional_rules": {
"visible_if": {
"field": "is_diabetic",
"operator": "equals",
"value": true
},
"required_if": {
"field": "glucose_level",
"operator": "not_empty"
}
}
}
Les opérateurs supportés : equals, not_equals, in, not_in, contains, not_empty, is_empty.
Le frontend évalue ces règles en temps réel. Le champ "Suivi glycémique" n'apparaît que si "Patient diabétique" est coché. Le backend valide les mêmes règles à la soumission. Double vérification.
L'isolation multi-tenant
Trois hôpitaux utilisent la plateforme. Chacun a ses propres templates. Mais certains templates sont partagés (catalogue commun).
class Template(GlobalWithHospitalMixin):
hospital = models.ForeignKey(Hospital, null=True, blank=True)
def is_shared_template(self):
return self.hospital is None
def is_available_to_hospital(self, hospital):
if self.is_shared_template():
return True
return self.hospital == hospital
Un template partagé (hospital=null) est visible par tous. Un template spécifique n'est visible que par son hôpital.
Les administrateurs d'un hôpital peuvent cloner un template partagé pour le personnaliser. Le clone appartient à leur hôpital. Les modifications n'affectent pas les autres.
Le versioning
Les templates évoluent. Mais les instances existantes doivent rester cohérentes.
Chaque modification majeure crée une nouvelle version. L'ancienne version reste active pour les instances en cours. Les nouvelles instances utilisent la dernière version.
def clone_to_new_version(self, user=None):
new_template = Template.objects.create(
name=self.name,
medical_act_type=self.medical_act_type,
version=self.version + 1,
hospital=self.hospital,
created_by=user
)
# Cloner tous les champs
for field in self.fields.all():
TemplateField.objects.create(
template=new_template,
**field.get_clonable_data()
)
return new_template
L'interface permet de voir toutes les versions, de basculer entre elles, d'activer une ancienne version si nécessaire.
L'interface d'administration
Le drag-and-drop est géré côté frontend avec Vue et vue-draggable-plus. L'administrateur voit :
- La liste des étapes (sections du formulaire) à gauche
- Les champs de l'étape sélectionnée au centre
- Les propriétés du champ sélectionné à droite
Il peut réorganiser les champs par glisser-déposer. Créer de nouvelles étapes. Configurer les règles conditionnelles via un formulaire visuel (pas de JSON à écrire).
Un onglet "JSON" permet aux utilisateurs avancés d'éditer directement la structure. Utile pour les imports/exports ou les modifications en masse.
Ce que ça a changé
Avant : chaque modification de formulaire nécessitait un ticket, un dev, un déploiement.
Après : l'administrateur de l'hôpital modifie le template, sauvegarde, c'est en production.
- Temps moyen de modification : 2 heures → 10 minutes
- Tickets "modifier un champ" : -90%
- Nouveaux templates créés par les métiers : 15+ en 3 mois
- Chaque hôpital a des workflows adaptés à sa réalité
Les médecins ne dépendent plus de l'équipe technique pour personnaliser leurs outils. Ils expérimentent. Ils itèrent. Ils adaptent les formulaires à leurs patients.
Ce qu'on en retient
Pendant des mois, on codait des formulaires à la main. Chaque modification était un ticket. Chaque hôpital demandait des variantes. L'équipe technique devenait un goulot d'étranglement.
Le moteur de templating a inversé la dynamique. Les métiers ont le contrôle. Les devs se concentrent sur les vraies features. Les workflows s'adaptent aux besoins réels du terrain.
C'est le principe du no-code appliqué aux formulaires métier. Pas de magie. Juste un système de configuration suffisamment expressif pour couvrir 95% des besoins. Les 5% restants passent encore par du code. Et c'est très bien comme ça.