mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
useLocations() mobile ; écran Sites (premier niveau) ; fiche site (identité, zones, ascenseurs du site — consultation seule, D4, pas de carte ni d'édition sur mobile pour l'instant) ; écran Ascenseurs (parc complet déjà préchargé, D1) ouvrant la fiche appareil R4 telle quelle (déjà générique, aucune modification nécessaire). Menu : Ascenseurs/Sites routent réellement au lieu de « à venir » ; Catégories reste à venir (admin, hors périmètre R6.2). Exécution directe de la maquette R6 déjà validée, patrons visuels réutilisés (Carte/LigneInfo/EnteteFiche) — pas de nouveau tour de design. Typecheck propre, 17 tests Jest, lint 5/5 paquets. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
954 lines
88 KiB
Markdown
954 lines
88 KiB
Markdown
# Journal de bord — SIOP V2
|
||
|
||
Trace chronologique des sessions (la plus récente en premier). Le **playbook** (`docs/0X-*`) est le livre ; ici, c'est le quotidien.
|
||
|
||
---
|
||
|
||
## 2026-08-02 — Pr. Daaif (+ Claude) — R6.2 : Parc sur mobile (Sites, Ascenseurs)
|
||
|
||
**Actions**
|
||
|
||
- `useLocations()` mobile (`GET /locations`, même liste plate que le web — le client construit
|
||
l'arbre site → zone).
|
||
- Écran **Sites** (`app/sites/index.tsx`) : sites de premier niveau (`parentId === null`), nom,
|
||
ville/adresse, nombre d'appareils.
|
||
- **Fiche site** (`app/sites/[id].tsx`) : identité (adresse, gardien, client/syndic), zones avec
|
||
leurs appareils, liste des ascenseurs du site — même filtre `siteName` que `fiche-site.tsx`
|
||
(web). Pas de carte interactive ni d'édition sur mobile (D4 — consultation seule pour cette
|
||
famille, réservé au web pour l'instant).
|
||
- Écran **Ascenseurs** (`app/ascenseurs/index.tsx`) : le parc complet, déjà préchargé (D1 lecture
|
||
locale, même donnée que le Scanner), trié par référence. Ouvre la fiche appareil R4 telle
|
||
quelle (`ascenseur/[id].tsx`, déjà générique — aucune modification nécessaire, elle dérive déjà
|
||
ce qu'elle affiche du rôle appelant côté serveur).
|
||
- Menu : « Ascenseurs » et « Sites » routent maintenant vers ces écrans réels (retirés de la
|
||
liste « à venir ») ; « Catégories » reste à venir (admin, hors périmètre R6.2).
|
||
- Vérifié : typecheck propre, 17 tests Jest, lint 5/5 paquets.
|
||
|
||
**Décisions**
|
||
|
||
- Aucune nouvelle — exécution directe de la maquette R6 déjà validée (Parc = D4, consultation
|
||
seule) ; pas de nouveau tour de maquette pour cette sous-étape, les patrons visuels réutilisés
|
||
(Carte/LigneInfo/EnteteFiche) sont déjà validés depuis R4.
|
||
|
||
**Prochaine étape** : R6.3 (Ressources — Stock, Tiers, Fichiers) ; confirmation du référent sur
|
||
iPhone pour R6.1 et le correctif déconnexion, toujours en attente.
|
||
|
||
---
|
||
|
||
## 2026-08-02 — Pr. Daaif (+ Claude) — R6 lancée : mobile ouvert à tous les rôles
|
||
|
||
**Contexte** — deux retours du référent en test réel sur iPhone : (1) une fois authentifié,
|
||
impossible de revenir en arrière ou de se déconnecter ; (2) l'app mobile devrait être accessible
|
||
à tous les rôles, pas seulement au Technicien, avec les mêmes actions que le web. Le premier est
|
||
un bug, corrigé dans la foulée ; le second est un changement de fond — R4 avait délibérément
|
||
fermé le périmètre au Technicien (décision actée dans ses maquettes). Ouvrir à tous les rôles
|
||
veut dire porter sur mobile une bonne partie de ce que fait le web (référentiel, ressources,
|
||
personnes, statistiques…) : plus gros que R4 dans son ensemble, donc une **nouvelle release,
|
||
R6** (R4 est close depuis le 17/07, R4.1/R4.2/R4.3 déjà pris par le socle mobile technicien
|
||
d'origine — pas question de les réutiliser et de brouiller l'historique).
|
||
|
||
**Actions**
|
||
|
||
- **Correctif déconnexion** : `EnteteTabs` (`composants/ui.tsx`) factorise une entête commune aux
|
||
4 onglets terrain — badge de compte + confirmation `Alert` au tap simple (l'ancien geste,
|
||
`onLongPress` sur « Ma journée » seulement, n'était pas découvrable). Le rôle affiché vient du
|
||
compte connecté, plus de libellé « Technicien » figé.
|
||
- **Maquette R6** (`docs/02-design/maquettes/maquette-mobile-tous-roles.html`, 7 écrans) rédigée
|
||
et validée par le référent le jour même : synthèse par rôle, Accueil adaptatif, Menu groupé
|
||
filtré par la matrice, OT côté Dispatcher, Demandes (création + approbation), Stock, Personnes
|
||
& statistiques. Cinq décisions actées : D1 barre d'onglets adaptative (Technicien/Technicien
|
||
limité inchangés + onglet Menu ajouté) ; D2 le Menu reprend à l'identique les 4 groupes du web
|
||
(Exploitation/Parc/Ressources/Pilotage), même matrice, aucune règle de droit nouvelle ; D3 un
|
||
groupe sans lien visible est masqué en entier (contrairement au web) ; D4 le mobile porte les
|
||
actions courantes par famille, pas les flux de gestion les plus denses (réservés au web pour
|
||
l'instant) ; D5 aucune logique de permission propre au mobile — un hook `usePermissions()`
|
||
calqué sur celui du web.
|
||
- **R6.1 — socle** : `usePermissions()` mobile (lit `me.permissions`, déjà renvoyé par
|
||
`/users/me`) ; barre d'onglets adaptative (`ongletsVisibles` par rôle, `href: null` pour masquer
|
||
sans retirer du navigateur — le Menu peut toujours y pousser) ; écrans Accueil (dashboard réel
|
||
pour Administrateur/Gestionnaire/Dispatcher/Vue seule — aucun chiffre inventé, seuls
|
||
WORK_ORDERS/REQUESTS sont câblés), OT (liste complète, `viewOther` déjà géré serveur, réutilise
|
||
la fiche OT R4 telle quelle — elle dérive déjà ses actions de `allowedTransitions`), Menu
|
||
(groupes filtrés, groupe vide masqué), Demandes (`PanneauDemandes`, un seul composant pour tous
|
||
les rôles : création + suivi pour le Demandeur, approbation/rejet à motif pour
|
||
Gestionnaire/Dispatcher/Administrateur, lecture seule pour Vue seule — même patron que le web,
|
||
découverte au passage : `GET /assets/options` déjà ouvert à tout rôle authentifié, réutilisé
|
||
tel quel). Familles pas encore portées (Sites, Ascenseurs, Stock, Tiers, Fichiers, Statistiques,
|
||
Assistant, Personnes) : écran « à venir » honnête (`composants/a-venir.tsx`, déjà présent mais
|
||
jamais câblé jusqu'ici) plutôt qu'un lien mort — chacune mérite son propre passage, sur le
|
||
modèle R4.1→R4.3.
|
||
- Vérifié : typecheck propre, 17 tests Jest verts, lint 5/5 paquets. Pas de vérification visuelle
|
||
en navigateur de mon côté (aucun outil de ce type dans cet environnement) — à confirmer par le
|
||
référent sur l'iPhone déjà connecté au serveur Metro.
|
||
|
||
**Décisions**
|
||
|
||
- Nouvelle release **R6** (pas R4.1/4.2/4.3, déjà pris ; pas de réouverture de R4, clos) pour tout
|
||
le chantier « mobile ouvert à tous les rôles ». Sous-étapes à venir : R6.2 (Parc — Sites,
|
||
Ascenseurs), R6.3 (Ressources — Stock, Tiers, Fichiers), R6.4 (Pilotage — Statistiques,
|
||
Personnes, Assistant), sur le modèle des sous-releases R4.1→R4.3.
|
||
|
||
**Prochaine étape** : R6.2 (Parc sur mobile) ; confirmation du référent sur iPhone (nav adaptative
|
||
et correctif déconnexion) ; restes non bloquants inchangés (recette Android physique,
|
||
redéploiement Dokploy, calibrage `AI_SEUIL_*`/darija, `DOKPLOY_WEBHOOK_URL`).
|
||
|
||
---
|
||
|
||
## 2026-08-01 — Pr. Daaif (+ Claude) — Dictée validée sur iPhone physique (ADR-004 §5)
|
||
|
||
**Actions**
|
||
|
||
- Reprise du reste laissé le 22/07 : le test tactile de la dictée sur iPhone, alors bloqué par une connexion
|
||
USB qui ne se rétablissait pas. Diagnostic cette fois : `system_profiler` (l'outil utilisé pour vérifier la
|
||
détection USB) était lui-même en panne et renvoyait un faux négatif silencieux — `ioreg` a révélé que
|
||
l'iPhone était en réalité détecté. Le vrai câble/port n'a jamais été le problème.
|
||
- Une fois l'app reconstruite (certificat gratuit expiré après plus d'une semaine — régénéré via le pipeline
|
||
officiel `expo run:ios`, jamais par un contournement manuel), **deux bugs réels trouvés et corrigés** :
|
||
1. `expo-audio` sur iOS refuse d'enregistrer sans un appel explicite à `setAudioModeAsync({ allowsRecording:
|
||
true })` avant `recorder.record()` — absent du code initial, jamais détecté hors d'un vrai appareil.
|
||
2. Le correctif des modules Expo/RN précompilés (ADR-005, `EXPO_USE_PRECOMPILED_MODULES=0` /
|
||
`RCT_USE_PREBUILT_RNCORE=0`) n'avait **jamais été committé** dans `apps/mobile/.env`, contrairement au
|
||
correctif FormData voisin déjà permanent — un vrai trou qui aurait refait planter l'app à la prochaine
|
||
reconstruction depuis zéro. Corrigé : les deux variables vivent maintenant côte à côte dans `.env`.
|
||
- **Recette réelle bout en bout** : micro → dictée d'une phrase → transcription fidèle (`faster-whisper` réel)
|
||
→ relecture → sauvegarde dans le bilan → clôture de l'OT → réindexation du corpus → **le contenu dicté
|
||
retrouvé par la recherche sémantique** (score 0,476). La boucle complète de l'idée d'origine (« cette
|
||
transcription devrait alimenter le corpus ») est validée sur vrai matériel, vraie voix, vrai réseau.
|
||
- Infra locale relancée depuis zéro après une semaine d'inactivité (Docker, conteneurs `siop2-*`, API,
|
||
service IA) — occasion de vérifier que le redémarrage à froid fonctionne proprement.
|
||
|
||
**Décisions**
|
||
|
||
- Aucune — cette session referme un reste déjà décidé, sans nouvelle décision de conception.
|
||
|
||
**Prochaine étape** : recette Android sur appareil physique (toujours en attente d'un appareil) ; redéploiement
|
||
Dokploy (`AI_SERVICE_TOKEN`) ; calibrage `AI_SEUIL_*` et qualité darija sur corpus SPELEV réel ; secret
|
||
`DOKPLOY_WEBHOOK_URL`.
|
||
|
||
---
|
||
|
||
## 2026-07-22 — Pr. Daaif (+ Claude) — Dictée implémentée : audio → transcription locale → corpus (ADR-004 §5)
|
||
|
||
**Actions**
|
||
|
||
- Suite du feu vert « on passe à l'audio » (maquette Voix amendée le 22/07) : implémentation complète de la
|
||
dictée (R5 D5) — écran maquetté jamais construit jusqu'ici, ni open source ni local en LLM externe, choix du
|
||
référent réaffirmé (« toujours pour l'open source et le local »).
|
||
- **`apps/ai`** : `faster-whisper` (CTranslate2, CPU, MIT), `AI_TRANSCRIPTION=off|locale|deterministe` (défaut
|
||
off), `AI_TRANSCRIPTION_MODEL` (défaut `small`) ; endpoint `/internal/transcrire` — l'audio ne survit
|
||
JAMAIS à l'appel (fichier temporaire supprimé quoi qu'il arrive, succès ou erreur) ; `indexer_bilans` inclut
|
||
désormais `InterventionReport.note` (anonymisée par le même pipeline D4) — le champ existait en base et au
|
||
contrat depuis R2 mais **n'avait jamais eu d'écran** ; 29 pytest (dont le transcripteur déterministe pour CI).
|
||
- **Contrat** (77 opérations) : `POST /assistant/transcribe` (multipart, `TranscriptionResult`).
|
||
- **API NestJS** : proxy multipart vers `siop2-ai` (`AssistantService.transcribe`), `WORK_ORDERS.edit` — même
|
||
droit que la saisie du bilan qu'elle alimente ; 2 tests e2e ajoutés (80 tests API au total).
|
||
- **Mobile** : `expo-audio` (enregistrement) + `expo-file-system` (purge locale) ; carte « Décrire pour
|
||
suggérer » de l'écran de clôture gagne un bouton dicter/terminer, une confirmation de purge, et
|
||
« Joindre la description à l'OT » (sauvegarde dans `note` via la file existante — corrige au passage un bug
|
||
latent : `enfilerBilan` ignorait silencieusement toute mise à jour de `note`).
|
||
- **Docker** : `siop2-ai` embarque désormais aussi le modèle Whisper au build (image 1,54 Go → 2,19 Go) —
|
||
construit et vérifié réellement (transcription en conteneur, non-root, 0 téléchargement au démarrage).
|
||
- **Vérification réelle** (voix de synthèse macOS `say`, français) : transcription fidèle en direct
|
||
(`TranscripteurLocal`), bout en bout via l'API NestJS, et dans le conteneur Docker construit — puis chaîne
|
||
complète corpus confirmée : note sauvegardée → OT clôturé → réindexation → contenu retrouvé par recherche
|
||
sémantique avec un bon score de pertinence.
|
||
- **Nettoyage** : la base de dev locale, polluée par les tests manuels de la recette terrain (deux OT seedés
|
||
clôturés en dehors de leur état d'origine), a été réinitialisée avec l'accord explicite du référent
|
||
(`prisma migrate reset --force`, bloqué par défaut pour un agent IA — consentement demandé et obtenu avant
|
||
exécution).
|
||
|
||
**Décisions**
|
||
|
||
- Pas de prototype de mesure français/darija avant l'implémentation (confirmé une seconde fois) — seuls des
|
||
contrôles d'ingénierie de base (le code tourne, avec de la vraie parole) ont été faits, pas un calibrage.
|
||
- **Reste** : le test tactile sur iPhone physique (bouton dicter, permission micro) n'a pas pu se jouer —
|
||
blocage USB persistant malgré câble/port/redémarrage/mode développeur essayés à plusieurs reprises. Reporté
|
||
comme la recette Android, sur décision du référent — ne bloque pas la suite.
|
||
|
||
**Prochaine étape** : test tactile de la dictée sur iPhone dès que la connexion USB fonctionnera ; recette
|
||
Android sur appareil physique ; redéploiement Dokploy (`AI_SERVICE_TOKEN`) ; calibrage `AI_SEUIL_*` et qualité
|
||
darija sur corpus SPELEV réel.
|
||
|
||
---
|
||
|
||
## 2026-07-22 — Pr. Daaif (+ Claude) — Idée backlog : transcription audio → corpus (maquette amendée, pas codée)
|
||
|
||
**Actions**
|
||
|
||
- Discussion de conception (aucun code) : étendre l'écran « Voix » de R5 (dictée, jamais construite — option
|
||
« si budget temps ») pour que la transcription relue, une fois jointe à l'OT, **alimente aussi le corpus** de
|
||
l'assistant à la clôture — pas seulement la description libre ponctuelle de la suggestion.
|
||
- Écarté : Ollama local pour les embeddings du corpus (déjà 100 % locaux via `fastembed`, aucun gain — pertinent
|
||
plutôt pour remplacer l'API Claude opt-in de génération, piste non creusée).
|
||
- Moteur retenu pour une future transcription locale : `faster-whisper` (CTranslate2, MIT, CPU) — même
|
||
philosophie que les embeddings. Point de vigilance identifié : Whisper transcrit mal le darija, probable en
|
||
mélange avec le français sur le terrain.
|
||
- Point d'accroche trouvé dans le schéma existant : `InterventionReport.note` (texte libre) existe déjà en base
|
||
et en UI mais n'est **pas encore indexé** dans le corpus (`indexer_bilans` ne reprend que les libellés codés).
|
||
- Gap identifié dans `anonymisation.py` : reconnaît seulement les noms CONNUS de la base, pas de NER générale —
|
||
risque de fuite plus élevé sur de la parole libre que sur des libellés codés.
|
||
- **Maquette amendée et validée** (`maquette-r5.html`, écran 6 « Voix ») : bandeau d'intention et carte
|
||
« Et ensuite » disent maintenant explicitement que le texte (anonymisé) rejoint le corpus à la clôture de l'OT.
|
||
|
||
**Décisions**
|
||
|
||
- Pas de prototype de mesure préalable (contrairement au calibrage des embeddings R5) — le calibrage
|
||
français/darija se fera plus tard si besoin.
|
||
- **Implémentation non demandée pour l'instant** — la maquette est validée, le code attend un prochain feu vert.
|
||
|
||
**Prochaine étape** : reprendre sur demande du référent — implémentation cohérente avec le flux esquissé (audio
|
||
jamais persisté, transcription → `InterventionReport.note` → corpus à la clôture), moteur et calibrage déjà
|
||
tranchés, pas à rediscuter.
|
||
|
||
---
|
||
|
||
## 2026-07-19 — Pr. Daaif (+ Claude) — Recette terrain mobile sur iPhone 15 Pro : les 7 points validés (ADR-005)
|
||
|
||
**Actions**
|
||
|
||
- **La recette « mode avion » due depuis `release/r4`** a enfin pu se jouer sur un vrai appareil (iPhone 15 Pro
|
||
du référent). Premier obstacle immédiat : Expo Go (App Store, dernière version) refuse le projet — « supported
|
||
SDK 54 » contre notre SDK 57, retard d'approbation Apple structurel, hors de notre contrôle. **Décision du
|
||
référent** : construire des builds natifs locaux (Xcode/Android Studio, signature gratuite via Apple ID
|
||
personnel) pour la vraie recette terrain, tout en conservant Expo Go comme canal léger pour un aperçu sans
|
||
installation ailleurs — actée dans **ADR-005**.
|
||
- **Cinq obstacles techniques réels** rencontrés et corrigés en route (détaillés dans ADR-005) : ciblage par UDID
|
||
plutôt que nom d'appareil (apostrophe) ; ne jamais contourner `expo run:ios` par un `xcodebuild` manuel (a
|
||
produit un crash `dyld: Library not loaded React.framework`) ; **incompatibilité des modules Expo/RN
|
||
précompilés (SDK 56/57) avec la liaison statique du projet** — fix `EXPO_USE_PRECOMPILED_MODULES=0` +
|
||
`RCT_USE_PREBUILT_RNCORE=0` ; **`fetch` global (`expo/fetch`) incompatible avec l'upload multipart natif**
|
||
(`Unsupported FormDataPart implementation`, classé à tort « réseau indisponible » par notre propre gestion
|
||
d'erreur) — fix permanent `EXPO_PUBLIC_USE_RN_FETCH=1` committé dans `apps/mobile/.env` ; découverte réseau du
|
||
dev client peu fiable sur ce Wi-Fi (IP du Mac changée 4 fois, reprises via `expo run:ios --device <udid>` qui
|
||
transmet l'adresse de Metro par deep link plutôt que par découverte automatique).
|
||
- **Recette terrain iOS — 7/7 points validés sur iPhone physique** : (1) connexion démo ; (2) scan caméra réel —
|
||
résolution locale d'une étiquette réelle (D4) + rejet propre d'un QR étranger ; (3) vrai mode avion (D1) — file
|
||
d'écriture visible ; (4) verrou optimiste (D2) — conflit provoqué en modifiant l'OT côté serveur pendant la
|
||
coupure, écran Synchro affichant le conflit, tranché par l'humain, OT clos avec bilan complet et **photo
|
||
réellement téléversée** (885 Ko, compression D5 confirmée) ; (5) persistance — saisie en file survivant à une
|
||
fermeture complète + reconstruction de l'app, resynchronisée au retour réseau (limite documentée : un build dev
|
||
client ne peut PAS démarrer à froid hors-ligne, aucun bundle embarqué — un vrai test « cold start offline »
|
||
demanderait un build Release, hors périmètre aujourd'hui) ; (6) suggestions IA R5 au vrai modèle sur le
|
||
téléphone, chips appliqués pré-remplissant les sélecteurs ; (7) sécurité ADR-003 — déconnexion (appui long,
|
||
purement locale) puis bascule de compte démo, file et cache vides, aucune fuite entre comptes.
|
||
- **Android** : build natif installé sur l'émulateur (`Pixel_3a_API_34`, Android Studio déjà en place) ; passage
|
||
santé complet — connexion démo, navigation Ma journée → fiche appareil (résolution de référence manuelle,
|
||
équivalent du scan sans caméra d'émulateur, confirmée fonctionnelle), écran Scanner sans plantage.
|
||
|
||
**Décisions**
|
||
|
||
- ADR-005 actée : deux canaux de distribution mobile (builds natifs pour la vraie recette, Expo Go pour l'aperçu
|
||
sans installation), managed workflow conservé (`ios/`/`android/` jamais committés).
|
||
- La validation terrain due depuis `release/r4` est **levée pour iOS**. La recette sur **appareil Android
|
||
physique** (vrai mode avion, vraie caméra) reste un reste, faute d'appareil disponible aujourd'hui — même
|
||
situation qu'iOS avant cette session.
|
||
|
||
**Prochaine étape** : recette Android sur appareil physique quand disponible ; redéploiement Dokploy
|
||
(`release/r3` puis r5 avec `AI_SERVICE_TOKEN`) ; calibrage `AI_SEUIL_*` sur corpus SPELEV réel ; secret
|
||
`DOKPLOY_WEBHOOK_URL`.
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — 🏁 R5 CLOSE : tag `release/r5`
|
||
|
||
**Actions**
|
||
|
||
- Tag annoté `release/r5` posé sur `cccfaaa` (CI verte, 8/8 jobs) et poussé — sur décision du référent,
|
||
après recette sans clé API et revue pixel validée (arbitrage tableau appliqué).
|
||
- R0 → R5 : le périmètre v1 du plan de releases est couvert. La suite est de l'exploitation :
|
||
déploiements, calibrage, recettes différées.
|
||
|
||
**Décisions**
|
||
|
||
- R5 est la dernière release du plan — les évolutions suivantes (voix opt-in « si budget temps »,
|
||
backlog v2) s'ouvriront par de nouvelles maquettes, même méthode.
|
||
|
||
**Prochaine étape** : redéploiement Dokploy de l'instance ENSET (main = release/r5 ; nouvelles variables
|
||
`AI_SERVICE_TOKEN` obligatoire — runbook §2 — puis « Réindexer tout »), recette R4 sur téléphone (Expo Go,
|
||
due avant production client mobile), calibrage `AI_SEUIL_*` sur corpus SPELEV réel, secret `DOKPLOY_WEBHOOK_URL`.
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — Arbitrages de recette R5 rendus : le corpus passe en tableau
|
||
|
||
**Actions**
|
||
|
||
- **Arbitrage du référent** sur la revue pixel : « Ça serait mieux d'utiliser un tableau car la plupart des
|
||
fichiers vont être des PDF » — le reste validé (« Appliquer » à écriture directe, calibrage au runbook).
|
||
- **Bibliothèque en tableau**, conforme à l'écran 5 de maquette-r5 : colonnes Document (nom + type · taille ·
|
||
date · auteur), Rattaché à, Indexation, Corpus (interrupteur, visible pour ASSETS.edit), actions
|
||
Ouvrir/Télécharger/Supprimer. Les vignettes restent aux cartes « Documents » des fiches (photos d'OT).
|
||
- Détail aligné sur la maquette : une image non indexable affiche son interrupteur **éteint** quel que soit
|
||
l'état stocké — l'interrupteur montre la réalité du corpus, pas une colonne de base.
|
||
- e2e adapté (lignes de tableau), **16/16 Playwright rejoués** ; artefact de revue pixel mis à jour (même URL),
|
||
arbitrages marqués rendus.
|
||
|
||
**Décisions**
|
||
|
||
- Revue pixel R5 **validée par le référent** (1 correction appliquée). Le tag `release/r5` peut être posé.
|
||
|
||
**Prochaine étape** : tag `release/r5` sur le mot du référent. Restes : recette R4 sur téléphone (Expo Go),
|
||
redéploiement Dokploy (`release/r3` puis r5 avec `AI_SERVICE_TOKEN`), calibrage `AI_SEUIL_*` sur corpus SPELEV.
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — Recette R5 (sans clé API) + durcissement production `siop2-ai`
|
||
|
||
**Actions**
|
||
|
||
- **Recette rejouée au vrai modèle, mode extractif (aucune clé API)** sur un corpus mis en scène (notice
|
||
Otis Gen2 générée, 2 pages). Elle a **invalidé le modèle d'embeddings choisi en R5.1** : sur la question
|
||
type du plan (« quel couple de serrage pour les guides du Gen2 ? »), MiniLM-384 classait la page-réponse
|
||
DERRIÈRE des passages sans rapport (0,24 contre 0,41). Banc comparatif mesuré : `mpnet-base-v2` multilingue
|
||
(768 d) rétablit le classement et une marge signal/bruit exploitable (pertinent ≥ 0,46, hors-corpus ≤ 0,42) ;
|
||
`multilingual-e5-large` (1 024 d, 2,2 Go) classait bien aussi mais scores compressés (0,73-0,90) et poids
|
||
rédhibitoires. **Bascule vers mpnet** (ADR-004 amendé, migration `r5_embeddings_mpnet` : pgvector 384 → 768,
|
||
index vidé — re-dérivable par « Réindexer tout ») + **découpage affiné** (~350 caractères : la phrase-réponse
|
||
ne se noie plus, l'extrait cité est lisible) + seuils par défaut recalés (0,45 / 0,40 / 0,55).
|
||
- **Recette validée après bascule** : réponse sourcée p. 2 en tête ✓, refus honnête chiffré sur question hors
|
||
corpus ✓ (« routeur wifi » → refus ; charabia → refus), suggestions étagées ✓, D1-D5 tenues, le tout SANS
|
||
clé. 16/16 Playwright (l'embeddeur déterministe suit les 768 dims sans recalibrage), 78 tests API, 23 pytest.
|
||
- **Revue pixel** : captures réelles des 6 écrans (web ×4, liseré « suggéré », mobile) face aux écrans de
|
||
maquette-r5 — artefact publié pour le référent avec 3 points d'arbitrage (« Appliquer » à écriture directe,
|
||
vignettes vs tableau du corpus, refus « voisin de domaine » dépendant du calibrage client).
|
||
- **Durcissement production** : `apps/ai/Dockerfile` (uv, venv non éditable, **modèle ONNX téléchargé au
|
||
build** — ADR-004 §1, non-root, healthcheck) ; compose Dokploy : service `siop2-ai` interne (jamais sur
|
||
`dokploy-network`, secret `AI_SERVICE_TOKEN` requis, génération opt-in par variables, seuils calibrables),
|
||
`siop2-api` branché (`AI_SERVICE_URL`) ; runbook enrichi (§2 variables, §5 service IA, §6 calibrage des
|
||
seuils sur corpus client, réindexation post-déploiement).
|
||
|
||
**Décisions**
|
||
|
||
- Le refus « voisin de domaine » (question ascenseur absente du corpus) reste dépendant du calibrage : jamais
|
||
d'invention (extraits réels cités), mais pas toujours un refus. Les seuils sont des variables d'environnement
|
||
pour être calibrés sur le corpus SPELEV réel — procédure au runbook §6.
|
||
- Le tag `release/r5` attend la validation de la revue pixel par le référent.
|
||
|
||
**Prochaine étape** : validation du référent (revue pixel + arbitrages) → tag `release/r5`. Restes : recette
|
||
R4 sur téléphone (Expo Go), redéploiement Dokploy (`release/r3` puis r5), secret `DOKPLOY_WEBHOOK_URL`.
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — R5.3 : les écrans de l'IA (web + mobile) et le corpus administrable
|
||
|
||
**Actions**
|
||
|
||
- **Contrat** (76 opérations) : `Document` expose son état de corpus (`inCorpus`, `indexedAt`, `chunkCount`), `PATCH /documents/{id}/corpus` (bascule réversible, ASSETS.edit), `POST /assistant/reindex` (bilan chiffré traduit du dialecte interne). Clients web **et mobile** régénérés — le job ci-contract vérifie désormais les deux.
|
||
- **Web — page Assistant** (sidebar Pilotage, WORK_ORDERS.view) : chat fidèle aux écrans 1-2 de maquette-r5 — réponse avec **extraits exacts cités** (« Ouvrir » → PDF en onglet authentifié, OT → fiche), avertissement « l'IA propose, vous validez » permanent ; **refus honnête chiffré** (« j'ai cherché dans N documents et M bilans ») avec l'action utile (téléverser la notice).
|
||
- **Web — Bibliothèque = corpus** (écran 5) : bandeau 09-08 (l'anonymisation est DITE), statut par document (indexé · date · extraits / à indexer / exclu / image non indexable), interrupteur d'exclusion (PDF seulement), « Réindexer tout » avec bilan. **Fiche OT** (écran 3) : « Décrire pour suggérer » dans la carte Bilan — suggestions justifiées (« N bilans similaires », confiance), « Appliquer » = le geste humain qui écrit (cohérent avec la carte à enregistrement direct), **liseré « suggéré »** retiré dès qu'un choix manuel reprend la main.
|
||
- **Mobile — clôture** (écran 4) : décrire au pouce → **chips suggérées** (un appui = un champ pré-rempli, ✓), note D1 visible ; hors-ligne le bouton dit « réseau requis » (l'IA est un service serveur — la clôture en file R4 n'en dépend pas).
|
||
- **`apps/ai`** : seuils par env (`AI_SEUIL_*`) — nécessaires à la CI et au calibrage de recette.
|
||
- **CI** : le job e2e démarre `siop2-ai` (embeddeur déterministe) et le parcours R5 se recette en vrai : upload d'un **PDF généré avec xref valide** → réindexation → statut → réponse sourcée (extrait « 25 Nm », p. 1) → refus sur charabia → exclusion → suggestion appliquée sur un OT créé. **16/16 Playwright** (recettes R0→R5).
|
||
- **Vérifications réelles** : seuils CI **mesurés** (vrai match 0,66 vs bruit de collisions 0,11 → pertinence 0,20 ; codes pertinents 0,14-0,16 vs parasite 0,08 → suggestion 0,10) ; chaîne complète au **vrai modèle ONNX** (réindexation, ask sourcé p. 8/p. 10, suggestions étagées avec vrais comptes de bilans) ; parcours **mobile Expo web 6/6** et **web 7/7** au vrai modèle, 0 erreur console. 78 tests API (2 nouveaux : bascule corpus gardée, reindex traduit + 403).
|
||
|
||
**Décisions**
|
||
|
||
- Sur le web, « Appliquer » écrit immédiatement (comme tout le reste de la carte Bilan R2) : le clic EST la validation humaine — l'esprit de la maquette (« rien sans vous ») est porté par le geste, pas par un bouton « Enregistrer » séparé. À montrer en revue pixel.
|
||
- Les seuils par défaut du vrai modèle (0,30/0,35/0,55) restent **à calibrer en recette sur le corpus client réel** : mesuré ce jour sur un corpus non-métier, le multilingual-MiniLM donne des scores plats (hors-sujet 0,35-0,43 vs pertinent 0,24-0,28) — c'est exactement pourquoi ils sont configurables.
|
||
|
||
**Prochaine étape** : recette R5 complète (doit passer SANS clé API), durcissement production (Dockerfile `siop2-ai` + compose Dokploy), puis tag `release/r5`. Restes : recette R4 sur appareil, redéploiement Dokploy de `release/r3`.
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — R5.2 : l'assistant au contrat + suggestion de codes de bilan
|
||
|
||
**Actions**
|
||
|
||
- **`apps/ai`** : `/internal/ask` — recherche → **seuil de pertinence** → extraits sourcés, ou **refus honnête** portant la taille du corpus cherché (« 1 document, 3 bilans » — l'écran 2 des maquettes aura ses chiffres) ; la rédaction passe par le `Generateur` (opt-in — mode extractif par défaut). `/internal/suggest` — similarité sémantique entre la description libre et les libellés **actifs** des référentiels (contextualisés « anomalie constatée : … »), un seul code par champ, au-dessus du seuil, confiance FORTE/MOYENNE + « N bilans similaires sur ce parc » (comptés dans les chunks d'historique). Sans LLM : déterministe, explicable. 23 pytest.
|
||
- **Contrat** (74 opérations) : `POST /assistant/ask` → `AssistantAnswer` (mode EXTRACTIVE/GENERATED/REFUSAL, extraits cités document/page ou bilan daté, corpus cherché) ; `POST /assistant/suggest-bilan` → codes existants seulement. Clients web/mobile régénérés.
|
||
- **API NestJS** : module `assistant` — proxy vers `siop2-ai` (`AI_SERVICE_URL`/`AI_SERVICE_TOKEN` à l'env, ADR-004 §4), permissions par la matrice (`ask` = WORK_ORDERS.view ; `suggest` = WORK_ORDERS.edit — qui remplit des bilans), traduction du dialecte interne vers le contrat, **503 propre** si le service IA est éteint (jamais un 500). 6 tests e2e sur un **stub HTTP** (76 tests API, 14 suites).
|
||
- **Vérifié sur la vraie chaîne** (NestJS → ai → pgvector, vrai modèle) — et elle a débusqué un bug : fastembed ne renvoie pas des vecteurs normés, la similarité des suggestions dépassait 1 (pgvector normalisait dans son opérateur, ce qui masquait l'écart). **Normalisation à l'encodage** + réindexation : bilans du parc trouvés en tête (0.41), refus honnête hors corpus, suggestions cosinus ≤ 1 à confiances étagées.
|
||
|
||
**Décisions**
|
||
|
||
- Les seuils (pertinence 0.30, suggestion 0.35, confiance forte 0.55) sont des constantes de départ — à calibrer en recette sur le corpus réel du client.
|
||
|
||
**Prochaine étape** : R5.3 — les écrans validés (Assistant sidebar, corpus dans la Bibliothèque, suggestions dans les fiches OT web et clôture mobile). Restes : recette R4 sur appareil, redéploiement Dokploy de `release/r3`.
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — R5.1+ : clé API de génération configurable (demande du référent)
|
||
|
||
**Actions**
|
||
|
||
- **`generation.py`** : l'interface `Generateur` d'ADR-004 §3 prend corps — `GenerateurExtractif` (contrat de base, aucun LLM) et `GenerateurAPI` (SDK officiel `anthropic`, dépendance **optionnelle** `--extra generation`, absente des tests/CI). Consigne système : citations [n] obligatoires depuis les extraits fournis, « n'invente RIEN », rappel de validation humaine. Tout échec (refus du modèle, quota, réseau) **retombe sur l'extractif** — jamais d'erreur utilisateur imputable au LLM ; `stop_reason == "refusal"` traité explicitement.
|
||
- **Configuration complète et validée au boot** : `AI_GENERATION=off|api`, `AI_API_KEY` (exigée en mode api — le démarrage refuse sinon, message clair), `AI_MODEL` (défaut `claude-opus-4-8`). `.env.example` posé ; `/healthz` expose le **mode** (off/api), jamais la clé ; le générateur rejoint l'état de l'app (R5.2 le consommera).
|
||
- 5 tests ajoutés (19 pytest au total) : défaut extractif, refus de boot api-sans-clé, mode inconnu refusé, clé/modèle lus de l'env, invite pure numérotant les extraits. Vérifié en réel : boot refusé sans clé, `GenerateurAPI` construit avec le SDK, healthz sans secret.
|
||
|
||
**Décisions**
|
||
|
||
- La clé vit en variable d'environnement (Dokploy secrets), pas en base ni dans l'UI — cohérent avec `JWT_SECRET` et ADR-002.
|
||
|
||
**Prochaine étape** : R5.2 — assistant au contrat (proxy NestJS), le mode extractif d'abord, la rédaction branchée sur ce `Generateur`. Restes : recette R4 sur appareil, redéploiement Dokploy de `release/r3`.
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — R5.1 : socle `apps/ai` — ingestion anonymisée + recherche sémantique
|
||
|
||
**Actions**
|
||
|
||
- **ADR-004 actée** : embeddings **locaux sur CPU** (fastembed ONNX, `paraphrase-multilingual-MiniLM-L12-v2`, 384 dims — les textes du client ne quittent jamais le serveur), pgvector dans le Postgres existant (table `RagChunk` **possédée par Prisma**, migration `r5_ia` + état de corpus sur Document), génération **opt-in** (`AI_GENERATION=off` par défaut : mode extractif honnête, la recette doit passer sans clé API), suggestion de bilan sans LLM (similarité sémantique, explicable). Topologie : `siop2-ai` jamais exposé, joint par l'API NestJS seule (`X-Service-Token`).
|
||
- **`apps/ai` posé** (FastAPI + uv, python 3.11) : config validée au démarrage, `/healthz`, `/internal/reindex` (idempotent) et `/internal/search` protégés par le jeton de service. **Pipeline** : PDF MinIO → texte par page (pypdf) → **anonymisation D4** (e-mails, téléphones marocains, noms connus de la base — insensible casse/accents, fonction pure) → découpage (~900 car., chevauchement, testé) → embeddings → `RagChunk` avec localisateur (« p. 42 », « bilan du 17/07 »). Bilans codés clôturés ingérés aussi (« sur votre parc… »). L'exclusion de corpus (D3) s'applique à l'ingestion ET à la lecture.
|
||
- **14 pytest verts + ruff** (embeddeur **déterministe** en test/CI — aucun téléchargement, même interface 384 dims) ; **job CI `ai`** (uv), deploy en dépend.
|
||
- **Vérifié en réel avec le vrai modèle ONNX** : réindexation du corpus seedé en 7 s (1 PDF réel → 31 extraits paginés, 3 bilans), recherche sémantique concluante (PDF trouvé par le sens, bilans du parc par « frottement des guides »), e-mails du PDF remplacés par ⟨contact⟩, **0 identité dans les chunks** (contrôle SQL sur les 9 noms seedés).
|
||
|
||
**Décisions**
|
||
|
||
- Convention de test R5 : pytest unitaires purs en CI (sans DB ni réseau) ; l'intégration réelle (DB + MinIO + modèle) se vérifie en local et en recette.
|
||
|
||
**Prochaine étape** : R5.2 — l'assistant au contrat (proxy NestJS authentifié → `siop2-ai`, mode extractif sourcé « sourcé ou silencieux ») + suggestion de codes de bilan. Restes : recette R4 sur appareil, redéploiement Dokploy de `release/r3`.
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — R5 ouverte : maquettes IA (design d'abord)
|
||
|
||
**Actions**
|
||
|
||
- **`maquette-r5.html`** (docs/02-design/maquettes) : 6 écrans — **Assistant sourcé** (recette type du cadrage : « couple de serrage des guides Gen2 ? » → réponse à citations numérotées, sources document/page/**extrait exact** ouvrables), **Sans source = refus** (l'anti-hallucination visible : « je préfère ne pas inventer » + ce qui a été cherché + action utile), **Suggestion de bilan** web et mobile (description libre → codes EXISTANTS des référentiels, justification + confiance, « Appliquer » pré-remplit avec liseré « suggéré », rien d'enregistré sans le geste humain), **Corpus & ingestion** (bibliothèque R3 + bilans codés, statut par document, exclusion réversible, bandeau 09-08), **Voix** (option « si budget » : dictée opt-in, transcription relue, audio purgé immédiatement). Bi-thème, tokens répliqués, vérifiée en navigateur (6 onglets + sombre, zéro erreur console).
|
||
- Artefact de validation publié (6 écrans + preuve bi-thème + **5 décisions à acter** : D1 aucune écriture automatique, D2 sourcé ou silencieux, D3 corpus fermé et visible, D4 anonymisation à l'ingestion 09-08, D5 voix opt-in avec purge).
|
||
|
||
**Décisions**
|
||
|
||
- **→ Levé le 17/07/2026 : maquettes R5 et les 5 décisions (D1-D5) VALIDÉES par le référent.** Lancement R5.1 (socle `apps/ai` + ADR-004).
|
||
|
||
**Prochaine étape** : R5.1 — ADR-004 (embeddings/modèles), migration `r5_ia` (chunks pgvector, état de corpus), pipeline d'ingestion anonymisé et testé. Restes : recette R4 sur appareil, redéploiement Dokploy de `release/r3`.
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — R4 CLOSE : tag `release/r4` (recette téléphone reportée)
|
||
|
||
**Actions**
|
||
|
||
- Tag annoté **`release/r4`** posé et poussé (CI verte sur `c8b3c17` : 74 tests API, 17 jest-expo, 14 Playwright).
|
||
|
||
**Décisions**
|
||
|
||
- **Décision du référent : la recette sur téléphone (Expo Go — vrai mode avion, scan caméra) est REPORTÉE**, il y accédera plus tard avec un appareil. Le tag est posé sur la foi de la recette « mode avion » rejouée 13/13 en Expo web piloté (toute la mécanique D1/D2 vérifiée, capteurs exclus). **La validation terrain reste due avant toute mise en production client de l'app mobile** — notée comme reste de release.
|
||
|
||
**Prochaine étape** : ouverture **R5 — IA** (design d'abord : maquettes assistant RAG sourcé + suggestion de codes de bilan, décisions à acter). Restes : recette R4 sur appareil, redéploiement Dokploy de `release/r3`.
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — R4.3 : file d'écriture, verrou optimiste, Synchro & conflits
|
||
|
||
**Actions**
|
||
|
||
- **Verrou optimiste côté serveur (D2)** : `updatedAt` exposé au détail OT ; `baseUpdatedAt` optionnel sur transition/coche/bilan → **409 « Conflit de version » contextualisé** (qui, quand — depuis le dernier événement). Toute écriture « secondaire » (coche, bilan, commentaire, conso, MO) fait désormais avancer la version, sinon le verrou serait aveugle. Sans version fournie, comportement web inchangé. 4 tests e2e dédiés (74 au total).
|
||
- **File d'écriture mobile (D1)** : store persisté AsyncStorage (survit au redémarrage, un ENVOI interrompu redevient rejouable), rejeu **dans l'ordre** ; succès → sortie de file **et propagation de la version fraîche** aux saisies restantes du même OT (nos propres écritures ne se conflictent pas entre elles — un écart étranger reste détecté) ; coupure → tout reste en attente ; refus → CONFLIT et la file s'arrête là. Transitions, coches, bilan et **photos (D5** : compressées ~1 600 px, expo-image-picker/manipulator**)** passent par la file, avec patch optimiste du cache (l'OT affiche « Terminé » localement, chips « en file » partout).
|
||
- **Écran Synchro & conflits** (écran 7) : file visible et horodatée, badge tabbar (ambre → rouge si conflit), carte de conflit avec le message de l'API et les trois choix — voir l'OT, **rejouer sur la version à jour**, abandonner. Préchargement du parc et des référentiels à l'entrée (trou D1 débusqué par la vérif : le bilan hors-ligne n'avait pas ses vocabulaires).
|
||
- **Sécurité & routage (demande du référent) : ADR-003** — qui vit où sur l'appareil et ce qui le protège (jeton en trousseau, cache/file en sandbox, purge complète à la déconnexion), l'API seule autorité, deep links `siop://` jamais suivis aveuglément (le scan extrait et résout localement). **Correctif réel au passage : la file et le cache persisté n'étaient pas purgés au logout** — un autre compte sur le même téléphone aurait pu rejouer les saisies du précédent.
|
||
- **Recette « mode avion » rejouée en Expo web piloté : 13/13** — OT ouvert, avion, Démarrer + bilan + clôture hors-ligne (état local immédiat, 3 en file), Salma modifie l'OT pendant ce temps, retour réseau → rejeu auto → **conflit tranché par l'humain** → file vide → serveur : Terminé avec bilan. 17 tests jest-expo (5 sur la file), zéro erreur console inattendue.
|
||
|
||
**Décisions**
|
||
|
||
- La recette officielle « mode avion en sous-sol » reste à rejouer **sur téléphone** (Expo Go) avec le référent — le web a validé toute la mécanique, pas les capteurs ni le vrai mode avion.
|
||
- Durcissements notés à l'ADR-003 : chiffrement applicatif de la file et épinglage TLS si exigence client.
|
||
|
||
**Prochaine étape** : recette R4 sur appareil avec le référent → tag `release/r4` → R5 IA (design d'abord). Redéploiement Dokploy de `release/r3` toujours en attente côté serveur.
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — R4.2 : scan QR, fiches terrain, grille cochable
|
||
|
||
**Actions**
|
||
|
||
- **Scanner réel** (expo-camera, QR seulement) : analyse du code testée (`analyseScan` — URL portail `…/q/REF` de l'étiquette A6 quel que soit le domaine, ou référence tapée ; les QR étrangers sont refusés proprement, jamais d'écran blanc). **Résolution D4 : le parc en cache d'abord** (fonctionne en sous-sol), rafraîchissement seulement si le réseau est là ; référence inconnue → message honnête. Repli saisie manuelle (seul chemin sur web, assumé).
|
||
- **Fiche ascenseur** (consultation, D3) : identité, organes, interventions **visibles par le rôle** (invariant « voir autre »), raccourci vers l'OT en cours. **Fiche OT** : un bouton principal selon la machine à états, pièces & main-d'œuvre aux coûts figés, garde de clôture alimentée par les `closureBlockers` de l'API (une seule source de vérité). **Clôture terrain** : bilan codé 6 champs (sélecteur plein écran au pouce — RN n'a pas de `<select>`), garde visible, clôture en ligne. **Préventif** : mes grilles → checklist cochable, appui long = N/A, coche individuelle à l'API.
|
||
- En R4.2 les écritures restent **en ligne** (bandeaux explicites hors-ligne) — la file générale et le verrou optimiste sont le cœur de R4.3, comme prévu.
|
||
- Vérifié en Expo web piloté : **12/12** — scan A1 → fiche → OT-0341 (505 MAD, garde), référence inconnue, coche/décoche (état restitué), et un OT créé par l'API : Démarrer → clôture bloquée bilan incomplet → 3 champs remplis → garde éteinte → **Terminé** ; zéro erreur console ; données de test purgées. 12 tests jest-expo (scan, progression, tri, tokens).
|
||
|
||
**Décisions**
|
||
|
||
- Leçon accessibilité RN web : `accessibilityState.checked` ne produit PAS `aria-checked` — utiliser la prop `aria-checked` (mieux pour les lecteurs d'écran, et testable).
|
||
- La caméra ne se recette pas sur web : scan réel à valider dans Expo Go (le repli manuel couvre le parcours en attendant).
|
||
|
||
**Prochaine étape** : R4.3 — file d'écriture persistée rejouée dans l'ordre, verrou optimiste tranché par l'humain (D2), écran Synchro & conflits, photos en file (D5) → recette « mode avion » sur téléphone. Redéploiement Dokploy de `release/r3` toujours en attente.
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — R4.1 : socle mobile Expo (app du technicien)
|
||
|
||
**Actions**
|
||
|
||
- **`apps/mobile` posé** (Expo SDK 57, TypeScript strict, pnpm workspace, `@siop/shared` consommé) : connexion e-mail/mdp + **sélecteur démo ADR-002** (n'existe que si l'API l'expose), coquille **tabbar** de la maquette (Scanner/Préventif/Synchro visibles mais marqués R4.2/R4.3 — même patron que la sidebar web), **Ma journée** : OT triés priorité puis échéance (fonction pure testée), urgence « personne bloquée » en tête, strie de priorité, pastille de synchro permanente.
|
||
- **D1 en actes (lecture)** : cache TanStack **persisté** dans AsyncStorage (7 jours), NetInfo → `onlineManager` + bandeau hors-ligne horodaté. Jeton en SecureStore. Client typé régénéré depuis `docs/openapi.json` (règle d'or). Thème : tokens light/dark répliqués (testés), Manrope embarquée.
|
||
- **API : `CORS_ORIGINS`** (env, vide par défaut = pas de CORS) — nécessaire à Expo **web**/debug seulement ; le web de prod reste derrière le proxy nginx, les apps natives n'ont pas d'Origin.
|
||
- Vérifié en navigateur réel (Expo web + Playwright) : **10/10** — connexion démo Ahmed → Ma journée (ses OT seulement : le scoping « voir autre » joue), bascule hors-ligne (bandeau + liste servie du cache) et retour, onglets à venir honnêtes ; zéro erreur console. 6 tests jest-expo, lint racine étendu (react-hooks sur mobile), **job CI `mobile`** ajouté (deploy dépend de lui).
|
||
|
||
**Décisions**
|
||
|
||
- La vraie recette hors-ligne (mode avion, redémarrage, file d'écriture) se fera **sur téléphone** — Expo web ne prouve que le chemin de code NetInfo→UI (dispatch `navigator.connection.change` observé).
|
||
- Leçon Playwright : `setOffline` ne déclenche pas les événements réseau du navigateur — NetInfo web écoute `navigator.connection` sur Chromium.
|
||
|
||
**Prochaine étape** : R4.2 — scan QR (expo-camera) + fiche ascenseur + fiche OT + checklist cochable ; puis R4.3 file d'écriture + verrou optimiste (D2). Redéploiement Dokploy de `release/r3` toujours en attente.
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — R4 ouverte : maquettes mobile (design d'abord)
|
||
|
||
**Actions**
|
||
|
||
- **`maquette-r4.html`** (docs/02-design/maquettes) : 7 écrans de l'app technicien — Ma journée (en ligne ET hors-ligne côte à côte), Fiche OT, Clôture terrain (bilan codé R2, garde visible ; variante « en file » hors-ligne), Scan QR (étiquettes A6 de R1, hors-ligne inclus), Fiche ascenseur (consultation devant la machine), Checklist du mois (coches individuelles en file), **Synchro & conflits** (file visible, conflit de verrou optimiste où l'humain tranche). Tokens répliqués, bi-thème, gabarit téléphone repris de la maquette R3, données du parc seedé. Vérifiée en navigateur (7 onglets + bascule sombre, zéro erreur console).
|
||
- Artefact de validation publié pour le référent (7 écrans + preuve bi-thème + **5 décisions à acter** : D1 offline-first lecture locale/écriture en file, D2 verrou optimiste tranché par l'humain, D3 périmètre fermé « app du technicien » sans création d'OT mobile, D4 scan local des étiquettes R1, D5 photos compressées en file — pas d'audio ni de géolocalisation en R4, loi 09-08).
|
||
|
||
**Décisions**
|
||
|
||
- **→ Levé le 17/07/2026 : maquettes R4 et les 5 décisions (D1-D5) VALIDÉES par le référent.** Lancement R4.1 (socle mobile Expo).
|
||
|
||
**Prochaine étape** : R4.1 — socle Expo : coquille tabbar, connexion + sélecteur démo (ADR-002), client typé du contrat, « Ma journée » en lecture avec cache hors-ligne. En parallèle, redéploiement Dokploy de `release/r3` toujours à faire.
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — R3 CLOSE : tag `release/r3`
|
||
|
||
**Actions**
|
||
|
||
- Recette R3 prononcée par le référent après la revue pixel corrigée (8 écarts + recherche globale, artefact à jour, CI verte sur `460ef4a`).
|
||
- Tag annoté **`release/r3`** posé et poussé.
|
||
|
||
**Décisions**
|
||
|
||
- Le tag précède le redéploiement (webhook CD toujours absent) : le déploiement Dokploy se fait manuellement depuis ce tag, puis vérification en ligne — même séquence que R1/R2.
|
||
|
||
**Prochaine étape** : redéployer sur Dokploy (migration `r3_recette_fixes` + seed au boot), vérifier en ligne (rattachements Tiers, recherche ⌘K, période des stats), puis ouvrir **R4 Mobile** (Expo — design d'abord, maquettes avant tout code).
|
||
|
||
---
|
||
|
||
## 2026-07-17 — Pr. Daaif (+ Claude) — R3.4 : revue pixel, corrections de recette, recherche globale
|
||
|
||
**Actions**
|
||
|
||
- **Revue pixel R3** (artefact partagé, captures maquette ↔ appli côte à côte, 1440 px, thème clair) : 7/7 écrans structurellement fidèles, 8 écarts relevés. **Arbitrage du référent : tout corriger + activer la recherche.**
|
||
- **Stock** : filtre Fournisseur, « Entrée de stock » depuis la liste (choix de la pièce dans la modale), pièces sous le seuil en tête. **Fiche pièce** : fournisseur → lien vers Tiers. **Statistiques** : « Période » devient un vrai sélecteur 3/6/12 mois (`months` au contrat, libellés et barres suivent). **Tiers** : les syndics affichent leurs sites — nouveau lien site→client/syndic (migration `r3_recette_fixes`, `Location.partnerId`, garde « CLIENT seulement »), éditable sur la fiche site, seedé (Tour Atlas, Résidence Al Manar). **Bibliothèque** : filtre « Rattaché à » + zone de glisser-déposer (la modale s'ouvre préremplie, le rattachement reste obligatoire).
|
||
- **Recherche globale ⌘K active** : `GET /search` (73e opération au contrat) — chaque famille (OT/ascenseurs/sites) n'est interrogée que si le rôle a la permission `view` du domaine, les OT respectent « voir autre » ; topbar avec debounce 200 ms, résultats groupés, navigation flèches + Entrée. 8 tests API dédiés (scoping vérifié rôle par rôle).
|
||
- Écart « regroupement des tiers » retiré : le tri type-puis-nom existait déjà (constat d'écran, pas de code).
|
||
- Vérifié : 18/18 contrôles en navigateur réel (zéro erreur console), **14/14 Playwright** (dont nouveau parcours « recette corrigée »), **74 tests API** (13 suites). Artefact de revue mis à jour (statut « corrigé », captures fraîches).
|
||
|
||
**Décisions**
|
||
|
||
- « Bons de commande » reste dans l'entête du Stock **en plus** de la maquette : c'est le seul accès à l'écran BC — à entériner en recette (ou déplacer si le référent préfère).
|
||
- La liste des tiers restant sous `PURCHASE_ORDERS.view`, le champ syndic de la fiche site n'est éditable que si le rôle voit les tiers (sinon lecture seule).
|
||
|
||
**Prochaine étape** : validation finale du référent sur l'artefact → déploiement Dokploy → tag `release/r3` → R4 Mobile (design d'abord).
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — R3.3+ : aperçu et ouverture des documents (retour de pré-recette)
|
||
|
||
**Actions**
|
||
|
||
- **Retour du référent** : les documents téléversés n'avaient ni aperçu ni ouverture (seul « Télécharger » existait). Corrigé dans la vignette (`carte-documents.tsx`) : les **images affichent leur vrai aperçu** (le fichier exige le jeton → récupération du blob puis URL objet révoquée au démontage, jamais de `<img src>` nu) ; les PDF gardent leur pictogramme.
|
||
- **« Ouvrir »** : nouveau bouton (et clic sur la vignette) — onglet ouvert **dans le geste utilisateur** (sinon bloqueurs de pop-up) puis pointé sur le blob ; le navigateur affiche PDF et images dans sa visionneuse. « Télécharger » inchangé.
|
||
- Vérifié en navigateur réel (Playwright, appli lancée) : vignette image réellement chargée (`naturalWidth > 0`), ouverture image et PDF en onglet `blob:` (PDF rendu dans la visionneuse Chrome — capture), téléchargement intact, suppression OK, zéro erreur console. Typecheck + vitest verts.
|
||
|
||
**Décisions**
|
||
|
||
- L'aperçu télécharge le fichier entier (pas de miniature côté serveur) : acceptable au volume actuel ; si la bibliothèque grossit, générer des miniatures à l'upload (noté pour le durcissement).
|
||
|
||
**Prochaine étape** : recette R3 avec le référent (revue pixel), déploiement Dokploy, tag `release/r3` → puis R4 Mobile (design d'abord).
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — R3.3 : les écrans web de la gestion — R3 prête pour recette
|
||
|
||
**Actions**
|
||
|
||
- **7 écrans fidèles à `maquette-r3.html`** : Stock (alerte sous seuil, strie ambre, « Préparer le BC » sur la ligne), Fiche pièce (« les mouvements SONT le stock », entrée/ajustement motivé), Bons de commande (liste + panneau, envoi/annulation/**réception → entrées de stock**), création de BC **préremplie** depuis l'alerte (fournisseur de la pièce, quantité pour repasser le seuil + 5, dernier PU), Tiers, Bibliothèque (upload rattaché à un appareil, filtre par type), Statistiques (4 KPI, pannes par organe **depuis les bilans codés**, coûts par mois, top équipements).
|
||
- **La fiche OT s'achève** : carte « Pièces & main-d'œuvre » réelle (consommer une pièce — prix figé annoncé AVANT le clic, impact stock affiché ; saisir du temps au taux figé) + carte « Documents » réelle (photo d'intervention téléversée/retéléchargée). La fiche ascenseur gagne la même carte Documents ; le tableau de bord allume « Pannes par organe » ; **Personnes affiche et édite le taux horaire**.
|
||
- Nav « Ressources »/« Statistiques » activée par la matrice (PARTS/PURCHASE_ORDERS/ASSETS/ANALYTICS.view) ; captures des 9 écrans comparées aux maquettes (bi-thème), zéro erreur console.
|
||
- **Recette R3 automatisée** (2 parcours Playwright, données E2E autonomes purgées au setup) : tiers → pièce → sous seuil → BC prérempli → réception → **l'alerte s'éteint** ; OT : consommation + 1 h 30 → **380 MAD** exacts ; upload/download MinIO réel ; statistiques lues par la Vue seule. **13/13 e2e verts, 10,7 s.**
|
||
|
||
**Décisions**
|
||
|
||
- Le formulaire BC prérempli attend la fin du refetch avant d'initialiser ses lignes (le cache TanStack portait un stock périmé — attrapé par la recette, pas par l'œil).
|
||
- `cleanup-e2e` purge désormais les entités R3 (mouvements liés aux OT E2E supprimés AVANT les OT, sinon ils deviennent orphelins et faussent les stocks seedés d'un run à l'autre).
|
||
|
||
**Prochaine étape** : recette R3 avec le référent (revue pixel), déploiement Dokploy, tag `release/r3` → puis R4 Mobile (design d'abord).
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — R3.2 : bibliothèque de documents + analytics
|
||
|
||
**Actions**
|
||
|
||
- **`FileStorage` gagne ses vraies opérations** (put/stream/remove ; bucket créé au démarrage — aucune étape manuelle au déploiement) ; MinIO reste confiné à son implémentation (règle ESLint).
|
||
- **Bibliothèque** : upload multipart (PDF/JPG/PNG, 20 Mo max, rattachement appareil OU OT **requis**, permission d'édition sur la cible), liste filtrable, **téléchargement streamé par l'API** (MinIO jamais exposé — topologie du runbook respectée), suppression. Test e2e : les octets téléchargés sont IDENTIQUES aux octets envoyés.
|
||
- **Analytics** (`GET /analytics/summary`, permission ANALYTICS) : tout est dérivé du réel — coûts par mois depuis les mouvements/main-d'œuvre figés, **pannes par organe depuis les bilans codés**, taux de préventif (grilles terminées/générées), durée moyenne de résolution, top équipements en coût.
|
||
- Générateur OpenAPI étendu (query params, multipart, réponse binaire) — 71 opérations. **CI : service MinIO ajouté** (jobs api + e2e, image bitnami).
|
||
- **58 tests verts** (92 % / 73,9 %) ; test de régression du tri des « Interventions récentes » rendu **déterministe** (les positions absolues étaient instables sous 12 suites parallèles) — 6 runs complets consécutifs verts.
|
||
|
||
**Prochaine étape** : R3.3 — écrans web (stock, fiche pièce, BC, coûts sur fiche OT, statistiques, tiers, bibliothèque, taux dans Personnes), puis recette R3 + déploiement + tag.
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — R3.1 : socle backend de la gestion
|
||
|
||
**Actions**
|
||
|
||
- **Maquettes R3 et les 4 décisions VALIDÉES par le référent** → lancement du socle.
|
||
- **Modèle** (migration `r3_gestion`, 7 tables) : Partner, Part (SANS colonne de quantité), StockMovement (signé, tracé, PU figé), PurchaseOrder/Line, LaborTime (taux figé), Document (préparé pour R3.2) ; `User.hourlyRate` (taux courant, administrable dans Personnes).
|
||
- **API** (66 opérations au contrat) : tiers, pièces (stock = Σ mouvements calculé en `groupBy`, alerte sous seuil), entrée/ajustement (motif requis, **stock jamais négatif** — vérifié en transaction), BC (Brouillon→Envoyé→Reçu ; **la réception crée les RECEIPT et met à jour `lastUnitPrice`**), consommation sur OT (stock suffisant + **prix figé**) et main-d'œuvre (**taux figé**, refus motivé si taux non défini) ; `WorkOrderDetail.costs` (lignes + total immuables) ; coûts verrouillés après clôture/annulation (409).
|
||
- **Seed** : 5 tiers, 5 pièces de la maquette (P-0113 sous seuil via son histoire de mouvements), BC reçu + BC envoyé, taux horaires, et **la carte maquette rejouée : OT-0341 = 505 MAD** (240 + 85 + 180) — vérifiée par test.
|
||
- **55 tests verts** (92 % / 74,9 %) dont la recette officielle : consommer sous seuil → alerte → BC → réception → réappro, prix d'hier intact sur l'OT pendant que le prix courant change ; hausse du taux d'Ahmed sans effet sur les OT passés.
|
||
|
||
**Leçon majeure (durcissement)**
|
||
|
||
- Les références « max+1 » (OT/DEM/BC/P) étaient une **course sous charge parallèle** (500 sporadiques malgré les retries). Remplacées par des **séquences Postgres** (`nextval`) initialisées au max existant : la classe de bugs disparaît — 3 runs Jest complets consécutifs verts. La numérotation ne se remet pas à zéro chaque année (l'unicité prime), consigné.
|
||
|
||
**Prochaine étape** : R3.2 — bibliothèque de documents (upload multipart 20 Mo → `FileStorage.putObject`, téléchargement streamé via l'API — MinIO jamais exposé) + analytics (`GET /analytics/summary`), puis R3.3 écrans web.
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — R3 ouverte : maquettes de la gestion à valider
|
||
|
||
**Actions**
|
||
|
||
- **R3 « Gestion » ouverte — design d'abord** : `maquette-r3.html`, **7 écrans** dans le moule validé : Stock (dérivé des mouvements, alertes sous seuil avec « Préparer le BC »), Fiche pièce (les mouvements SONT le stock), Bons de commande (Brouillon → Envoyé → Reçu ; la réception crée les entrées), Coûts sur OT (la carte validée R0 devient réelle : consommation à **prix figé**, main-d'œuvre à **taux figé**), Statistiques (coûts, pannes par organe alimenté par les bilans codés, taux de préventif, top équipements — palette CVD), Tiers (annuaire minimal fournisseurs/syndics), Bibliothèque (documents typés rattachés appareil/OT, MinIO via FileStorage — futur corpus RAG R5).
|
||
- Rendu vérifié (7 écrans + thème sombre, zéro erreur).
|
||
|
||
**Décisions (proposées à la validation)**
|
||
|
||
- **Stock strictement dérivé des mouvements** (décision v1 éprouvée) : aucune saisie directe de quantité ; les ajustements d'inventaire sont des mouvements motivés.
|
||
- **Prix figé à la consommation** (dernier prix d'achat) et **taux horaire figé** par personne (champ géré dans Personnes) : le coût d'un OT ne bouge plus jamais.
|
||
- La réception d'un BC crée les mouvements d'entrée et fige le PU.
|
||
- Documents : types fermés (Notice / Certificat / Photo / Autre), 20 Mo max, rattachement obligatoire (appareil ou OT).
|
||
|
||
**⛔ Bloquant** : validation des maquettes R3 par le référent avant toute ligne de code R3.
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — 🏁 R2 CLOSE : recettée, déployée, taguée
|
||
|
||
**Actions**
|
||
|
||
- **Recette R2 prononcée par le référent** (une anomalie détectée et corrigée en cours de recette — voir entrée précédente : tri des « Interventions récentes »).
|
||
- **Déploiement vérifié en ligne** : portail public `/q/A1` opérationnel (résolution QR 200), 13 OT avec l'urgence en tête, préventif de juillet généré sur l'instance (8 grilles, 7 premiers contrôles).
|
||
- DoD complète → **tag `release/r2`**. Le carnet papier est remplacé : signalement QR sans compte → OT → bilan codé → clôture gardée → suivi, plus préventif idempotent et compteurs.
|
||
|
||
**Prochaine étape** : R3 Gestion — design d'abord : maquettes stock/BC/tiers/coûts OT/analytics/bibliothèque à produire et faire valider.
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — Recette R2 : anomalie du référent corrigée
|
||
|
||
**Anomalie (recette du référent)** : un OT créé, assigné à Ahmed, traité et clôturé apparaissait dans le tableau de bord d'Ahmed mais PAS dans celui de Salma ni de l'administrateur.
|
||
|
||
**Diagnostic** : la liste API triait **par statut d'abord** (les Terminés relégués en queue) et le tableau de bord affiche les 5 premières lignes. Ahmed, sans « voir autre », a une liste courte → son OT clôturé restait dans le top 5 ; Salma et l'admin voient tout le parc → l'OT clôturé était éjecté des « Interventions récentes ». (La liste OT filtrait aussi « actifs » par défaut, conforme à la maquette — c'est le tableau de bord qui trompait.)
|
||
|
||
**Correctif** : la liste trie par **dernière activité** (`updatedAt` desc) — un OT qui vient d'être clôturé remonte en tête pour tout le monde ; les urgences « personne bloquée » **actives** restent épinglées au sommet (un OT d'urgence annulé ne squattait plus la tête, corrigé au passage). Reproduit avant / vérifié après sur le scénario exact du référent ; **test de régression** ajouté à la recette e2e (position de l'OT clôturé dans la liste de Salma ≤ nombre d'urgences actives). 50 tests API + 11 Playwright verts.
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — R2.4 : portail public QR — l'étiquette prend vie
|
||
|
||
**Actions**
|
||
|
||
- **Trois routes publiques `/portal`** (les seules routes @Public métier de l'API), **throttlées** (`@nestjs/throttler` : 10 signalements/min, 60 lectures/min — première surface sans compte) : résolution du QR (`référence → appareil`), signalement (retourne un **jeton de suivi opaque**), suivi par `référence + jeton` (pas de jeton, pas de lecture — aucune énumération possible).
|
||
- **Suivi sans compte** : `Request.publicToken` (migration `r2_portail`) ; le téléphone du gardien garde `[référence, jeton]` en localStorage (10 max) ; étapes sans jargon **Reçu → Intervention → Résolu** dérivées de l'OT lié ; un rejet s'affiche « Sans suite : {motif} ».
|
||
- **Page `/q/{réf}`** fidèle à l'écran 6 validé R0 (mobile d'abord) : équipement prérempli « détecté par le QR », gros interrupteur personne bloquée, description, photo annoncée (upload → R3), messages d'erreur en français métier (QR inconnu, throttling).
|
||
- **e2e Playwright** : LA boucle produit — le gardien signale sans compte sur `/q/A1`, l'équipe traite (approbation → démarrage → bilan → clôture via API), le gardien recharge et voit ✓ Résolu. 11 tests Playwright verts, 50 tests API, 52 opérations au contrat. Générateur OpenAPI : les paramètres non-`id` ne sont plus typés uuid.
|
||
|
||
**Décisions**
|
||
|
||
- Le portail n'expose JAMAIS de liste : lecture uniquement par jeton individuel.
|
||
- Throttling au niveau du contrôleur portail seulement (le reste de l'API est derrière JWT).
|
||
|
||
**R2 est fonctionnellement complète** (OT, bilan codé, demandes, portail QR, préventif, compteurs). Prochaine étape : recette R2 avec le référent (revue pixel ↔ maquettes R0+R2), déploiement, tag `release/r2`.
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — R2.3 : écrans web de l'exploitation
|
||
|
||
**Actions**
|
||
|
||
- **7 écrans/évolutions fidèles aux maquettes validées** : Liste OT (filtres, strie rouge, « immédiat »), **Fiche OT** (boutons de transition issus d'`allowedTransitions`, clôture grisée avec l'explication de la garde, bilan codé 6 selects alimentés par les référentiels, checklist Fait→N-A→à faire au clic, activité chronologique + commentaires, assignation, annulation motivée), Nouvel OT (interrupteur « personne bloquée » qui force la priorité), Demandes (table + panneau d'approbation, rejet en modale à motif), Préventif (4 tuiles réelles, bouton générer avec résumé « regénérer ne double rien », gabarits administrables), Compteurs (saisie + historique), **Tableau de bord réel** (bandeau urgence cliquable, KPIs, OT par statut, interventions récentes ; coûts et pannes par organe annoncés R3).
|
||
- **L'urgence traverse l'app** : chip pulsante dans la topbar (rafraîchie 60 s), badges de nav (urgences OT, demandes à traiter), bandeau dashboard, tête de liste. Fiche appareil : historique réel + accès compteurs. Accueil dédié aux rôles sans exploitation (Demandeur).
|
||
- **Trou de conception débusqué par l'e2e** : le Demandeur n'a pas `ASSETS.view` → son sélecteur d'équipement était vide. Correctif : `GET /assets/options` (authentification seule — les références sont affichées en cabine). 49 opérations au contrat.
|
||
- **Deux fragilités réelles corrigées dans la génération du préventif** : collision de référence (course avec une création d'OT) désormais RETENTÉE au lieu de sauter silencieusement un appareil ; appareil supprimé entre lecture et écriture toléré (P2003). 3 runs Jest complets consécutifs verts.
|
||
- **9 tests Playwright verts** dont la recette R2 officielle : Karim signale → Salma approuve+assigne → OT → démarrage → clôture bloquée visible → bilan → clôture → la demande de Karim passe « Résolue » ; + préventif idempotent via l'UI, urgence bout-en-bout, compteur croissant. 50 tests API (94,6 % / 78,9 %).
|
||
|
||
**Prochaine étape** : R2.4 — portail public QR (`/q/{ref}` → signalement prérempli sans compte, suivi sans jargon — écran validé R0), puis recette R2 + déploiement + tag.
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — R2.2 : préventif (génération idempotente) + compteurs
|
||
|
||
**Actions**
|
||
|
||
- **Modèle** (migration `r2_preventif_compteurs`) : `Asset.underContract` (contrat préventif, maquette fiche appareil) et `WorkOrder.periodKey` avec **unicité `[assetId, periodKey]` : l'idempotence de la génération est EN BASE**, pas seulement dans le code — deux générations concurrentes ne peuvent pas doubler une grille.
|
||
- **Génération mensuelle** (`POST /preventive/generate`, 200 idempotent) : une grille par appareil sous contrat ; tâches dues par périodicité **ancrée sur la mise en service** (mensuelles toujours dues) ; appareil sans historique préventif → OT « **Premier contrôle** » avec toutes les tâches ; échéance = fin de mois ; événement GENERATED tracé. Déclenchement manuel en R2 (bouton maquette) — l'automatisation cron/BullMQ viendra avec le durcissement production.
|
||
- Gabarits administrables (ajout/renommage/désactivation — un gabarit désactivé sort des générations suivantes) ; `GET /preventive/status` (générées, terminées, en retard, premiers contrôles du mois).
|
||
- **Compteurs** : `GET /assets/{id}/meters` (les 2 compteurs, relevés récents d'abord) ; `POST /assets/{id}/meter-readings` — **relevé strictement croissant**, refus motivé sinon.
|
||
- Contrat : +7 opérations (48). **50 tests verts** (couverture 94,7 % / 78 %) : idempotence (8 grilles seed, jamais 16), premier contrôle vs grille normale, mois anniversaire (mars 2031 pour A1 : les 3/6/12 mois tombent ensemble), gabarit désactivé exclu, compteur qui refuse de redescendre. Smoke test prod : juillet 2026 généré (9 grilles, 8 premiers contrôles), 2ᵉ appel à zéro.
|
||
|
||
**Leçon**
|
||
|
||
- Les specs Jest partagent la base en parallèle : les assertions e2e doivent porter sur **leurs propres données** (ici les 8 appareils du seed), jamais sur des comptages globaux.
|
||
|
||
**Prochaine étape** : R2.3 — écrans web de l'exploitation (liste/fiche OT, demandes+approbation, nouvel OT, préventif, checklist, compteurs, chip urgence + bandeau tableau de bord).
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — R2.1 : socle backend de l'exploitation
|
||
|
||
**Actions**
|
||
|
||
- **Modèle R2** (doc + migration `r2_exploitation`, 10 tables) : WorkOrder (référence séquentielle, horodatages par état), WorkOrderEvent (activité), Request (1-1 vers OT, motif de rejet), ReferenceValue (référentiels du bilan par champ), InterventionReport (6 FK nommées), TaskTemplate/ChecklistItem, Meter/MeterReading.
|
||
- **Contrat** : 15 opérations (41 total) — la **table des transitions** et les champs requis du bilan vivent dans `@siop/shared` (une seule loi pour l'API et l'UI) ; la fiche OT expose `allowedTransitions` et `closureBlockers` (messages métier).
|
||
- **API** : machine à états stricte (transition → DONE refusée si bilan incomplet OU checklist avec tâche sans réponse — messages « quoi faire », charte §7) ; approbation de demande = création d'OT lié en 1-1 (double traitement → 409) ; rejet à motif obligatoire ; **scoping « voir autre »** appliqué aux listes ET aux accès directs (404, pas de fuite) ; valeurs de bilan validées champ par champ.
|
||
- **Seed** : 31 valeurs de référentiels (6 champs), 8 gabarits de préventif (parachute réglementaire), 4 OT et 4 demandes rejouant la maquette, compteurs d'A1.
|
||
- **45 tests verts** (couverture 95 % / 79 % branches) dont la **recette officielle rejouée** : demande Karim → approbation Salma → OT assigné Ahmed → démarrage → clôture refusée sans bilan → bilan codé → clôture → Karim voit sa demande résolue. Smoke test build prod.
|
||
|
||
**Décisions**
|
||
|
||
- `POST` de transition/rejet répondent **200** (le contrat prime sur le défaut 201 de Nest — c'est le contrat qui a raison).
|
||
- La priorité « personne bloquée » trie en tête côté API : aucun client ne peut l'oublier.
|
||
|
||
**Prochaine étape** : R2.2 — génération mensuelle du préventif (BullMQ, idempotente, premier contrôle) + API compteurs, puis R2.3 écrans web.
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — R2 ouverte : maquettes des écrans manquants à valider
|
||
|
||
**Actions**
|
||
|
||
- **R2 « Exploitation » ouverte — design d'abord.** Les écrans cœur sont déjà validés depuis R0 (tableau de bord, liste OT, fiche OT avec bilan codé 6 champs et garde de clôture, portail demandeur) ; `maquette-r2.html` complète avec les **5 écrans manquants** : Demandes (file + panneau d'approbation : approuver → OT 1-1, rejeter avec motif requis), Nouvel OT (interrupteur « personne bloquée » explicite), Préventif (gabarits à périodicité — parachute marqué réglementaire — génération du mois **idempotente**, premiers contrôles), OT préventif (checklist Fait/N-A, clôture bloquée si tâche sans réponse), Compteurs (relevés croissants, historique).
|
||
- Rendu vérifié (5 écrans + thème sombre, zéro erreur).
|
||
|
||
**Décisions (proposées à la validation)**
|
||
|
||
- Statuts de DEMANDE distincts (Reçue / Approuvée→OT lié / Rejetée / Résolue) réutilisant la sémantique chromatique des statuts OT ; à trancher : est-ce une entorse à « réservés » de la charte ou une extension cohérente ?
|
||
- Un rejet de demande **exige un motif** (lisible côté portail, sans jargon).
|
||
- Génération mensuelle : automatique le 1ᵉʳ à 06 h + bouton manuel ; **regénérer ne crée que le manquant** ; appareil sans historique → OT « premier contrôle » (toutes les tâches).
|
||
- Relevé de compteur strictement croissant (refus sinon).
|
||
|
||
**⛔ Bloquant** : validation des maquettes R2 par le référent avant toute ligne de code R2.
|
||
**→ Levé le 16/07/2026 : maquettes R2 et les 4 décisions VALIDÉES par le référent** (statuts de demande = extension cohérente, actée). Lancement R2.1 (socle backend exploitation).
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — 🏁 R1 CLOSE : recettée, déployée, taguée
|
||
|
||
**Actions**
|
||
|
||
- **Recette R1 prononcée par le référent** (écrans ↔ maquette-r1, deux thèmes).
|
||
- **Déploiement vérifié en ligne** (<https://siop2.apps.enset.top>) : migration `r1_referentiel` + seed appliqués au boot du conteneur — 5 sites, 8 appareils, écrans /sites servis ; parcours API admin vérifié (locations, assets).
|
||
- DoD complète (recette, CI 6 jobs verts, revue pixel, production, journal) → **tag `release/r1`**.
|
||
|
||
**Prochaine étape** : R2 Exploitation. Maquettes R0 déjà validées pour tableau de bord, liste OT, fiche OT (bilan codé), portail demandeur ; il restera à maquetter le préventif (gabarits, grille du mois, checklist) avant tout code R2 — design d'abord.
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — R1.2 : écrans web du référentiel
|
||
|
||
**Actions**
|
||
|
||
- **7 écrans fidèles à maquette-r1.html** : Sites (liste + **carte Leaflet/OSM réelle**, pins maquette, création en modale avec position posée au clic), Fiche site (arbre site→zones, appareils, ajout de zone), Ascenseurs (table dense, filtres site/statut, strie rouge sur appareil à l'arrêt), Fiche appareil (écran validé R0 : identité, organes ± ajout/retrait, changement de statut, **QR réel** ; historique R2 et documents R3 annoncés sans simulation), Nouvel ascenseur (identité → rattachement → organes ligne à ligne), **Étiquette A6 imprimable** (`window.print` n'imprime qu'elle, objet papier même en sombre), Personnes & équipes (invitation → **lien d'activation affiché à copier** — pas d'email en R1, décision explicite ; renvoi de lien ; équipes), Catégories (ajout, renommage inline, désactivation).
|
||
- **Page /activation** (cible du lien d'invitation, moule visuel de la connexion) — la personne choisit son mot de passe et entre directement.
|
||
- **Navigation pilotée par la matrice** : les entrées livrées n'apparaissent que si `canView` (Catégories exige SETTINGS) ; boutons de création/édition conditionnés par `can()` — l'API re-vérifie de toute façon.
|
||
- **e2e Playwright** : la **recette R1 officielle rejouée intégralement** (site → zone → appareil+organe → étiquette → invitation → activation du compte → l'invité est connecté) + un parcours « la matrice pilote l'UI » (Technicien : lecture sans boutons) ; purge idempotente des données de test en globalSetup. 5 tests verts, 4 vitest, build prod, lint.
|
||
|
||
**Leçon**
|
||
|
||
- Paquet workspace CJS + Vite : les exports nommés runtime échouent (pas de pré-bundle des paquets liés) → alias Vite `@siop/shared` → **source TypeScript** ; l'API garde le dist CJS. Symptôme vicieux : app blanche, tests e2e tous rouges.
|
||
|
||
**Prochaine étape** : R1.3 — recette R1 avec le référent (revue pixel écrans ↔ maquette-r1), déploiement (CI → Dokploy), tag `release/r1`. Puis R2 Exploitation (maquettes déjà validées pour OT/demandes/portail).
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — R1.1 : socle backend du référentiel
|
||
|
||
**Actions**
|
||
|
||
- **Modèle R1** (doc + Prisma + migration `r1_referentiel`) : Category (kind EQUIPMENT/COMPONENT_TYPE), Location (site → zone, lat/lng + **colonne PostGIS générée** `geography(Point,4326)` + index GIST), Asset (statut d'équipement), AssetComponent (organe **sans emplacement par construction**), Team (m2m User), invitation sur User (token unique + expiration). Migration **autosuffisante** (`CREATE EXTENSION IF NOT EXISTS postgis`).
|
||
- **Contrat** : 21 nouvelles opérations (26 au total) — categories/locations/assets+organes/teams/users/roles/invitations/activate ; générateur OpenAPI étendu aux paramètres de chemin ; spec + client web régénérés dans le même commit.
|
||
- **API** : 4 nouveaux modules + gestion des personnes, tous sous `@RequirePermission` (la matrice décide) ; invariants en service : profondeur 2, kinds de catégories, catégorie jamais supprimée, lien d'activation 7 jours à usage unique qui connecte directement.
|
||
- **Seed** : parc de la maquette (5 sites + 8 zones, 8 appareils dont B2 à l'arrêt et M1 en maintenance, organes d'A1/B2, 9 catégories, 2 équipes).
|
||
- **36 tests verts** (couverture 96 % stmts / 85 % branches) : parcours de recette site→zone→appareil→organes, profondeur 3 refusée, matrice vivante (Technicien lit mais ne crée pas), invitation→activation complète (lien périmé/consommé/renvoyé). Smoke test sur build de prod : sites avec compteurs, fiche A1 et ses 4 organes.
|
||
- **CI** : bascule sur `postgis/postgis:18-3.6` (la migration R1 l'exige) — le moment anticipé dans le commentaire du workflow.
|
||
|
||
**Leçons**
|
||
|
||
- Migration modifiée après application locale ⇒ réaligner son checksum dans `_prisma_migrations` (ou reset) — d'où la règle : rendre la migration autosuffisante AVANT de l'appliquer.
|
||
- Un serveur `reuseExistingServer` de Playwright peut squatter :3000 et faire tester un dist périmé — tuer le port avant tout smoke test.
|
||
|
||
**Prochaine étape** : R1.2 `apps/web` — écrans Sites (+ carte Leaflet/OSM), Fiche site, Ascenseurs, Nouvel ascenseur, Étiquette QR, Personnes & équipes, Catégories, fidèles à maquette-r1.html.
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — R0 CLOSE (tag) · R1 ouverte : maquettes à valider
|
||
|
||
**Actions**
|
||
|
||
- **R0 close** : recette prononcée par le référent, tag `release/r0` poussé (DoD complète : CI verte, revue pixel, production en ligne, journal).
|
||
- **R1 « Référentiel » ouverte — design d'abord** (principe n°1) : `maquette-r1.html` produite dans le moule validé (CSS répliquée de `maquette-web.html`, mêmes tokens/typo/motifs) — **7 écrans** : Sites (liste + carte PostGIS), Fiche site (hiérarchie d'emplacements), Ascenseurs (liste, statuts d'équipement distincts des statuts OT), Nouvel ascenseur (identité → rattachement → organes), Étiquette QR imprimable (A6 papier, blanche même en thème sombre), Personnes & équipes (invitation par lien d'activation 7 jours, modale montrée), Catégories (référentiels administrables, jamais de suppression si utilisé).
|
||
- Rendu vérifié en Chrome headless : 7 écrans + 2 contrôles thème sombre, zéro erreur.
|
||
|
||
**Décisions (proposées à la validation)**
|
||
|
||
- Statuts d'équipement (En service / À l'arrêt / En maintenance) **distincts** des statuts OT ; l'appareil à l'arrêt porte la strie rouge.
|
||
- Un organe **n'a jamais d'emplacement propre** : il suit son appareil (contrainte en base, comme en v1).
|
||
- Invitation par **lien d'activation** (7 jours) — aucun mot de passe créé pour autrui ; compte inactif avant activation.
|
||
- Catégorie utilisée : renommage/désactivation seulement, jamais de suppression.
|
||
|
||
**⛔ Bloquant** : validation des maquettes R1 par le référent avant toute ligne de code applicatif R1.
|
||
**→ Levé le 16/07/2026 : maquettes R1 et les 4 décisions de conception VALIDÉES par le référent.** Lancement R1.1 (socle backend).
|
||
|
||
---
|
||
|
||
## 2026-07-16 — Pr. Daaif (+ Claude) — 🚀 R0 EN PRODUCTION : <https://siop2.apps.enset.top>
|
||
|
||
**Actions**
|
||
|
||
- Blocage de clone résolu (le provider « Custom » HTTPS n'avait pas d'identifiants sur dépôt privé) ; déploiement Dokploy réussi depuis GitHub, domaine posé sur `siop2-web:80`, certificat Let's Encrypt émis.
|
||
- **Vérification de l'instance en ligne** (commit `3f9d0d8`) : `/api/health` → `ok` (base, Redis, MinIO `up`) ; 7 comptes démo ; demo-login → `/users/me` (Salma Idrissi, Dispatcher, 10 lignes de matrice) ; `401` sans jeton ; fallback SPA sur `/design` ; HTTPS valide.
|
||
- Principe « déployer tôt » honoré : R0 est en ligne avant l'ouverture de R1.
|
||
|
||
**Reste à faire**
|
||
|
||
- Poser le secret `DOKPLOY_WEBHOOK_URL` (runbook §4) pour activer le déploiement continu — le job `deploy` saute proprement tant qu'il manque.
|
||
- Sauvegardes PostgreSQL côté Dokploy (runbook §5) à configurer.
|
||
- **Recette R0 avec le référent** sur l'instance en ligne (revue pixel écrans ↔ maquettes) → clôture du jalon R0 → ouverture R1 Référentiel.
|
||
|
||
## 2026-07-15 — Pr. Daaif (+ Claude) — R0.13 : Dockerfiles + runbook Dokploy
|
||
|
||
**Actions**
|
||
|
||
- **Image API** (multi-stage, node:24-slim) : `pnpm deploy --legacy --prod`, client Prisma régénéré dans l'arborescence déployée, entrypoint `prisma migrate deploy` → seed optionnel (`SEED_ON_START=true`, compilé en `dist/seed`) → API ; utilisateur non-root, HEALTHCHECK `/health`. `prisma` passe en dépendance de production (migrations au boot).
|
||
- **Image web** (nginx:alpine) : statique Vite + proxy `/api` → `${API_UPSTREAM}` (template envsubst) — même topologie que le proxy Vite de dev ; cache immuable sur `/assets`, `no-cache` sur `index.html`.
|
||
- **`infra/docker-compose.dokploy.yml`** : 5 services préfixés `siop2-`, secrets exigés (`:?`), profils production client / instance démo documentés dans le fichier ; seul `siop2-web` rejoint `dokploy-network` (l'API n'est jamais exposée).
|
||
- **Runbook** `docs/06-production/runbook-dokploy.md` : topologie, variables, checklist de première mise en production, releases suivantes, rollback, répétition locale.
|
||
- **Répétition locale validée** : images construites, conteneurs lancés sur le réseau infra (migrate + seed au boot), parcours complet vérifié à travers nginx conteneurisé (`/api/health` tout `up`, demo-login → `/users/me`, fallback SPA).
|
||
|
||
**Leçons de la répétition (consignées pour le playbook)**
|
||
|
||
- **Prisma en conteneur** : `binaryTargets` explicites dans `schema.prisma` (`debian-openssl-3.0.x` x64 serveur + `linux-arm64-openssl-3.0.x` répétition Mac) — sinon mismatch de moteur au runtime.
|
||
- **nginx** : upstream résolu **à la requête** (resolver `127.0.0.11` + variable) et non au boot — sinon nginx refuse de démarrer si l'API n'est pas encore là.
|
||
- **Healthcheck alpine** : `127.0.0.1` et non `localhost` (busybox wget tente ::1, nginx écoute en IPv4).
|
||
- **Le double verrou ADR-002 a été prouvé en vraie situation** : l'image (NODE_ENV=production) avec `DEMO_MODE=true` sans `DEMO_MODE_I_KNOW` **refuse de démarrer** — comportement observé, pas seulement testé.
|
||
|
||
**Décisions**
|
||
|
||
- Migrations **au démarrage du conteneur** (idempotentes) : la base suit toujours le code déployé ; un échec de migration arrête l'API sans servir de trafic.
|
||
- Le web est l'unique service exposé (domaine → nginx → proxy interne `/api`) : mêmes chemins en dev et en prod, surface d'attaque minimale.
|
||
|
||
**Statut** : R0.13 prêt — la première exécution réelle attend les **accès au serveur du partenaire**. Reste pour clore R0 : recette avec le référent (revue pixel écrans ↔ maquettes).
|
||
|
||
---
|
||
|
||
## 2026-07-15 — Pr. Daaif (+ Claude) — R0.13 (suite) : instance ENSET + déploiement continu
|
||
|
||
**Actions**
|
||
|
||
- **Instance de démonstration décidée** : `https://siop2.apps.enset.top` (Dokploy ENSET, projet compose créé par le référent) — la production client SPELEV suivra la même procédure avec le profil « production client ».
|
||
- Job **`deploy`** ajouté au pipeline : appelle le webhook Dokploy sur push `main` **uniquement si lint + contrat + api + web + e2e sont verts** ; « skip » explicite tant que le secret `DOKPLOY_WEBHOOK_URL` n'est pas configuré. L'« Auto Deploy » natif de Dokploy reste désactivé (il ignorerait la CI).
|
||
- Runbook §3 réécrit en checklist concrète (source GitHub, Compose Path, variables du profil démo, domaine → `siop2-web:80`) et §4 en procédure CD (secret webhook).
|
||
|
||
**Prochaine étape** : premier déploiement manuel (runbook §3), pose du secret `DOKPLOY_WEBHOOK_URL` (§4), vérification `https://siop2.apps.enset.top/api/health`, puis recette R0 avec le référent sur l'instance en ligne.
|
||
|
||
---
|
||
|
||
## 2026-07-15 — Pr. Daaif (+ Claude) — R0.12 (2/2) : ESLint + parcours Playwright — R0.12 CLOS
|
||
|
||
**Actions**
|
||
|
||
- **ESLint 10** (flat config unique à la racine, typescript-eslint, react-hooks sur le web) + scripts `lint` par paquet et job CI dédié. La règle d'architecture ADR-001 est codée : `no-restricted-imports` interdit `minio` partout **sauf** `minio-storage.service.ts` — violation vérifiée par un fichier-test (détectée puis retiré). Monorepo lint : 0 erreur.
|
||
- **Playwright** (`apps/web/e2e`) : 3 tests — redirection sans jeton (fermée par défaut), parcours complet connexion démo → coquille (rail actif, chip DÉMO) → **bascule de rôle chronométrée < 3 s** (ADR-002) → /design, et déconnexion avec purge de session. `webServer` démarre l'API construite + Vite ; seed idempotent en globalSetup ; localement `PW_CHANNEL=chrome` (pas de téléchargement).
|
||
- CI : jobs `lint` et `e2e` (services PostgreSQL/Redis, artefact playwright-report en cas d'échec). Vitest restreint à `src/` (e2e appartient à Playwright).
|
||
|
||
**Décisions**
|
||
|
||
- Une seule config ESLint à la racine (pas une par app) : les règles d'architecture sont transversales et la config est le lieu du cours.
|
||
|
||
**Prochaine étape** : R0.13 — Dockerfiles (api, web) + runbook Dokploy (`docs/04-*`), puis recette R0 avec le référent (revue pixel écrans ↔ maquettes).
|
||
|
||
---
|
||
|
||
## 2026-07-15 — Pr. Daaif (+ Claude) — R0.12 (1/2) : pipeline GitHub Actions
|
||
|
||
**Actions**
|
||
|
||
- `.github/workflows/ci.yml`, 3 jobs sur push main + PR : **ci-contract** (régénère `docs/openapi.json` + `schema.d.ts`, échoue au moindre diff — la règle d'or devient bloquante), **api** (PostgreSQL 18 + Redis en services, `prisma migrate deploy`, typecheck, Jest avec **couverture ≥ 70 % bloquante** via `coverageThreshold`), **web** (typecheck + vitest + build prod).
|
||
- Test unitaire de `PermissionsGuard` ajouté (aucune route `@RequirePermission` en R0 ne l'exerçait) : couverture 97,5 % stmts / 90,7 % branches, 23 tests.
|
||
- Badge CI dans le README ; `ci-contract` simulé en local (diff propre) avant push.
|
||
|
||
**Décisions**
|
||
|
||
- CI sur PostgreSQL nu en R0 (la migration `r0_identity` n'exige aucune extension) ; bascule sur l'image `infra/postgres` dès que des tests toucheront pgvector/PostGIS (R1+).
|
||
- Pas de MinIO en CI : `/health` répond `degraded` sans casser les tests — seul `database: up` est exigé.
|
||
|
||
**Prochaine étape** : R0.12 (2/2) — ESLint (dont la règle « pas d'import MinIO hors FileStorage »), parcours Playwright (connexion démo → coquille → bascule de rôle), puis R0.13 Dockerfiles + runbook Dokploy.
|
||
|
||
---
|
||
|
||
## 2026-07-15 — Pr. Daaif (+ Claude) — R0.11 : apps/web (connexion + sélecteur démo, coquille, /design)
|
||
|
||
**Actions**
|
||
|
||
- `apps/web` (React 19 + Vite + Tailwind v4) : `tokens.css` copié tel quel depuis 02-design ; classes composants **extraites de la maquette validée** ; Manrope auto-hébergée (@fontsource) ; primitives shadcn-style possédées (Button/variants cva, menu Radix).
|
||
- **Client typé** : `pnpm generate:client` → `src/api/schema.d.ts` généré depuis `docs/openapi.json` et committé (règle d'or) ; openapi-fetch + injection du jeton.
|
||
- Écrans : **connexion** (fidèle maquette, liste démo masquée si l'API répond 404 — ADR-002), **coquille** sidebar (4 groupes, rail safran actif, écrans à venir marqués R1-R3) + topbar (recherche ⌘K, bascule de thème, chip DÉMO, **sélecteur de rôle** dans le menu compte), **/design** (référence vivante : nuanciers, typo, boutons, pastilles), tableau de bord R0 minimal.
|
||
- Compléments : tokens `--nav-*` ajoutés à `tokens.css` (valeurs issues de la maquette validée) ; **seed réaligné sur les personas de la maquette** (Salma Idrissi, Ahmed Benali, …) pour des revues pixel cohérentes.
|
||
- **Vérifié dans un vrai navigateur** (Playwright + Chrome headless) : parcours démo-login → tableau de bord → bascule Dispatcher→Technicien en **101 ms** (critère < 3 s) → /design clair & sombre ; 7 captures, zéro erreur console. Typecheck, 3 tests vitest, build prod OK.
|
||
|
||
**Décisions**
|
||
|
||
- Le web ne lit pas `VITE_DEMO_MODE` : la présence du mode démo est déduite de la réponse de `/auth/demo-accounts` (404 ⇒ rien n'est affiché) — une seule source de vérité, l'API.
|
||
- Les entrées de navigation des releases futures restent visibles mais inertes, marquées R1/R2/R3 — le périmètre est annoncé, pas simulé.
|
||
|
||
**Prochaine étape** : R0.12 — CI GitHub Actions (lint, typecheck, tests, couverture api ≥ 70 %, `ci-contract` sur openapi.json/clients, Playwright du parcours démo) puis R0.13 Dockerfiles + runbook Dokploy.
|
||
|
||
---
|
||
|
||
## 2026-07-15 — Pr. Daaif (+ Claude) — R0.10 : apps/api complète (auth, matrice, démo-login, seed)
|
||
|
||
**Actions**
|
||
|
||
- `packages/shared` : vocabulaires (7 rôles, 10 catégories d'objets), schémas Zod (auth, profil, santé) et **contrat d'API** ; `pnpm contract` génère `docs/openapi.json` (committée — règle d'or ADR-001).
|
||
- `apps/api` (NestJS 11 + Prisma 6) : migration `r0_identity` (Role/Permission/User) ; **guard JWT global fermé par défaut** (+ `@Public()` explicite) ; **PermissionsGuard** (`@RequirePermission`, matrice relue en base, cache 60 s) ; `FileStorage` (seul point d'import MinIO) ; `/health` (base, Redis, stockage).
|
||
- **Démo-login ADR-002** : module enregistré uniquement si `DEMO_MODE=true` (sinon routes **404**), double verrou production (`DEMO_MODE_I_KNOW`), refus des comptes `isDemo=false`.
|
||
- **Seed idempotent** : 7 rôles, matrice complète (70 lignes), 7 comptes démo (mot de passe commun `SEED_DEMO_PASSWORD` pour la connexion classique).
|
||
- **Vérifié bout-en-bout** : 19 tests Jest verts (dont e2e démo on/off) ; smoke test sur build de prod — démo-login → `/users/me` avec matrice, 401 sans jeton, health `ok`.
|
||
|
||
**Décisions**
|
||
|
||
- `AppModule.forRoot()` (module dynamique) pour rendre l'enregistrement conditionnel du module démo **testable dans les deux états** — le e2e « routes absentes » est l'exigence n°1 de l'ADR-002.
|
||
- Le seed n'écrase jamais une ligne de matrice existante : **la base est la source de vérité des droits**, le fichier n'est que le point de départ.
|
||
|
||
**Prochaine étape** : R0.11 `apps/web` — login + sélecteur de comptes démo (fidèle à maquette-web.html), coquille sidebar/topbar avec bandeau « DÉMO », page /design, client typé généré depuis `docs/openapi.json`.
|
||
|
||
---
|
||
|
||
## 2026-07-15 — Pr. Daaif (+ Claude) — R0 : maquettes VALIDÉES ; architecture + squelette
|
||
|
||
**Actions**
|
||
|
||
- **Maquettes HD validées par le référent** → le design est la loi des revues pixel.
|
||
- 03-architecture : ADR-001 (stack), ADR-002 (démo-login `DEMO_MODE`, double verrou prod), vue C4, modèle de données R0 (Role/Permission/User, `isDemo`).
|
||
- Racine monorepo (pnpm + turbo, Node 24) ; `infra/` : compose local (PostgreSQL 18 pgvector+PostGIS via Dockerfile dédié, Redis, MinIO).
|
||
|
||
**Décisions**
|
||
|
||
- **Convention `siop2-`** pour tous services/conteneurs Docker (référent — collisions sur le réseau partagé Dokploy en v1).
|
||
|
||
**Prochaine étape (reprise)** : R0.10 `apps/api` (auth JWT + matrice permissions + démo-login + seed) → R0.11 `apps/web` (login + sélecteur démo + coquille + /design) → R0.12 CI → R0.13 prépa Dokploy.
|
||
|
||
---
|
||
|
||
## 2026-07-15 — Pr. Daaif (+ Claude) — R0 : kickoff, vision, cadrage, design
|
||
|
||
**Actions**
|
||
|
||
- **Refondation décidée** : nouveau dépôt `siop-spelev/siop2`, 100 % nouveau code ; la v1 (`siop`) est gelée comme référence. Jalons R0→R5 créés.
|
||
- Playbook initialisé : **00-vision** (charte projet, personas, benchmark Atlas), **01-cadrage** (plan de releases avec DoD, user-story map, exigences non fonctionnelles mesurables, registre des risques).
|
||
- **02-design** : charte graphique (bleu-treuil / safran / acier, Manrope, motif « rail »), `tokens.css` (source de vérité, bi-thème), palettes analytics **validées par script** (CVD + contraste, modes clair et sombre), **maquettes HD** navigables (7 écrans : connexion + démo-login, tableau de bord, liste OT, fiche OT avec bilan codé, fiche ascenseur + QR, portail demandeur, mobile technicien) — publiées pour revue.
|
||
|
||
**Décisions (référent, via questions)**
|
||
|
||
- Diagnostic v1 : esthétique absente, docs éparpillées, périmètre dérivant, bascule de comptes pénible → V2 **design-first**, playbook en livre, releases fermées, **sélecteur de compte démo** (`DEMO_MODE`) dès R0.
|
||
- 100 % nouveau code ; périmètre v1 = web + mobile + IA par paliers ; déploiement Dokploy ; nom conservé : **SIOP**.
|
||
|
||
**Prochaine étape** : validation de la charte + maquettes par le référent (**bloquant** — aucun code applicatif avant), puis 03-architecture + squelette monorepo.
|
||
|
||
---
|