# Rapport de version — v1.3.0

*STE MOBDIE PRIVÉ — application de gestion — 28 juillet 2026*

## Ce que disait la recette

Trois reproches, tous fondés, tous vérifiés dans le code avant d'être traités : le parcours de domiciliation est très mauvais et devrait être très facile ; on ne trouve pas où déposer les CIN scannées et le certificat négatif pour l'analyse IA ; on ne trouve pas l'archive des documents. À ces trois reproches s'ajoute une découverte faite en ouvrant le code pour les instruire, et qui les précède toutes en gravité : l'analyse IA ne pouvait pas fonctionner, quelle que soit la clé renseignée. Ce rapport suit cet ordre, parce que c'est celui dans lequel les corrections ont dû être faites.

## L'analyse IA ne pouvait pas aboutir, et pour deux raisons plutôt qu'une

Le réglage `ia.modele` valait `claude-opus-4-8`, un identifiant de modèle qui n'existe pas. Il venait du seeder, il était figé dans le dump initial livré en production, il servait de valeur par défaut dans `config/services.php` et dans le `.env.example`. À chaque analyse, l'API répondait 404, le job attrapait l'exception, marquait l'analyse « echoue », et personne ne voyait jamais pourquoi. Corriger le `.env` en production n'aurait rien changé, puisque le réglage en base l'emporte sur la configuration.

En instrumentant l'appel pour vérifier la correction, une seconde cause de panne est apparue, qui n'était pas dans la recette et qui aurait suffi à elle seule à tout maintenir en échec. Les modèles de la génération 5 refusent tout paramètre d'échantillonnage dont la valeur n'est pas celle par défaut, et répondent 400 quand on leur en envoie un. Or le client envoyait systématiquement `temperature: 0`, lu depuis le réglage `ia.temperature`. Corriger le seul identifiant de modèle aurait donc remplacé un 404 par un 400, sans que rien ne fonctionne davantage. La température n'est désormais transmise qu'aux modèles qui l'acceptent encore, et le réglage reste utile pour ceux-là.

La liste des identifiants valables n'est plus écrite qu'à un seul endroit, `App\Support\Ia`, d'où la lisent le formulaire des réglages, la migration de correction, la commande de correction et le client HTTP. Elle contient `claude-opus-5`, `claude-sonnet-5` et `claude-haiku-4-5`, avec pour chacun un libellé lisible et une indication de coût relatif ; `claude-sonnet-5` est la valeur retenue par défaut, comme le meilleur rapport entre le prix et la capacité à lire un scan ordinaire. Le champ texte libre des réglages est devenu une liste fermée, une migration de données réécrit toute valeur hors liste, la commande `php artisan mobdie:corriger-modele-ia` fait le même travail à la demande, et l'enregistrement des réglages refuse en dernier ressort un identifiant inconnu même si le formulaire était contourné. Il n'est plus possible de remettre l'application dans l'état où elle était.

## L'échec était enregistré, mais invisible

Le champ `message_erreur` était bien renseigné à chaque échec, et n'était affiché nulle part : la seule trace était une notification fugace. La liste des analyses IA porte maintenant une colonne d'erreur et une ligne développable qui montre le message brut en entier, celui dont on a besoin pour diagnostiquer. Les messages techniques sont traduits en phrases qui disent quoi faire : un 404 devient « Le modèle d'IA configuré n'existe pas — corrigez-le dans Réglages, onglet IA », un 401 « La clé API a été refusée — vérifiez ANTHROPIC_API_KEY », un 429 « Quota d'API dépassé, réessayez plus tard », un dépassement de délai « La pièce est trop lourde ou l'API trop lente — réessayez avec un scan allégé ». Le message brut reste consultable à côté de sa traduction, jamais à la place. Chaque analyse échouée porte une action « Relancer l'analyse » qui redispatche le job sur les mêmes pièces, et retombe sur toutes les pièces analysables du client quand l'échec est survenu avant la première extraction. La fiche du client porte un bandeau rouge quand sa dernière analyse a échoué, avec la raison en clair.

## Rien ne prouvait que la file d'attente tournait

L'analyse est dispatchée en tâche de fond sur la connexion `database`. Si le cron `queue:work` n'est pas actif, l'utilisateur lit « Analyse lancée », l'analyse reste indéfiniment « en attente », et rien ne le lui dit jamais. C'est très probablement une partie de ce que la recette appelle « non complet ». Un témoin de vie a été installé : le worker écrit un battement dans le cache à chaque boucle, et un bandeau d'avertissement persistant apparaît en tête des écrans de domiciliation dès que ce battement date de plus de cinq minutes. Le bandeau ne se contente pas de constater la panne, il indique la commande de diagnostic à lancer et rappelle que l'analyse directe et la saisie manuelle restent disponibles. En connexion `sync`, où il n'y a pas de worker à surveiller, le témoin ne se déclenche pas.

Un mode direct sert de filet quand le cron est cassé. L'action d'analyse propose « Analyser maintenant, sans passer par la file », proposée cochée quand la file est arrêtée, qui exécute le job en synchrone sur une seule pièce avec un garde-fou de quarante-cinq secondes — la passerelle LiteSpeed coupant vers soixante. L'utilisateur ne se retrouve jamais bloqué sans recours.

Enfin, l'onglet IA des réglages porte un bouton « Tester la connexion IA » qui envoie une requête de dix jetons et affiche le résultat exact, modèle utilisé, statut HTTP et réponse brute en cas d'échec ; il teste le modèle affiché dans le formulaire, ce qui permet de vérifier un changement avant de l'enregistrer. La commande `php artisan mobdie:diagnostic` contrôle en une passe la clé API, la validité du modèle, la réponse de l'API, le battement de la file, l'écriture dans storage, l'espace disque, les gabarits de documents, la configuration mail et la base de données, affiche un tableau et sort en échec dès qu'un contrôle ne passe pas — ce qui la rend utilisable depuis un cron de supervision. Une option `--sans-api` permet de la lancer hors ligne.

## Le parcours de création : vingt-deux actions et neuf écrans

Le constat de la recette était exact et il s'expliquait par une raison très concrète. Le formulaire de création était un `CreateRecord` nu posé sur trente champs répartis en cinq onglets, sans le moindre champ de dépôt de fichier. Le seul champ de dépôt de toute l'arborescence Filament se trouvait dans le gestionnaire de relation des documents, lui-même accessible uniquement depuis la page « Modifier » d'un client déjà enregistré. Il fallait donc créer le client à vide, l'enregistrer, rouvrir en modification, descendre jusqu'à l'onglet des pièces, déposer un fichier, recommencer pour le second, remonter lancer l'analyse, quitter l'écran, attendre, aller vérifier ailleurs, revenir, créer le contrat, puis générer le document. Voilà pourquoi personne ne trouvait où déposer ses scans : le dépôt était derrière une porte qui ne s'ouvrait qu'après la création.

Un assistant en quatre étapes remplace ce parcours, à l'adresse `/admin/domiciliation/nouveau-dossier`, atteignable par un bouton primaire en tête de la liste des clients et par un bandeau en tête du tableau de bord. La première étape ne contient que du dépôt : deux zones larges côte à côte pour la CIN du gérant et le certificat négatif, une troisième plus discrète pour les autres pièces, le glisser-déposer, la sélection multiple, tous les formats, une miniature après dépôt, aucun champ texte et aucun sélecteur de type. Un seul bouton primaire, « Analyser et pré-remplir », et un lien secondaire « Je préfère saisir à la main » qui mène directement à l'étape de vérification sans lancer d'analyse et sans perdre les pièces déjà déposées.

La deuxième étape montre la lecture en cours, interroge l'analyse toutes les deux secondes, affiche une barre de progression fondée sur le nombre réel de pièces traitées, et détaille pièce par pièce ce qui a été lu. Elle n'enregistre rien et reste toujours contournable. La troisième présente les champs pré-remplis, chacun portant une pastille d'origine — « IA » en ambre avec le nom de la pièce d'où vient la valeur, « Saisi » en gris — et signale en rouge les champs exigés par l'article 544-1 de la loi 15-95 restés vides ; seuls les champs qui comptent sont visibles, le reste est replié sous « Compléments ». La quatrième porte le contrat et l'échéancier, avec un aperçu recalculé en direct qui annonce le nombre d'échéances, les montants hors taxes et toutes taxes comprises et les dates, en employant exactement le même calcul que le générateur d'échéances pour qu'aucune surprise n'apparaisse après la validation.

La validation finale écrit tout en une seule transaction : le client, les pièces rattachées comme dépôts, le contrat, les échéances, le contrat généré en Word et en PDF, et le devis si on l'a demandé. Les pièces déposées transitent par un dossier d'attente sur le disque et ne sont rattachées qu'au moment de la création : un abandon en cours de route ne laisse donc pas de client fantôme en base, ce qui était le principal risque de conception de cet écran. L'analyse elle-même est menée sans client, ce qui a demandé d'ajouter au modèle d'analyse la description des pièces en attente de rattachement.

Les trois scénarios de repli demandés par la recette ont été traités et sont couverts par des tests nommés. Sans clé API, aucune analyse n'est lancée, l'utilisateur est prévenu et passe à la saisie manuelle. Analyse en échec, la raison s'affiche en français et l'assistant continue. File d'attente arrêtée, l'assistant bascule de lui-même en mode direct plutôt que de laisser l'analyse en attente pour toujours. Dans les trois cas, le dossier se crée et les pièces déjà déposées sont conservées.

## Le dépôt de pièces se voit désormais partout

L'action d'analyse n'apparaissait qu'à un seul endroit de tout le projet, les actions d'en-tête de la page « Modifier ». Elle rejoint la fiche en consultation, accompagnée d'une action de dépôt multiple : on dépose ses cinq scans d'un coup, l'application devine le type de chacun d'après le nom du fichier — « cin », puis « certificat », « négatif » ou « CN », puis « contrat », « avenant », « résiliation », « statuts », « rc » — et présente un tableau récapitulatif où chaque type deviné reste modifiable avant enregistrement. Une pièce par fichier. La devinette est prudente : elle travaille sur un nom débarrassé de ses accents, teste ses motifs du plus spécifique au plus général, et pose des limites de mot pour ne pas prendre le « cn » de « cnss » pour un certificat négatif — cas qui fait l'objet d'un test à part.

L'onglet des pièces s'appelle maintenant « Pièces du dossier », porte une pastille de comptage rouge quand il est vide, expose le dépôt et l'analyse en actions d'en-tête, propose par ligne « Analyser cette pièce » et un aperçu en fenêtre modale — une balise `iframe` pour les PDF, une image pour les scans photo, un renvoi au téléchargement pour les formats qu'aucun navigateur n'affiche. Son état vide est rédigé et contient le bouton de dépôt. Les documents générés et les pièces déposées y sont regroupés par origine, si bien que le contrat généré ne se perd plus au milieu des scans.

En tête de la fiche, un bandeau « Pièces obligatoires » montre trois voyants — CIN, certificat négatif, contrat signé — dont chaque voyant rouge est un bouton qui ouvre la fenêtre de dépôt sur le bon type. Un second bandeau annonce qu'une analyse est terminée et combien de champs attendent, avec le bouton qui ouvre directement la vérification ; l'écran de vérification « Vérifier & appliquer », qui n'était atteignable que depuis la liste des analyses, a été extrait en action partagée et sert aux deux endroits. Sur la liste des clients, une colonne du nombre de pièces, une action de dépôt par ligne, et trois filtres : sans CIN, sans certificat négatif, sans contrat signé.

## L'archive des documents n'existait pas

L'utilisateur ne la trouvait pas pour la meilleure des raisons : elle n'existait pas. Le groupe de navigation « Documents » ne contenait que les gabarits Word et l'archive comptable brute, et les pièces des clients n'étaient consultables qu'une par une, client par client, en passant par la page de modification. Aucun écran ne les listait à l'échelle de l'entreprise.

La ressource a été créée. Le tableau montre une miniature pour les images et une icône de format pour le reste, le client cliquable vers sa fiche, le type et l'origine en pastilles, le libellé, le nom du fichier tronqué avec le nom complet en infobulle, les deux dates et la taille formatée en kilo-octets ou méga-octets ; tri par date de dépôt décroissante, vingt-cinq lignes par page avec les choix habituels. La recherche porte sur le nom du fichier, le libellé, le numéro, et, par la relation, sur la raison sociale du client, son ICE et son code. Les filtres couvrent le client, le type, l'origine, l'année, un intervalle de dates, les pièces sans date et les pièces non rattachées à un contrat ; ce dernier filtre a demandé une attention particulière, car son « ou » devait rester enfermé dans sa propre parenthèse sous peine d'annuler les autres filtres actifs. Trois regroupements sont proposés, par client, par type et par année.

Les actions de ligne offrent l'aperçu en modale, le téléchargement, le reclassement — type, libellé et date seulement, jamais le fichier, pour que l'archive reste fidèle à ce qui a été reçu — et la suppression sous permission. Les actions groupées permettent de télécharger la sélection en ZIP diffusé en flux, nommé `archive-mobdie-{date}.zip` et organisé en un dossier par client avec gestion des doublons de noms, de changer le type en lot et de supprimer. Les actions d'en-tête ajoutent le dépôt avec choix du client et l'export de l'index en Excel. Un bandeau de compteurs surmonte le tout : nombre de pièces, poids total, et les trois populations de clients auxquelles il manque une pièce obligatoire, chaque chiffre ouvrant la liste filtrée correspondante.

Les permissions `documents.voir` et `documents.gerer` ont été ajoutées au référentiel des rôles, d'où le seeder et la matrice les tirent automatiquement. Une migration les crée et les accorde aux rôles existants ; sans elle, l'écran serait resté invisible pour tout le monde après le déploiement, le seeder ne tournant pas en production.

La commande `php artisan mobdie:indexer-archive` réconcilie le disque et la base. Elle parcourt les dossiers `client-documents` et `archive-source` du disque local — dont la racine est `storage/app/private` depuis Laravel 11, ce qui méritait d'être écrit noir sur blanc — rattache chaque fichier à son client par le code porté par le chemin, ignore les pièces en transit d'un assistant en cours, crée les enregistrements manquants, signale à l'inverse ceux dont le fichier a disparu, et affiche un rapport chiffré. Une option `--simulation` permet de la lancer sans rien écrire.

Exécutée sur la base de développement, elle a trouvé quatre fichiers présents sur le disque et absents de la base : un contrat en Word et en PDF pour le client 2024-25, une attestation pour le client 2024-02, un acte de résiliation pour le client 2026-01. Elle a créé les quatre enregistrements, n'a rencontré aucun fichier sans client identifiable, et n'a signalé aucun enregistrement orphelin. Une seconde passe a reparcouru les mêmes quatre fichiers, les a tous reconnus comme déjà indexés et n'a rien créé.

## Moins de clics

Un clic sur une ligne de la liste des clients ouvre la fiche et non le formulaire de modification, ce qui correspond à l'usage réel : on consulte bien plus souvent qu'on ne modifie. La fiche s'organise en onglets nommés, Résumé puis Pièces du dossier, Contrats, Échéancier et Facturation, ce dernier étant un gestionnaire de relation nouveau — les factures d'un domicilié obligeaient jusqu'ici à quitter sa fiche pour l'écran des factures.

Le tri par défaut de la liste des clients change de second critère. Il classait par montant impayé décroissant, puis par prochaine échéance croissante, et ce second critère remontait en tête les sociétés du groupe domiciliées gratuitement, dont l'échéance est toujours proche et dont on n'a précisément rien à faire. Il classe désormais par montant impayé décroissant puis par date de dernière modification.

Sur la persistance des filtres, de la recherche et du tri, la recette rappelle un reproche de la v1.1 « resté sans réponse ». Il ne l'était plus tout à fait : la v1.2 avait posé cette persistance globalement dans `AppServiceProvider`, pour toutes les tables du panneau. Ce qui manquait, c'était la preuve. Un test la vérifie maintenant explicitement sur six listes, dont la nouvelle archive, et échouera si quelqu'un défait ce réglage.

## Ce qui a été trouvé de cassé en chemin

Outre la température rejetée par les modèles de génération 5, dont il a été question plus haut et qui est de loin la plus grave de ces découvertes, quatre défauts ont été trouvés en écrivant les tests plutôt qu'en lisant le code.

Les clés sous lesquelles l'assistant range les pièces en transit se chevauchaient entre les trois zones de dépôt : chaque zone numérotant ses fichiers à partir de zéro, la CIN et le certificat négatif se retrouvaient sous la même clé et le second écrasait le premier. Une seule pièce sur deux parvenait au client créé. Les clés sont maintenant préfixées par la zone d'origine.

Les deux bandeaux de la fiche client émettaient deux éléments racine — le widget et la fenêtre modale des actions en frères — ce que Livewire refuse. Le défaut était masqué tant que les bandeaux étaient rendus en différé ; il faisait tomber la fiche entière dès qu'ils passaient en rendu immédiat, ce qui était le comportement souhaitable pour un rappel permanent. La fenêtre modale a été replacée à l'intérieur du widget.

Le rendu différé, justement, est le troisième point. Filament rend les widgets en différé par défaut. Un rappel permanent qui apparaît une seconde après le reste de la page n'est plus un rappel, c'est un sursaut : les quatre bandeaux de cette version sont explicitement en rendu immédiat. Ils sont également exclus de la découverte automatique des widgets, faute de quoi ils seraient apparus sur le tableau de bord, hors de leur contexte.

L'analyseur de pièces, lui, renvoyait un tableau vide quand le fichier d'une pièce était introuvable sur le disque, ce qu'un job interprétait comme une analyse réussie sans aucun champ extrait. Il lève désormais une exception nommée, qui remonte comme un échec et s'affiche comme tel.

Enfin, le drapeau qui fait sauter l'analyse quand l'utilisateur choisit « Je préfère saisir à la main » restait levé pour toute la durée de l'assistant. Revenir à la première étape et cliquer « Analyser et pré-remplir » n'aurait alors plus jamais lancé d'analyse, sans qu'aucun message ne l'explique. Il ne vaut plus que pour un passage.

## L'état de la recette

Le dépôt compte cent quatre-vingt-quatre tests pour sept cent quatre-vingt-dix-sept assertions, tous au vert, contre cent vingt tests et cinq cent quarante-trois assertions avant ce chantier. Les soixante-quatre tests ajoutés couvrent nommément ce que la recette exigeait : l'assistant crée en une seule validation un client, ses pièces, son contrat, ses échéances et son contrat généré ; il va jusqu'au bout sans clé API, avec une analyse en échec, et avec une file d'attente arrêtée ; le dépôt multiple crée bien une pièce par fichier avec le bon type deviné ; l'archive se charge, se filtre sur chacun de ses filtres, et son export ZIP est relu depuis l'archive produite pour vérifier que les dossiers par client et le contenu des fichiers sont bien ceux attendus ; le réglage du modèle d'IA ne peut plus prendre une valeur hors liste ; et le diagnostic renvoie un code de sortie non nul quand un contrôle échoue. S'y ajoute une passe qui vérifie que chacun des dix écrans modifiés s'affiche réellement, avec ses bandeaux et ses libellés, et non seulement que sa route répond — précaution directement inspirée de la recette v1.1, où des classes de mise en forme avaient été purgées à la compilation. Les feuilles de style ont été recompilées et les classes employées par les nouveaux gabarits ont été vérifiées présentes dans le thème produit.

## Ce qu'il reste à faire

Deux choses relèvent de l'exploitation et non du code, et conditionnent le fonctionnement de l'analyse IA en production. La clé `ANTHROPIC_API_KEY` doit être renseignée dans le `.env` du serveur : le diagnostic la signale absente aujourd'hui, y compris en développement. Le cron `queue:work` doit être actif : le diagnostic signale qu'aucun battement n'a jamais été enregistré, ce qui confirme le soupçon de la recette. Tant qu'il ne tourne pas, le bandeau d'avertissement s'affichera sur les écrans de domiciliation et l'analyse basculera d'elle-même en mode direct, ce qui est un filet et non une solution. La marche à suivre est dans `DEPLOIEMENT.md`.

Trois limites méritent d'être connues plutôt que découvertes. Le mode direct n'analyse qu'une pièce à la fois, par construction : c'est le prix du garde-fou de quarante-cinq secondes imposé par la passerelle. L'export ZIP de l'archive construit le fichier dans un dossier temporaire avant de le diffuser, ce qui convient à une sélection ordinaire mais demanderait un vrai flux si l'on voulait un jour exporter plusieurs milliers de pièces d'un coup. Enfin, la devinette du type d'après le nom du fichier reste une aide et non une garantie : elle propose, l'utilisateur dispose, et le tableau récapitulatif est là pour cela.

Deux chantiers, enfin, n'étaient pas demandés et n'ont pas été entrepris. La ressource d'archive ne propose pas encore de prévisualisation des documents Word, faute d'un convertisseur côté serveur ; ces fichiers renvoient au téléchargement. Et l'assistant ne sait pas reprendre un dossier laissé en cours : les pièces déposées puis abandonnées restent dans le dossier de transit du disque, sans être rattachées ni supprimées. Une commande de nettoyage périodique de ce dossier serait le complément naturel de la commande d'indexation.
