<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Automatisation on Boris Dupoux</title><link>https://borisdupoux.com/fr/categories/automatisation/</link><description>Recent content in Automatisation on Boris Dupoux</description><generator>Hugo -- gohugo.io</generator><language>fr</language><lastBuildDate>Mon, 01 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://borisdupoux.com/fr/categories/automatisation/index.xml" rel="self" type="application/rss+xml"/><item><title>Automatisation déterministe des processus sous fortes contraintes de sécurité</title><link>https://borisdupoux.com/fr/research/deterministic-rpa/</link><pubDate>Mon, 01 Jun 2026 00:00:00 +0000</pubDate><guid>https://borisdupoux.com/fr/research/deterministic-rpa/</guid><description>&lt;h2 id="contexte"&gt;&lt;a href="#contexte" class="header-anchor"&gt;&lt;/a&gt;Contexte
&lt;/h2&gt;&lt;p&gt;Un client soumis à de fortes contraintes de sécurité gère plusieurs centaines de sites
et exécute chaque jour quatre processus administratifs récurrents : la maintenance des
équipements sur le terrain, le traitement des événements parasites récurrents émis par
ces équipements, la rédaction de rapports d&amp;rsquo;intervention pour ses propres clients et la
vérification de la bonne réception du contrôle de santé périodique de chaque site. Les
données d&amp;rsquo;entrée sont dispersées entre trois systèmes aux modes d&amp;rsquo;accès très différents :
des rapports PDF reçus par courriel, un portail web métier sans API publique et des
classeurs Excel tenus à la main depuis de nombreuses années.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;objectif : automatiser ces processus sur un poste de travail de bureau tout en laissant
à un opérateur humain le dernier mot sur chaque message adressé aux clients.&lt;/p&gt;
&lt;h2 id="contraintes-de-conception"&gt;&lt;a href="#contraintes-de-conception" class="header-anchor"&gt;&lt;/a&gt;Contraintes de conception
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Aucun modèle de langage à l&amp;rsquo;exécution.&lt;/strong&gt; Chaque décision découle de règles explicites
et chaque phrase d&amp;rsquo;un modèle de texte. La sortie du robot est ainsi reproductible : les
mêmes entrées produisent toujours les mêmes brouillons, de sorte qu&amp;rsquo;une nouvelle version
peut être comparée mot pour mot à la production, et que toute modification peut être
rattachée à une règle ou à une source de données. Aucune donnée client n&amp;rsquo;est transmise à
un service de modèle externe. Un modèle vision-langage a été utilisé une seule fois, hors
ligne, lors de la conception, pour cartographier des sessions d&amp;rsquo;écran enregistrées
d&amp;rsquo;opérateurs ; il ne fait pas partie du système déployé.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Des brouillons, pas des envois.&lt;/strong&gt; Le robot se contente de créer des brouillons
Outlook ; l&amp;rsquo;opérateur les relit et les envoie. Un interrupteur d&amp;rsquo;arrêt global impose le
mode brouillon quels que soient les réglages propres à chaque processus.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exécution locale, sans nouvelle infrastructure.&lt;/strong&gt; Python sur un poste Windows, tâches
planifiées, dossier partagé sur NAS pour l&amp;rsquo;état et la configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aucun secret en clair.&lt;/strong&gt; Identifiants et jetons sont stockés dans le Gestionnaire
d&amp;rsquo;identifiants Windows (DPAPI) ; la configuration est en YAML, modifiable par
l&amp;rsquo;opérateur sans recompilation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="architecture"&gt;&lt;a href="#architecture" class="header-anchor"&gt;&lt;/a&gt;Architecture
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 SCHED["Passages planifiés (matin / après-midi)"] --&gt; ING
 MAIL["Boîte aux lettres (Microsoft Graph, lecture)"] --&gt; ING["Ingestion rapports PDF regroupés par jour"]
 ING --&gt; PARSE["Analyseur PDF (pypdf + expressions régulières)"]
 PORTAL["Portail web métier connexion Playwright → jeton de session → rejeu des points d'accès JSON"] --&gt; RULES
 XLS["Classeurs de référence &amp; d'historique (Excel)"] --&gt; RULES
 PARSE --&gt; RULES["Moteur de règles 4 processus, règles ordonnées &amp; exclusions"]
 RULES --&gt; TPL["Composeur de modèles accords, noms, dates en français"]
 TPL --&gt; DRAFTS["Brouillons Outlook (Graph, jamais envoyés)"]
 RULES --&gt; TRACK["Classeurs de suivi (ajout atomique)"]
 DRAFTS --&gt; OP(["L'opérateur relit &amp; envoie"])
 OP -. "éléments envoyés : destinataires corrigés" .-&gt; RULES
 NAS[("NAS : verrous, battements de cœur, journal, registre d'idempotence")] --- RULES
 RULES --&gt; HEALTH["Alertes de santé webhook + contrôle de l'homme mort"]&lt;/pre&gt;&lt;h2 id="les-quatre-processus"&gt;&lt;a href="#les-quatre-processus" class="header-anchor"&gt;&lt;/a&gt;Les quatre processus
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Maintenance des équipements.&lt;/strong&gt; Les lignes de panne extraites des rapports PDF
quotidiens sont confrontées à l&amp;rsquo;historique des piles et des réparations de chaque
appareil. Un arbre de décision ordonné (la première correspondance l&amp;rsquo;emporte) choisit
entre ignorer, attendre, remplacer la pile, remplacer l&amp;rsquo;appareil ou tester la portée
radio, et rédige un ordre d&amp;rsquo;intervention pour le technicien ; chaque appareil d&amp;rsquo;un ordre
portant sur plusieurs appareils conserve sa propre décision.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Événements parasites récurrents.&lt;/strong&gt; Le nombre d&amp;rsquo;événements par site et par appareil sur
24 heures est comparé à des seuils pour détecter les événements parasites récurrents. Les
données du portail (un incident confirmé comme réel, un système mis à l&amp;rsquo;arrêt quelques
minutes après une salve) priment sur ces comptages ; il en résulte un courriel de
sensibilisation au client, un ordre d&amp;rsquo;intervention, ou rien.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rapports d&amp;rsquo;intervention.&lt;/strong&gt; Pour chaque intervention sur site, le robot reconstitue une
chronologie unique à partir du journal d&amp;rsquo;événements du portail, du journal d&amp;rsquo;appels et
des relevés des équipes de terrain, gère les événements à cheval sur minuit et rédige un
rapport client accompagné des fichiers médias.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Contrôles de santé périodiques.&lt;/strong&gt; Les sites dont le contrôle de santé périodique est
absent sont identifiés, l&amp;rsquo;état de chaque canal de connectivité est vérifié sur le
portail, puis un courriel au client, un ordre d&amp;rsquo;intervention ou une note interne est
rédigé.&lt;/p&gt;
&lt;h2 id="rédiger-un-courriel--des-paramètres-au-brouillon"&gt;&lt;a href="#r%c3%a9diger-un-courriel--des-param%c3%a8tres-au-brouillon" class="header-anchor"&gt;&lt;/a&gt;Rédiger un courriel : des paramètres au brouillon
&lt;/h2&gt;&lt;p&gt;Chaque brouillon est l&amp;rsquo;aboutissement d&amp;rsquo;une longue chaîne de décisions explicites.
Comptés dans le code source (octobre 2026), les quatre moteurs de règles comportent
&lt;strong&gt;53 issues terminales&lt;/strong&gt; (chaque endroit où une règle rend une décision), réparties sur
&lt;strong&gt;37 types de décision&lt;/strong&gt; et &lt;strong&gt;13 générateurs de brouillons&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Le courriel de sensibilisation du processus « événements parasites » en donne la mesure.
Un site n&amp;rsquo;y aboutit qu&amp;rsquo;après &lt;strong&gt;14 tests ordonnés&lt;/strong&gt; au plus dans le moteur de règles
(16 issues terminales pour ce seul processus). Le composeur lit ensuite &lt;strong&gt;21 entrées&lt;/strong&gt; :
17 champs de la fiche de décision du site et de la fiche client, la liste des
destinataires, les pièces jointes et deux ressources apprises (les noms de zones du
portail et un dictionnaire d&amp;rsquo;orthographe appris dans le classeur d&amp;rsquo;historique de
l&amp;rsquo;opérateur lui-même). Entre la décision et le texte final, on compte &lt;strong&gt;108 points de
branchement conditionnel&lt;/strong&gt; (instructions &lt;code&gt;if&lt;/code&gt;, expressions conditionnelles et opérandes
booléens, comptés sur l&amp;rsquo;arbre syntaxique du composeur et des dix fonctions auxiliaires
qu&amp;rsquo;il appelle, avant la mise en forme HTML).&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 IN(["1 · Moteur de règles — 14 tests ordonnés, la première correspondance l'emporte. Entrées : rapports d'événements quotidiens · journal du portail · fiche client · profil appris du site"]) --&gt; T0

 T0["Drapeau bloquant sur la fiche du site ?"] -- oui --&gt; X0["Pas de courriel"]
 T0 -- non --&gt; T1["R6 · incident confirmé comme réel aujourd'hui ?"]
 T1 -- oui --&gt; X1["Pas de courriel"]
 T1 -- non --&gt; T2["R17 · événements médias à moins de 30 s entre zones ?"]
 T2 -- oui --&gt; X2["Arbitrage manuel"]
 T2 -- non --&gt; T3["R12 · cause externe confirmée par les médias ?"]
 T3 -- non --&gt; T4["R8 · ≥ 8 événements d'intégrité ?"]
 T4 -- oui --&gt; T4b["persistants depuis ≥ 3 jours ?"]
 T4b -- oui --&gt; X3["Ordre d'intervention technicien"]
 T4b -- non --&gt; X4["En attente"]
 T4 -- non --&gt; T5["≥ 2 événements d'intégrité, ≥ la moitié de la salve ?"]
 T5 -- oui --&gt; X5["Pas de courriel"]
 T5 -- non --&gt; T6["R16 · ≥ 140 événements, sensibilisation ignorée depuis ≥ 4 semaines ? (désactivée par défaut)"]
 T6 -- oui --&gt; X6["Avis de facturation"]
 T6 -- non --&gt; T7["R15 · drapeau bâtiment partagé ?"]
 T7 -- oui --&gt; X7["Courriel de coordination"]
 T7 -- non --&gt; T8["R5 · week-end + drapeau appareil chroniquement défaillant ?"]
 T8 -- oui --&gt; X8["Demande de masquage temporaire"]
 T8 -- non --&gt; T9["R1 · même appareil ≥ 7 événements ?"]
 T9 -- non --&gt; T10["R2 · ≥ 5 appareils et ≥ 8 événements ?"]
 T10 -- non --&gt; T11["R4 · ≥ 8 événements, ≥ 50 % entre 5 h et 7 h ?"]
 T11 -- non --&gt; T12["R3 · ≥ 8 événements, ≥ 60 % entre 12 h et 18 h, ≥ 70 % en 4 h ?"]
 T12 -- non --&gt; T13["R1bis · total du site ≥ 7 événements ?"]
 T13 -- non --&gt; X9["Pas de courriel"]

 T3 &amp; T9 &amp; T10 &amp; T11 &amp; T12 &amp; T13 -- oui --&gt; ADV

 ADV(["2 · Composeur — courriel de sensibilisation, identifiant de règle transmis. Nom du client : forme juridique retirée, casse rétablie, sigles conservés"])
 ADV --&gt; SUBJ["≥ 30 événements ?"]
 SUBJ -- oui --&gt; S1["Objet avec préfixe d'urgence"]
 SUBJ -- non --&gt; S2["Objet simple"]
 S1 &amp; S2 --&gt; GR["Cellule contact"]
 GR -- "plusieurs personnes, ou mot de fonction / d'organisation" --&gt; G1["Salutation générique"]
 GR -- "civilité + nom" --&gt; G2["Salutation + civilité normalisée (7 abréviations) + NOM"]
 GR -- "nom seul" --&gt; G3["Salutation + NOM"]
 G1 &amp; G2 &amp; G3 --&gt; DEV["Appareils concernés"]
 DEV -- un --&gt; D1["Nom de zone du portail s'il est connu, sinon libellé nettoyé + table de jargon + accents appris"]
 DEV -- plusieurs --&gt; D2["Formule générique + liste de 6 au plus par nombre d'événements, chacun avec son code s'il est connu"]
 D1 &amp; D2 --&gt; HND["Système mis en marche ou à l'arrêt à quelques minutes de la salve ? (R1 à R4 seulement)"]
 HND -- "arrêt juste après" --&gt; C1["Erreur de manipulation, formulation A"]
 HND -- "marche juste avant" --&gt; C2["Erreur de manipulation, formulation B"]
 HND -- non --&gt; ACT["Site en activité ? — toutes les conditions : R1 à R4 ou R1bis · ≥ 2 appareils · ≥ 10 événements · aucune intervention dans la fenêtre · site professionnel · ≥ 90 % dans les heures apprises · capteurs médias seuls : ≥ 90 % système en marche"]
 ACT -- oui --&gt; C3["Formulation « site en activité »"]
 ACT -- non --&gt; RUL["Règle déclenchée"]
 RUL -- "R1, R2" --&gt; C4["Cause environnementale générique"]
 RUL -- "R3, R4" --&gt; C5["Cause : passages du personnel"]
 RUL -- R12 --&gt; C6["Cause externe confirmée par les médias"]
 RUL -- R1bis --&gt; C7["Cause à rechercher sur place"]
 C4 &amp; C5 --&gt; ATT["Médias réellement joints ?"]
 ATT -- oui --&gt; A1["+ renvoi aux pièces jointes"]
 ATT -- non --&gt; FLG
 A1 &amp; C1 &amp; C2 &amp; C3 &amp; C6 &amp; C7 --&gt; FLG["Drapeau du site : travaux fréquents ?"]
 FLG -- oui --&gt; F1["Demande : arrêt avant travaux"]
 FLG -- non --&gt; F2["Demande : traiter la cause"]
 F1 &amp; F2 --&gt; REC["Adresses dans la fiche client ?"]
 REC -- oui --&gt; RC1["Toutes les adresses trouvées"]
 REC -- non --&gt; RC2["Boîte interne de repli"]
 RC1 &amp; RC2 --&gt; DRAFT(["Texte brut → HTML Outlook (dé-repliage, listes, typographie fixe, signature) → dossier des brouillons, jamais envoyé"])
 classDef q stroke-dasharray:4 3,stroke-width:2px;
 class T0,T1,T2,T3,T4,T4b,T5,T6,T7,T8,T9,T10,T11,T12,T13,SUBJ,GR,DEV,HND,ACT,RUL,ATT,FLG,REC q;&lt;/pre&gt;&lt;p&gt;L&amp;rsquo;espace de formulation de ce seul courriel : 2 formes d&amp;rsquo;objet, 3 formes de salutation,
2 formes de phrase d&amp;rsquo;ouverture, 7 formulations de cause atteignables (dont deux avec ou
sans renvoi aux médias joints), 2 demandes d&amp;rsquo;action et une liste facultative de 2 à
6 appareils. Tous les seuils ci-dessus sont dans la configuration YAML, pas dans le code.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Paramètre&lt;/th&gt;
					&lt;th&gt;Source&lt;/th&gt;
					&lt;th&gt;Effet sur le courriel&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Nombre d&amp;rsquo;événements sur 24 h&lt;/td&gt;
					&lt;td&gt;rapports quotidiens&lt;/td&gt;
					&lt;td&gt;chiffre de la phrase d&amp;rsquo;ouverture ; à partir de 30, préfixe d&amp;rsquo;urgence dans l&amp;rsquo;objet&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Règle déclenchée&lt;/td&gt;
					&lt;td&gt;moteur de règles&lt;/td&gt;
					&lt;td&gt;choisit la formulation de la cause ; conditionne les vérifications « erreur de manipulation » et « site en activité »&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Appareils concernés (nettoyés, dédoublonnés)&lt;/td&gt;
					&lt;td&gt;rapports quotidiens&lt;/td&gt;
					&lt;td&gt;un appareil nommé, ou une formule générique suivie d&amp;rsquo;une liste de 6 lignes au plus, par nombre d&amp;rsquo;événements&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Code de l&amp;rsquo;appareil sur chaque ligne de rapport&lt;/td&gt;
					&lt;td&gt;rapports quotidiens&lt;/td&gt;
					&lt;td&gt;ajouté entre parenthèses à chaque appareil listé, seulement s&amp;rsquo;il est connu&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Nom de zone de l&amp;rsquo;appareil principal&lt;/td&gt;
					&lt;td&gt;portail&lt;/td&gt;
					&lt;td&gt;remplace le libellé brut du rapport lorsqu&amp;rsquo;il est disponible&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Graphies apprises&lt;/td&gt;
					&lt;td&gt;classeur d&amp;rsquo;historique de l&amp;rsquo;opérateur&lt;/td&gt;
					&lt;td&gt;rétablit accents et casse des noms d&amp;rsquo;appareils&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Cellules civilité et nom du contact&lt;/td&gt;
					&lt;td&gt;fiche client&lt;/td&gt;
					&lt;td&gt;l&amp;rsquo;une des 3 formes de salutation ; plusieurs personnes, une fonction ou un nom d&amp;rsquo;organisation ramènent à la forme générique&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Raison sociale&lt;/td&gt;
					&lt;td&gt;fiche client&lt;/td&gt;
					&lt;td&gt;forme juridique retirée, casse rétablie en conservant les sigles, utilisée dans l&amp;rsquo;objet et le corps&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Type de site, heures d&amp;rsquo;ouverture apprises, interventions dans la fenêtre&lt;/td&gt;
					&lt;td&gt;classeur de suivi, journal du portail&lt;/td&gt;
					&lt;td&gt;active ou neutralise la formulation « site en activité »&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Mise en marche ou à l&amp;rsquo;arrêt autour de la salve&lt;/td&gt;
					&lt;td&gt;journal du portail&lt;/td&gt;
					&lt;td&gt;formulation « erreur de manipulation » (2 variantes), prioritaire sur toute autre cause&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Drapeaux du site&lt;/td&gt;
					&lt;td&gt;fiche client&lt;/td&gt;
					&lt;td&gt;choix entre les 2 demandes d&amp;rsquo;action&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Présence de pièces jointes&lt;/td&gt;
					&lt;td&gt;médias du portail&lt;/td&gt;
					&lt;td&gt;renvoi aux pièces jointes, ou rien&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Adresses des destinataires&lt;/td&gt;
					&lt;td&gt;fiche client&lt;/td&gt;
					&lt;td&gt;toutes les adresses trouvées ; à défaut, une boîte interne de repli&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Les autres brouillons s&amp;rsquo;appuient sur les mêmes fonctions partagées de rédaction en
français : la préposition devant un nom de site s&amp;rsquo;accorde avec son premier nom (6 formes,
de « chez » à « à l&amp;rsquo; » et « aux ») ; les titres des rapports s&amp;rsquo;accordent en genre et en
nombre ; les dates sont écrites en toutes lettres ; l&amp;rsquo;intitulé « Contact » passe au
pluriel dès que plusieurs contacts sont listés ; et une table de 8 entrées traduit le
jargon technique des équipements en vocabulaire client.&lt;/p&gt;
&lt;h2 id="ingénierie-de-la-fiabilité"&gt;&lt;a href="#ing%c3%a9nierie-de-la-fiabilit%c3%a9" class="header-anchor"&gt;&lt;/a&gt;Ingénierie de la fiabilité
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Idempotence.&lt;/strong&gt; Un registre quotidien indexé par site, type de message et empreinte de
l&amp;rsquo;objet empêche les brouillons en double ; un registre inter-jours empêche de réémettre
un ordre d&amp;rsquo;intervention avant que l&amp;rsquo;intervention précédente n&amp;rsquo;ait eu lieu. Les jours
manqués sont rattrapés automatiquement.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Concurrence sur un NAS partagé.&lt;/strong&gt; Fichiers de verrou exclusifs (&lt;code&gt;O_CREAT|O_EXCL&lt;/code&gt;)
avec une durée de vie pour les verrous orphelins ; deux postes publient des battements de
cœur, et le poste secondaire ne prend le relais que lorsque le principal est silencieux
depuis deux minutes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Écritures sûres dans les classeurs.&lt;/strong&gt; Les écritures passent par un fichier temporaire
et un remplacement atomique, avec nouvelles tentatives lorsque le classeur est ouvert par
un utilisateur ; les lignes insérées décalent les formules de pied de tableau pour que
les totaux restent justes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Robustesse réseau.&lt;/strong&gt; Les appels Graph gèrent le renouvellement des jetons, l&amp;rsquo;en-tête
&lt;code&gt;Retry-After&lt;/code&gt; des réponses HTTP 429 et le repli progressif sur les erreurs 5xx.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Échec explicite.&lt;/strong&gt; Une enveloppe de plantage, sans dépendance au paquet principal,
envoie une alerte expurgée et un signal d&amp;rsquo;homme mort même lorsque les imports échouent ;
une sentinelle déclenche une alerte lorsque le portail ne renvoie aucune donnée au lieu
de signaler un succès.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Évaluation en parallèle (shadow).&lt;/strong&gt; Une version bac à sable rejoue une journée en
lecture seule et compare ses brouillons à ceux de la production. Le critère
d&amp;rsquo;acceptation est la part des brouillons que l&amp;rsquo;opérateur envoie sans en modifier un
seul mot.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="résultats"&gt;&lt;a href="#r%c3%a9sultats" class="header-anchor"&gt;&lt;/a&gt;Résultats
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1 766&lt;/strong&gt; tests automatisés dans le dépôt (octobre 2026), reposant sur des adaptateurs
simulés de messagerie et de portail, les tests sur systèmes réels étant marqués à part.&lt;/li&gt;
&lt;li&gt;Un audit du corpus de rapports reçus a montré que &lt;strong&gt;9,5 %&lt;/strong&gt; des lignes d&amp;rsquo;événements
étaient écartées silencieusement, parce que les codes et libellés d&amp;rsquo;événements sont
concaténés dans le texte extrait des PDF ; l&amp;rsquo;analyseur a été réécrit pour reconnaître les
formes de codes effectivement émises.&lt;/li&gt;
&lt;li&gt;La mise en cache du classeur d&amp;rsquo;historique des pannes (1,49 Mo, 15 533 lignes) a réduit
la durée du processus de maintenance des équipements, sur un rejeu de rapports réels, de
&lt;strong&gt;161,1 s à 23,3 s&lt;/strong&gt; ; le nombre d&amp;rsquo;ouvertures du classeur est passé de 26 à 3.&lt;/li&gt;
&lt;li&gt;Première comparaison en parallèle sans écart avec la production : &lt;strong&gt;14 brouillons de
chaque côté, 14 en commun, 0 manquant, 0 en trop&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="stack-technique"&gt;&lt;a href="#stack-technique" class="header-anchor"&gt;&lt;/a&gt;Stack technique
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Python 3.11&lt;/code&gt; · &lt;code&gt;Microsoft Graph&lt;/code&gt; · &lt;code&gt;MSAL&lt;/code&gt; · &lt;code&gt;httpx&lt;/code&gt; · &lt;code&gt;Playwright&lt;/code&gt; · &lt;code&gt;pypdf&lt;/code&gt; ·
&lt;code&gt;openpyxl&lt;/code&gt; · &lt;code&gt;pydantic&lt;/code&gt; · &lt;code&gt;keyring&lt;/code&gt; · &lt;code&gt;pytest&lt;/code&gt; · &lt;code&gt;PyInstaller&lt;/code&gt;&lt;/p&gt;</description></item><item><title>De l'enregistrement multipiste à la timeline éditable : transcription, diarisation et export AAF</title><link>https://borisdupoux.com/fr/research/speech-to-edit/</link><pubDate>Mon, 01 Jun 2026 00:00:00 +0000</pubDate><guid>https://borisdupoux.com/fr/research/speech-to-edit/</guid><description>&lt;h2 id="contexte"&gt;&lt;a href="#contexte" class="header-anchor"&gt;&lt;/a&gt;Contexte
&lt;/h2&gt;&lt;p&gt;Une conférence enregistrée sur plusieurs microphones (intervenants, salle,
musique) produit des heures d&amp;rsquo;audio multipiste brut. L&amp;rsquo;essentiel du temps de
montage est consacré à repérer la matière — qui parle quand, sur quelle piste,
où se trouvent les silences — avant toute coupe.&lt;/p&gt;
&lt;p&gt;Les outils vocaux open source (Whisper et ses dérivés, pyannote, Silero VAD)
résolvent bien la transcription et la question « qui a parlé quand », mais
s&amp;rsquo;arrêtent au &lt;strong&gt;texte&lt;/strong&gt;. La question traitée ici est de savoir si cette analyse
peut être livrée sous la forme d&amp;rsquo;un &lt;strong&gt;objet de montage&lt;/strong&gt; : une timeline
multipiste au format AAF, le format d&amp;rsquo;échange que les stations audionumériques
ouvrent nativement, avec des pistes qualifiées et les temps morts supprimés.&lt;/p&gt;
&lt;h2 id="avant--après"&gt;&lt;a href="#avant--apr%c3%a8s" class="header-anchor"&gt;&lt;/a&gt;Avant → après
&lt;/h2&gt;&lt;p&gt;Un vrai projet traité de bout en bout : une conférence enregistrée sur
&lt;strong&gt;16 pistes&lt;/strong&gt;. Toute la colonne de droite est produite automatiquement, sans
aucune coupe manuelle ; les chiffres sont lus dans la base d&amp;rsquo;analyse du projet
et dans l&amp;rsquo;AAF livré.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;Entrée (enregistrement brut)&lt;/th&gt;
					&lt;th&gt;Sortie générée (timeline AAF)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Pistes&lt;/td&gt;
					&lt;td&gt;16 × 1 h 32 min 54 s (≈ 24 h 46 min d&amp;rsquo;audio à parcourir)&lt;/td&gt;
					&lt;td&gt;16 pistes conservées dans l&amp;rsquo;AAF, dont 6 portent des clips&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Rôles des pistes&lt;/td&gt;
					&lt;td&gt;4 parole · 2 musique (dont une détectée comme ambiance) · 10 silencieuses&lt;/td&gt;
					&lt;td&gt;10 pistes silencieuses laissées vides, sans tri manuel&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Matière&lt;/td&gt;
					&lt;td&gt;204 régions de parole et 5 passages musicaux détectés&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;41 clips&lt;/strong&gt; (37 parole, 4 musique)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Coupes&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;40 coupes franches&lt;/strong&gt; entre sources, dont 33 changements de micro&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Transitions&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
					&lt;td&gt;0 fondu enchaîné : coupes franches, chaque clip de parole prolongé de 0,3 s&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Temps morts&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
					&lt;td&gt;3 trous ≥ 4,5 s supprimés en ripple (36,9 s) ; timeline de 1 h 32 min 17 s&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img alt="Avant/après multipiste : les 16 pistes d’entrée colorées par rôle dans le temps, et le montage généré montrant la piste retenue pour chaque segment, avec les changements de source marqués" class="gallery-image" data-flex-basis="333px" data-flex-grow="138" height="1152" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://borisdupoux.com/fr/research/speech-to-edit/multitrack-before-after.fr.png" srcset="https://borisdupoux.com/fr/research/speech-to-edit/multitrack-before-after.fr_hu_ed7871e33c6cf34f.png 800w, https://borisdupoux.com/fr/research/speech-to-edit/multitrack-before-after.fr.png 1600w" width="1600"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;En haut : les 16 pistes d&amp;rsquo;entrée sur toute la durée de l&amp;rsquo;enregistrement,
colorées selon le rôle et l&amp;rsquo;activité détectés ; les quatre micros de parole
sont actifs presque en permanence, car chacun capte aussi ses voisins. En bas :
le montage généré, une ligne par piste comme dans l&amp;rsquo;AAF — une seule source est
retenue à chaque instant, les repères marquent les 40 changements de source,
les zones hachurées du panneau supérieur sont les temps morts supprimés.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Les clips de parole sont peu nombreux et longs (médiane 79 s, le plus long
10 min 11 s) : le montage suit l&amp;rsquo;intervenant actif au lieu de couper à chaque
respiration, et les micro-bascules de moins de 0,8 s sont absorbées par le
lissage anti-scintillement. Dix clips durent encore moins de 6 s — de brèves
interjections, premier endroit à vérifier lors de la relecture du montage.&lt;/p&gt;
&lt;h2 id="approche"&gt;&lt;a href="#approche" class="header-anchor"&gt;&lt;/a&gt;Approche
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 A["Enregistrement WAV multipiste"] --&gt; B["Décodage en mono 16 kHz + énergie RMS par 100 ms"]
 B --&gt; C["Détection d'activité vocale (Silero)"]
 C --&gt; D["Transcription (Whisper large-v3, par segments)"]
 D --&gt; E["Alignement forcé des mots (WhisperX / wav2vec2)"]
 C --&gt; F["Diarisation des locuteurs (pyannote)"]
 E --&gt; G["Attribution des locuteurs + post-traitement des segments"]
 F --&gt; G
 B --&gt; H["Rôle de la piste : parole / musique / silence"]
 G --&gt; H
 H --&gt; I["Montage : micro actif par trame de 100 ms"]
 I --&gt; J["Export AAF (lié aux noms WAV d'origine)"]
 I --&gt; K["Mixage mono WAV"]&lt;/pre&gt;&lt;h2 id="méthodes"&gt;&lt;a href="#m%c3%a9thodes" class="header-anchor"&gt;&lt;/a&gt;Méthodes
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Transcription.&lt;/strong&gt; Whisper &lt;code&gt;large-v3&lt;/code&gt; via faster-whisper (CTranslate2), exécuté
sur des segments de longueur fixe dans un processus de travail isolé et
persistant : le modèle n&amp;rsquo;est chargé qu&amp;rsquo;une fois, et un plantage natif ne fait
perdre qu&amp;rsquo;un segment, signalé plutôt qu&amp;rsquo;ignoré silencieusement. Le décodage
utilise une recherche en faisceau avec filtrage Silero VAD, et
&lt;code&gt;condition_on_previous_text&lt;/code&gt; est désactivé pour limiter les boucles de
répétition.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Horodatage des mots et locuteurs.&lt;/strong&gt; Les horodatages au niveau du mot
proviennent de l&amp;rsquo;alignement forcé de WhisperX (wav2vec2). Les tours de parole
proviennent de la diarisation pyannote et sont attribués aux mots par
recouvrement temporel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Post-traitement.&lt;/strong&gt; Des filtres purs suppriment les hallucinations connues de
Whisper (crédits de sous-titrage types, formules du genre « merci d&amp;rsquo;avoir
regardé », segments purement musicaux), les sorties en écriture non latine sur
de l&amp;rsquo;audio français et les doublons, puis fusionnent les segments consécutifs
d&amp;rsquo;un même locuteur.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Qualification des pistes et montage.&lt;/strong&gt; Chaque piste est classée en parole,
musique ou silence en combinant l&amp;rsquo;énergie par trame, la couverture VAD et des
statistiques de transcription. Le montage sélectionne, pour chaque trame de
100 ms, le microphone présentant le meilleur rapport signal sur bruit
au-dessus de son propre bruit de fond (ce qui réduit la diaphonie entre
micros), applique un lissage anti-papillotement et resserre la timeline en
supprimant les temps morts globaux.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Synchronisation.&lt;/strong&gt; Les enregistreurs distincts sont alignés en appariant des
mots caractéristiques entre transcriptions (médiane robuste des décalages),
après un placement grossier fondé sur l&amp;rsquo;horloge et corrigé de la dérive
d&amp;rsquo;horloge.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Export.&lt;/strong&gt; La liste de montage est écrite sous forme d&amp;rsquo;AAF multipiste avec
&lt;code&gt;pyaaf2&lt;/code&gt;, en intégrant l&amp;rsquo;audio ou en le référençant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Déploiement.&lt;/strong&gt; Un portail web reçoit les tâches et un nœud de calcul distinct
l&amp;rsquo;interroge en HTTPS. Les clients ne téléversent que des copies d&amp;rsquo;analyse
compressées accompagnées d&amp;rsquo;un manifeste de leurs fichiers d&amp;rsquo;origine ; l&amp;rsquo;AAF
livré référence les &lt;strong&gt;noms des WAV d&amp;rsquo;origine&lt;/strong&gt;, de sorte que le monteur
reconnecte localement les sources en pleine qualité et que les originaux ne
quittent jamais la machine du client.&lt;/p&gt;
&lt;h2 id="résultats"&gt;&lt;a href="#r%c3%a9sultats" class="header-anchor"&gt;&lt;/a&gt;Résultats
&lt;/h2&gt;&lt;p&gt;Banc d&amp;rsquo;essai (&lt;code&gt;bench_stt.py&lt;/code&gt;) sur de l&amp;rsquo;audio réel de conférence : fenêtres de
parole d&amp;rsquo;une minute sélectionnées par VAD sur quatre pistes de parole, Whisper
&lt;code&gt;large-v3&lt;/code&gt; dans tous les cas, ordinateur portable Apple Silicon, mesures à
chaud hors chargement du modèle. Aucune transcription de référence n&amp;rsquo;étant
disponible, les indicateurs de qualité sont des &lt;strong&gt;divergences par rapport à
une pseudo-référence&lt;/strong&gt; (sortie de WhisperX), et non des erreurs absolues.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Chaîne de traitement (CPU)&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Facteur temps réel&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;openai-whisper&lt;/td&gt;
					&lt;td style="text-align: right"&gt;0,43&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;WhisperX&lt;/td&gt;
					&lt;td style="text-align: right"&gt;0,51&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;WhisperX + alignement forcé&lt;/td&gt;
					&lt;td style="text-align: right"&gt;0,54&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;openai-whisper + horodatage des mots&lt;/td&gt;
					&lt;td style="text-align: right"&gt;0,57&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;faster-whisper (faisceau 5 + VAD)&lt;/td&gt;
					&lt;td style="text-align: right"&gt;0,66&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;faster-whisper + horodatage des mots&lt;/td&gt;
					&lt;td style="text-align: right"&gt;0,70&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Horodatage des mots :&lt;/strong&gt; les horodatages natifs s&amp;rsquo;écartent de l&amp;rsquo;alignement
forcé d&amp;rsquo;environ 205 ms en médiane (faster-whisper) et d&amp;rsquo;environ 210 ms
(openai-whisper) ; l&amp;rsquo;alignement forcé ajoute environ 1,2 s par minute
d&amp;rsquo;audio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Divergence des transcriptions :&lt;/strong&gt; 7 à 11 % de divergence au niveau du mot
entre chaînes, essentiellement de la ponctuation, de la segmentation et de la
normalisation après inspection manuelle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Répétitions :&lt;/strong&gt; 1 à 2 % de contenu répété ; aucun passage halluciné observé
sur ces extraits.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Détection des pistes :&lt;/strong&gt; les trois pistes de musique ou de silence de
l&amp;rsquo;enregistrement à sept pistes ont été identifiées par VAD et écartées
automatiquement.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="limites--suite"&gt;&lt;a href="#limites--suite" class="header-anchor"&gt;&lt;/a&gt;Limites &amp;amp; suite
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;La qualité est mesurée par rapport à une pseudo-référence ; un jeu de test
transcrit manuellement est nécessaire pour rapporter un véritable taux
d&amp;rsquo;erreur sur les mots et un taux d&amp;rsquo;erreur de diarisation.&lt;/li&gt;
&lt;li&gt;Les extraits du banc d&amp;rsquo;essai sont courts (une minute par piste) et ne
comportent qu&amp;rsquo;un seul locuteur par fenêtre : la précision de la diarisation
en cas de chevauchement de plusieurs locuteurs n&amp;rsquo;est donc pas encore
quantifiée.&lt;/li&gt;
&lt;li&gt;CTranslate2 n&amp;rsquo;a pas de backend Metal : sur Apple Silicon, faster-whisper et
WhisperX s&amp;rsquo;exécutent sur CPU, tandis que CUDA est utilisé lorsqu&amp;rsquo;il est
disponible. Le choix du matériel pour chaque étape a été mesuré plutôt que
présumé ; une comparaison sous CUDA reste à effectuer.&lt;/li&gt;
&lt;li&gt;L&amp;rsquo;export AAF cible l&amp;rsquo;import dans Logic Pro ; les autres logiciels de montage
n&amp;rsquo;ont pas été testés de manière systématique.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="stack-technique"&gt;&lt;a href="#stack-technique" class="header-anchor"&gt;&lt;/a&gt;Stack technique
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Python&lt;/code&gt; · &lt;code&gt;faster-whisper&lt;/code&gt; · &lt;code&gt;CTranslate2&lt;/code&gt; · &lt;code&gt;WhisperX&lt;/code&gt; · &lt;code&gt;pyannote.audio&lt;/code&gt; · &lt;code&gt;Silero VAD&lt;/code&gt; · &lt;code&gt;PyTorch (MPS / CUDA)&lt;/code&gt; · &lt;code&gt;ffmpeg&lt;/code&gt; · &lt;code&gt;pyaaf2&lt;/code&gt; · &lt;code&gt;SQLite&lt;/code&gt; · &lt;code&gt;PHP&lt;/code&gt; · &lt;code&gt;MySQL&lt;/code&gt;&lt;/p&gt;</description></item><item><title>Identification sur le terrain des véhicules concernés par un rappel</title><link>https://borisdupoux.com/fr/research/fleet-management/</link><pubDate>Mon, 01 Jun 2026 00:00:00 +0000</pubDate><guid>https://borisdupoux.com/fr/research/fleet-management/</guid><description>&lt;h2 id="contexte"&gt;&lt;a href="#contexte" class="header-anchor"&gt;&lt;/a&gt;Contexte
&lt;/h2&gt;&lt;p&gt;Les campagnes de rappel de sécurité supposent d&amp;rsquo;atteindre chaque véhicule concerné, mais
courriers et appels téléphoniques laissent une population résiduelle de propriétaires qui
ne répondent jamais. Dans les territoires insulaires et ultramarins, le problème est plus
aigu : la couverture réseau est inégale, la logistique de terrain s&amp;rsquo;étend sur plusieurs
territoires et le registre de qui conduit quoi est incomplet. La question pratique devient
alors une question de gestion de flotte : &lt;em&gt;quels véhicules du parc en circulation sont
encore concernés, où sont-ils aperçus, et où en est chacun d&amp;rsquo;eux sur le chemin de la
réparation ?&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="approche"&gt;&lt;a href="#approche" class="header-anchor"&gt;&lt;/a&gt;Approche
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 A["Liste de rappel&lt;br/&gt;(récupération planifiée du fichier ou CSV manuel)"] --&gt; B["Serveur central&lt;br/&gt;import · versionnage · décodage des noms de modèles"]
 B --&gt;|"diff incrémental"| C["Unités de terrain (ordinateurs portables dans les véhicules des agents)&lt;br/&gt;copie locale de la liste de rappel"]
 D["Flux caméra"] --&gt; E["Détection de plaques + OCR&lt;br/&gt;(sur l'appareil, hors ligne)"]
 E --&gt; F["Correction tenant compte du format&lt;br/&gt;+ confirmation par double lecture"]
 F --&gt; G{"Correspondance avec&lt;br/&gt;la liste de rappel locale ?"}
 G --&gt;|"non"| H["Écartée — rien n'est stocké"]
 G --&gt;|"oui"| I["Alerte à l'agent&lt;br/&gt;+ file d'attente locale"]
 I --&gt;|"synchronisation au retour du réseau"| B
 B --&gt; J["Tableau de bord&lt;br/&gt;unités · tournées · détections · incidents"]
 J --&gt; K["Processus de contact des propriétaires&lt;br/&gt;identifié → contacté → réparé"]&lt;/pre&gt;&lt;h2 id="méthodes"&gt;&lt;a href="#m%c3%a9thodes" class="header-anchor"&gt;&lt;/a&gt;Méthodes
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Ingestion et versionnage des données.&lt;/strong&gt; La liste de rappel est récupérée automatiquement
selon un calendrier ou importée manuellement. Chaque import est comparé au précédent à
l&amp;rsquo;aide d&amp;rsquo;identifiants stables, versionné par empreinte, et seul le delta est envoyé aux
unités de terrain. Les codes de type constructeur sont traduits en noms de modèles lisibles
lors de l&amp;rsquo;import.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Correspondance malgré le bruit de l&amp;rsquo;OCR.&lt;/strong&gt; La détection des plaques et la reconnaissance
des caractères s&amp;rsquo;exécutent sur l&amp;rsquo;ordinateur portable de terrain avec des modèles ONNX, sans
dépendance au réseau. Deux garde-fous réduisent les fausses correspondances : (i) une étape
de &lt;em&gt;correction positionnelle&lt;/em&gt; exploite la structure connue des formats de plaques
nationaux (actuel et ancien) pour lever les confusions classiques de l&amp;rsquo;OCR (O/0, I/1, S/5,
B/8, Z/2, G/6), en rejetant toute lecture ambiguë ; (ii) une &lt;em&gt;confirmation par double
lecture&lt;/em&gt; ne comptabilise une plaque que si elle est lue deux fois dans une courte fenêtre
de temps. Une page web pour smartphone offre une solution de secours manuelle (photo ou
saisie de la plaque, avec validation de la clé de contrôle du VIN).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Synchronisation pensée pour le hors-ligne.&lt;/strong&gt; Les correspondances sont mises en file d&amp;rsquo;attente
localement et transmises au serveur central à la connexion suivante, de sorte que les
détections survivent à des coupures de plusieurs jours. Les unités envoient un signal de
vie (version du logiciel, version de la liste de rappel, compteurs de lectures).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Opérations de terrain.&lt;/strong&gt; Les superviseurs planifient des tournées sous forme de points de
contrôle ordonnés et géolocalisés sur une carte, les attribuent aux équipes et aux unités,
réutilisent des modèles et suivent l&amp;rsquo;avancement. Les agents signalent les incidents avec un
niveau de gravité. Un processus de contact consigne le résultat de chaque démarche auprès
d&amp;rsquo;un propriétaire, et le tableau de bord en déduit un entonnoir de conversion (identifié →
approché → engagé → réparé) avec les tendances hebdomadaires, le délai médian jusqu&amp;rsquo;à la
réparation et une ventilation par territoire.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Protection des données dès la conception.&lt;/strong&gt; Les plaques qui ne figurent pas sur la liste
de rappel ne sont jamais stockées ni transmises ; les enregistrements conservés ont des
durées de conservation fixes avec purge automatique ; les actions sont consignées dans un
journal d&amp;rsquo;audit ; l&amp;rsquo;accès au tableau de bord repose sur des rôles, y compris des comptes en
lecture seule ; les données au repos sont chiffrées. Un registre des traitements a été
rédigé parallèlement au code.&lt;/p&gt;
&lt;h2 id="déploiement--exploitation-devops"&gt;&lt;a href="#d%c3%a9ploiement--exploitation-devops" class="header-anchor"&gt;&lt;/a&gt;Déploiement &amp;amp; exploitation (DevOps)
&lt;/h2&gt;&lt;p&gt;Le système tourne sur deux types de machines très différents : des ordinateurs
portables Windows dans les véhicules des agents, qui peuvent rester hors ligne
plusieurs jours, et un serveur par instance. Après les premières semaines de
terrain, l&amp;rsquo;essentiel du travail d&amp;rsquo;ingénierie a consisté à rendre les deux côtés
faciles à déployer, à superviser et à remettre en service, par une seule
personne, sans interrompre le travail de terrain.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 subgraph ADM["Poste d'administration"]
 GIT["Dépôt Git&lt;br/&gt;arbre commité uniquement"]
 PROV["Script de provisionnement&lt;br/&gt;un fichier de configuration par unité"]
 BLD["Build terrain&lt;br/&gt;paquet PyInstaller + modèles ONNX"]
 end
 subgraph SRV["Serveur (VPS Ubuntu LTS)"]
 CAD["Caddy&lt;br/&gt;TLS · HSTS · sans journal d'accès"]
 APP["Conteneur Docker&lt;br/&gt;API FastAPI + tableau de bord&lt;br/&gt;port en boucle locale uniquement"]
 VOL[("Volume LUKS2&lt;br/&gt;SQLite · photos · archives")]
 TIM["Timers systemd&lt;br/&gt;supervision · sauvegarde · nettoyage"]
 end
 FLD["Portables de terrain&lt;br/&gt;SQLite local · boucle de synchronisation"]
 OBJ[("Stockage objet externe&lt;br/&gt;sauvegardes chiffrées age")]
 CHAT["Alertes messagerie&lt;br/&gt;changements d'état + bilans quotidiens"]
 GIT --&gt;|"git archive via SSH → script de déploiement"| APP
 PROV --&gt;|"API d'administration"| APP
 PROV --&gt;|"fichier de configuration + LISEZMOI"| FLD
 BLD --&gt;|"archive + installateur en un clic"| FLD
 FLD &lt;--&gt;|"HTTPS : signal de vie · envois · diff des rappels"| CAD
 CAD --&gt; APP
 APP --- VOL
 TIM --&gt; VOL
 TIM --&gt;|"copie chiffrée"| OBJ
 TIM --&gt; CHAT&lt;/pre&gt;&lt;h3 id="portables-de-terrain--empaquetage-installation-et-mises-à-jour"&gt;&lt;a href="#portables-de-terrain--empaquetage-installation-et-mises-%c3%a0-jour" class="header-anchor"&gt;&lt;/a&gt;Portables de terrain : empaquetage, installation et mises à jour
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Empaquetage.&lt;/strong&gt; L&amp;rsquo;application de terrain (backend Python, interface React)
est figée avec PyInstaller en un exécutable Windows « un dossier » qui embarque
les modèles ONNX et la configuration de l&amp;rsquo;OCR : une unité n&amp;rsquo;a besoin ni d&amp;rsquo;une
installation Python ni du réseau pour lire les plaques. Des modes intégrés
&lt;code&gt;--diag&lt;/code&gt; et &lt;code&gt;--selftest&lt;/code&gt; vérifient l&amp;rsquo;environnement d&amp;rsquo;exécution embarqué et
lisent une image de test sans lancer l&amp;rsquo;interface, ce qui a permis de
diagnostiquer les échecs de démarrage directement sur la machine de terrain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Installateur en un clic.&lt;/strong&gt; Le build est livré sous forme d&amp;rsquo;archive avec un
&lt;code&gt;INSTALLER.bat&lt;/code&gt; à lancer d&amp;rsquo;un double-clic. Il s&amp;rsquo;élève lui-même en
administrateur et exécute un script PowerShell qui retire des fichiers la
marque « téléchargé depuis Internet » (sans quoi l&amp;rsquo;environnement embarqué ne
se charge pas), désactive la mise en veille USB pour que le dongle GPS ne soit
pas suspendu, et installe le fichier de configuration de l&amp;rsquo;unité là où
l&amp;rsquo;exécutable le lit. Le script refuse de deviner : si plusieurs fichiers de
configuration &lt;em&gt;différents&lt;/em&gt; sont présents, il les liste et demande lequel est
le nouveau. Une réinitialisation optionnelle efface la base locale lorsqu&amp;rsquo;une
unité change de serveur ; elle affiche le serveur cible avant de demander
confirmation et avertit que les détections non synchronisées seraient perdues.
Le jeton d&amp;rsquo;accès de l&amp;rsquo;unité n&amp;rsquo;est jamais affiché. Une mise à jour suit le même
chemin : nouveau build, nouvelle exécution de l&amp;rsquo;installateur.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Provisionnement.&lt;/strong&gt; Un script Python qui n&amp;rsquo;utilise que la bibliothèque
standard, et tourne donc sur un Windows nu, crée les unités à partir d&amp;rsquo;un
fichier CSV via l&amp;rsquo;API d&amp;rsquo;administration du serveur et génère, pour chacune, son
fichier de configuration (adresse du serveur, code de l&amp;rsquo;unité, nom affiché,
jeton) et un LISEZMOI à remettre avec le portable. La clé d&amp;rsquo;administration est
lue dans une variable d&amp;rsquo;environnement ou saisie de manière masquée, jamais
passée en argument de ligne de commande.&lt;/p&gt;
&lt;h3 id="versionnage--logiciel-et-liste-de-rappel"&gt;&lt;a href="#versionnage--logiciel-et-liste-de-rappel" class="header-anchor"&gt;&lt;/a&gt;Versionnage : logiciel et liste de rappel
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Version du logiciel.&lt;/strong&gt; Chaque unité indique la version de son application
dans son signal de vie. Le tableau de bord la compare à la version attendue et
signale les unités qui restent à mettre à jour.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version de la liste de rappel.&lt;/strong&gt; La liste de rappel est versionnée par
empreinte. À chaque cycle, une unité compare son empreinte locale à celle du
serveur : identique, rien à faire ; pas de copie locale, téléchargement
complet ; sinon, le serveur ne renvoie que les lignes ajoutées et retirées
depuis cette empreinte (et l&amp;rsquo;unité se rabat sur un téléchargement complet si
le serveur ne connaît plus sa version). La modification est appliquée en une
seule transaction, et l&amp;rsquo;empreinte locale n&amp;rsquo;avance que si tout a été appliqué.&lt;/p&gt;
&lt;p&gt;Cette dernière règle vient d&amp;rsquo;un incident de terrain. Une contrainte de la base
de données faisait échouer le diff sur une unité, l&amp;rsquo;erreur était absorbée par
la boucle de synchronisation, et l&amp;rsquo;unité continuait d&amp;rsquo;envoyer ses signaux de
vie alors que sa liste de rappel restait figée pendant plus de deux semaines :
elle alertait donc sur des véhicules déjà réparés. La version 1.4.1 a corrigé
la cause, ajouté un retour arrière explicite et livré une migration de schéma
qui conserve les données existantes. Elle a été testée par une série de 62
tests, qui reproduisait le bug et migrait une base à l&amp;rsquo;ancien schéma. La
supervision lève désormais une alerte « synchronisation des rappels figée »
lorsqu&amp;rsquo;une unité n&amp;rsquo;a pas reçu une liste publiée au-delà d&amp;rsquo;un délai de grâce.&lt;/p&gt;
&lt;h3 id="synchronisation-incrémentale-et-résiliente"&gt;&lt;a href="#synchronisation-incr%c3%a9mentale-et-r%c3%a9siliente" class="header-anchor"&gt;&lt;/a&gt;Synchronisation incrémentale et résiliente
&lt;/h3&gt;&lt;p&gt;Toutes les cinq minutes, ou à la demande, une unité exécute un cycle de
synchronisation composé d&amp;rsquo;étapes indépendantes : signal de vie, envoi des
détections (par lots), trace GPS, incidents, mises à jour des points de
contrôle, puis récupération de la liste de rappel et des tournées. Les envois
passent avant les récupérations, de sorte que les données de terrain partent
en premier. Une étape en échec ne bloque plus les autres : dans une version
antérieure, le cycle était monolithique, et un signal de vie en échec a un
jour retardé les détections de 22 minutes. Les requêtes utilisent des délais
d&amp;rsquo;attente courts, les téléchargements des délais plus longs. Le signal de vie
transmet la version du logiciel, la version de la liste de rappel, le nombre
de détections en attente et les compteurs de lectures, sur lesquels reposent
le tableau de bord et la supervision.&lt;/p&gt;
&lt;h3 id="déploiement-du-serveur"&gt;&lt;a href="#d%c3%a9ploiement-du-serveur" class="header-anchor"&gt;&lt;/a&gt;Déploiement du serveur
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Kit d&amp;rsquo;instance.&lt;/strong&gt; Chaque déploiement est une instance indépendante, décrite
par un court identifiant, sur un serveur virtuel Ubuntu LTS. Un script
d&amp;rsquo;amorçage, interactif et relançable sans risque, installe les paquets
système, Docker et Caddy depuis leurs dépôts officiels. Il active les mises à
jour de sécurité automatiques sans redémarrage automatique, restreint SSH à
l&amp;rsquo;authentification par clé, active un pare-feu en refus par défaut et une
protection contre la force brute, et vérifie que l&amp;rsquo;enregistrement DNS pointe
vers le serveur avant d&amp;rsquo;appliquer la configuration TLS. Les secrets résident
dans des fichiers de configuration réservés à root sur le serveur, jamais dans
le dépôt.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conteneur et proxy inverse.&lt;/strong&gt; Un Dockerfile multi-étapes construit le
tableau de bord React avec Node, puis exécute le serveur FastAPI sur une image
Python allégée, sous un utilisateur non privilégié. Son contrôle de santé
prévoit une période de démarrage de trois minutes, car les modèles de plaques
et d&amp;rsquo;OCR sont chargés avant que le serveur n&amp;rsquo;écoute. Le conteneur n&amp;rsquo;est publié
que sur l&amp;rsquo;interface de boucle locale. Caddy est le seul point d&amp;rsquo;entrée public :
certificat Let&amp;rsquo;s Encrypt automatique, HSTS explicite et aucun journal
d&amp;rsquo;accès, car les URL de scan contiennent des plaques. Le journal d&amp;rsquo;accès de
l&amp;rsquo;application elle-même est désactivé pour la même raison.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Déploiement et retour arrière.&lt;/strong&gt; Le code ne quitte le poste
d&amp;rsquo;administration que sous la forme d&amp;rsquo;un &lt;code&gt;git archive&lt;/code&gt; d&amp;rsquo;un arbre commité : les
modifications non commitées sont listées et ne partent pas, et les fins de
ligne sont forcées en LF. Sur le serveur, le nouvel arbre est extrait à côté
de l&amp;rsquo;actuel puis permuté, le précédent est conservé, et le commit déployé est
consigné. Le script de déploiement se détache ensuite de la session SSH : une
connexion coupée n&amp;rsquo;interrompt ni le déploiement ni un retour arrière. Un
verrou empêche les déploiements concurrents. Le script construit l&amp;rsquo;image
(étiquetée avec son commit), conserve l&amp;rsquo;image précédente comme &lt;code&gt;rollback&lt;/code&gt;,
recrée le conteneur et attend qu&amp;rsquo;il se déclare en bonne santé. Il abandonne
au bout de cinq minutes, ou dès que le conteneur a redémarré deux fois. En
cas d&amp;rsquo;échec, il revient automatiquement à l&amp;rsquo;image et à l&amp;rsquo;arbre source
précédents, met de côté le code en échec et envoie une alerte. Revenir à
n&amp;rsquo;importe quelle version antérieure revient à redéployer ce commit. Un retour
arrière d&amp;rsquo;image ne défait pas une migration de schéma : la procédure
documentée consiste donc à faire une sauvegarde avant tout déploiement qui
modifie le schéma.&lt;/p&gt;
&lt;h3 id="volume-chiffré-et-redémarrages-sûrs"&gt;&lt;a href="#volume-chiffr%c3%a9-et-red%c3%a9marrages-s%c3%bbrs" class="header-anchor"&gt;&lt;/a&gt;Volume chiffré et redémarrages sûrs
&lt;/h3&gt;&lt;p&gt;Les données du serveur (base, photos de contact, archives des listes de
rappel) résident dans un conteneur de fichier LUKS2 monté avec des options
restrictives. Sa phrase de passe n&amp;rsquo;est jamais stockée sur la machine : après
un redémarrage, une personne déverrouille le volume. Le conteneur redémarre de
lui-même, mais le serveur refuse de démarrer tant qu&amp;rsquo;un fichier sentinelle
&lt;em&gt;situé sur le volume chiffré&lt;/em&gt; est absent. Sans lui, un redémarrage aurait
laissé un dossier vide à la place du volume, et l&amp;rsquo;application y aurait créé
une base neuve, non chiffrée, et accepté les données de terrain. Cette
protection a été vérifiée dans les deux sens sur un conteneur jetable. Un
script distinct agrandit le volume pendant qu&amp;rsquo;il est monté.&lt;/p&gt;
&lt;h3 id="sauvegardes-et-reprise"&gt;&lt;a href="#sauvegardes-et-reprise" class="header-anchor"&gt;&lt;/a&gt;Sauvegardes et reprise
&lt;/h3&gt;&lt;p&gt;Un timer systemd quotidien prend un instantané cohérent de la base SQLite via
son API de sauvegarde, l&amp;rsquo;archive avec les photos et les archives de rappel, et
chiffre le résultat avec &lt;code&gt;age&lt;/code&gt; vers une clé publique. La clé privée n&amp;rsquo;est
affichée qu&amp;rsquo;une fois, à sa création, et conservée hors du serveur. Les
sauvegardes sont gardées localement et, en option, copiées vers un stockage
objet compatible S3 avec &lt;code&gt;rclone&lt;/code&gt;, configuré uniquement par variables
d&amp;rsquo;environnement. Un mode de &lt;em&gt;test&lt;/em&gt; de restauration déchiffre une sauvegarde
dans un dossier temporaire sur le volume chiffré, lance un contrôle
d&amp;rsquo;intégrité, compte les lignes par table, puis efface tout. Le mode
d&amp;rsquo;&lt;em&gt;application&lt;/em&gt; déplace l&amp;rsquo;état courant de côté au lieu de le supprimer. La
documentation comprend une reconstruction pas à pas après perte totale du
serveur. Le plan de continuité vise un délai de reprise de 4 heures et une
perte de données maximale de 24 heures.&lt;/p&gt;
&lt;h3 id="supervision"&gt;&lt;a href="#supervision" class="header-anchor"&gt;&lt;/a&gt;Supervision
&lt;/h3&gt;&lt;p&gt;Un chien de garde s&amp;rsquo;exécute toutes les 15 minutes et ne publie sur un webhook
de messagerie que lorsqu&amp;rsquo;un état change (problème, puis « rétabli »). Un bilan
plus complet est envoyé deux fois par jour, calé sur les horaires de terrain.
Il vérifie que le volume chiffré est monté, le point de santé de l&amp;rsquo;API (avec
plusieurs essais, car l&amp;rsquo;import quotidien peut le bloquer environ une minute),
l&amp;rsquo;état de santé du conteneur, la lisibilité de la base, l&amp;rsquo;occupation du disque
système et du volume chiffré, la taille du journal d&amp;rsquo;écriture anticipée de
SQLite, l&amp;rsquo;expiration du certificat TLS, l&amp;rsquo;âge et la taille de la dernière
sauvegarde, les unités silencieuses depuis plus de 48 heures (et non 24, car
les unités sont éteintes le week-end) et les unités dont la liste de rappel
est figée. Un guetteur distinct alerte lorsqu&amp;rsquo;aucun import de liste de rappel
n&amp;rsquo;a eu lieu depuis plus de 30 heures. Plusieurs de ces contrôles ont été
ajoutés après des incidents réels : le volume chiffré a un jour atteint 90 %
sans aucune alerte, car seul le disque système était surveillé. Un timer
hebdomadaire nettoie les caches de construction Docker et ne conserve que les
images courante et de &lt;code&gt;rollback&lt;/code&gt;. Un script dédié tronque le journal SQLite
avec une attente de verrou courte, afin que les écritures du terrain ne
soient jamais bloquées longtemps.&lt;/p&gt;
&lt;h3 id="résilience-hors-ligne"&gt;&lt;a href="#r%c3%a9silience-hors-ligne" class="header-anchor"&gt;&lt;/a&gt;Résilience hors ligne
&lt;/h3&gt;&lt;p&gt;Le côté terrain est conçu pour continuer à fonctionner pendant une panne du
serveur : lecture des plaques, correspondance avec la copie locale de la liste
de rappel, alertes, tournées déjà téléchargées et onglet de statistiques local
fonctionnent sans réseau, et la file d&amp;rsquo;attente se vide à la connexion
suivante. Seuls le tableau de bord (et la page pour
smartphone hébergée sur le serveur) sont indisponibles tant que le serveur
est arrêté.&lt;/p&gt;
&lt;h3 id="tests"&gt;&lt;a href="#tests" class="header-anchor"&gt;&lt;/a&gt;Tests
&lt;/h3&gt;&lt;p&gt;Le dépôt ne comporte pas de chaîne d&amp;rsquo;intégration continue. Les versions sont
testées avant leur livraison, et les commits consignent ces exécutions :
12/12 et 6/6 pour les étapes de correction des plaques et de confirmation,
62 tests pour la version 1.4.1. Les comportements qui ne se prêtent pas aux
tests unitaires, comme la protection du volume ou un déploiement réel, ont été
vérifiés sur des conteneurs jetables et sur le point de santé de la
production.&lt;/p&gt;
&lt;h2 id="résultats"&gt;&lt;a href="#r%c3%a9sultats" class="header-anchor"&gt;&lt;/a&gt;Résultats
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Lors des premières opérations de terrain, les agents ont signalé &lt;strong&gt;32 %&lt;/strong&gt; des détections
comme des erreurs de lecture sur environ une semaine. Cette mesure a motivé les étapes de
correction positionnelle et de confirmation par double lecture, et les statistiques du
tableau de bord excluent désormais les détections marquées comme faux positifs.&lt;/li&gt;
&lt;li&gt;Les nouvelles étapes ont été livrées avec des tests unitaires : &lt;strong&gt;12/12&lt;/strong&gt; pour la
correction des plaques et &lt;strong&gt;6/6&lt;/strong&gt; pour la confirmation, les réglages et le journal des
lectures.&lt;/li&gt;
&lt;li&gt;Après l&amp;rsquo;extension de la table de correspondance des noms de modèles, &lt;strong&gt;une seule&lt;/strong&gt; ligne
de la liste de rappel de production restait sans nom de modèle décodé.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Aucun taux de reconnaissance de bout en bout n&amp;rsquo;a encore été mesuré.&lt;/p&gt;
&lt;h2 id="limites--suite"&gt;&lt;a href="#limites--suite" class="header-anchor"&gt;&lt;/a&gt;Limites &amp;amp; suite
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;La précision de la reconnaissance n&amp;rsquo;a pas été mesurée sur un banc d&amp;rsquo;essai avec vérité
terrain. Les objectifs du cahier des charges (taux de lecture, latence) sont des
objectifs de conception, non des résultats.&lt;/li&gt;
&lt;li&gt;Aucune lecture de plaque à l&amp;rsquo;ancien format n&amp;rsquo;a été observée au départ. Un compteur dédié
suit désormais ces lectures de bout en bout, mais le correctif doit encore être vérifié
sur le terrain.&lt;/li&gt;
&lt;li&gt;Une fausse correspondance avec une liste volumineuse coûte toujours du temps aux agents.
L&amp;rsquo;étape suivante consiste à calibrer le seuil de confiance et la fenêtre de confirmation
sur des vidéos de terrain annotées.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="stack-technique"&gt;&lt;a href="#stack-technique" class="header-anchor"&gt;&lt;/a&gt;Stack technique
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Python&lt;/code&gt; · &lt;code&gt;FastAPI&lt;/code&gt; · &lt;code&gt;SQLite&lt;/code&gt; · &lt;code&gt;ONNX Runtime&lt;/code&gt; · &lt;code&gt;fast-alpr&lt;/code&gt; (détecteur de plaques YOLOv9 + OCR) · &lt;code&gt;OpenCV&lt;/code&gt; · &lt;code&gt;PaddleOCR&lt;/code&gt; · &lt;code&gt;React&lt;/code&gt; · &lt;code&gt;TypeScript&lt;/code&gt; · &lt;code&gt;Vite&lt;/code&gt; · &lt;code&gt;Leaflet&lt;/code&gt; · &lt;code&gt;Docker&lt;/code&gt; · &lt;code&gt;Caddy&lt;/code&gt; · &lt;code&gt;LUKS&lt;/code&gt; · &lt;code&gt;systemd&lt;/code&gt; · &lt;code&gt;age&lt;/code&gt; · &lt;code&gt;rclone&lt;/code&gt; · &lt;code&gt;PyInstaller&lt;/code&gt; · &lt;code&gt;PowerShell&lt;/code&gt;&lt;/p&gt;</description></item></channel></rss>