Commit Graph

26 Commits

Author SHA1 Message Date
pr-daaif
28eecc1fb9 feat(r5.3): écrans IA — assistant web, corpus administrable, suggestions OT/mobile
- Contrat (76 opérations) : Document expose inCorpus/indexedAt/chunkCount,
  PATCH /documents/{id}/corpus (ASSETS.edit), POST /assistant/reindex
  (bilan chiffré) ; ci-contract vérifie désormais aussi le client mobile.
- Web : page /assistant (chat sourcé — extraits exacts cités, Ouvrir vers
  PDF authentifié ou fiche OT, avertissement permanent ; refus honnête
  chiffré avec action utile) ; Bibliothèque = corpus (bandeau 09-08,
  statut d'indexation par document, interrupteur d'exclusion PDF,
  Réindexer tout) ; fiche OT : « Décrire pour suggérer » (Appliquer =
  geste humain, liseré « suggéré » retiré au choix manuel).
- Mobile : chips de suggestion dans la clôture (un appui = un champ
  pré-rempli, « réseau requis » hors-ligne — la file R4 n'en dépend pas).
- apps/ai : seuils AI_SEUIL_* configurables par env (CI + calibrage).
- CI e2e : service siop2-ai (embeddeur déterministe, seuils calibrés sur
  mesures : match 0,66 vs bruit 0,11) + parcours R5 Playwright (PDF généré
  xref valide → réindexation → réponse sourcée → refus → suggestion).
- Vérifié : 16/16 Playwright, 78 tests API, 23 pytest, 17 jest-expo ;
  chaîne réelle au vrai modèle ONNX (web 7/7, mobile Expo web 6/6).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:26:22 +01:00
pr-daaif
45ae491827 feat(r5.2): assistant au contrat + suggestion de codes de bilan
apps/ai : /internal/ask — seuil de pertinence, extraits sourcés ou
refus honnête portant la taille du corpus cherché (D2), rédaction via
le Generateur opt-in ; /internal/suggest — similarité sémantique entre
la description libre et les libellés ACTIFS des référentiels, un code
par champ, confiance FORTE/MOYENNE, « N bilans similaires sur ce
parc ». Sans LLM : déterministe, explicable. 23 pytest.

Contrat (74 opérations) : POST /assistant/ask → AssistantAnswer
(EXTRACTIVE/GENERATED/REFUSAL, extraits cités, corpus cherché) et
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, ADR-004 §4), permissions matrice (ask=view,
suggest=edit), traduction interne→contrat, 503 propre si service
éteint. 6 e2e sur stub HTTP (76 tests API).

Bug débusqué par la vraie chaîne : fastembed ne norme pas ses
vecteurs — la similarité des suggestions dépassait 1 (pgvector
normalisait dans son opérateur, masquant l'écart). Normalisation à
l'encodage + réindexation : bilans en tête (0.41), refus hors corpus,
scores cosinus ≤ 1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:05:08 +01:00
pr-daaif
76c2ccdfb1 feat(r5.1): clé API de génération configurable (ADR-004 §3 en actes)
Demande du référent : la génération opt-in devient réellement
configurable. AI_GENERATION=off|api, AI_API_KEY (exigée en mode api —
le boot refuse sinon, jamais loguée, /healthz n'expose que le mode),
AI_MODEL (défaut claude-opus-4-8).

generation.py : interface Generateur — GenerateurExtractif (contrat de
base sans LLM) et GenerateurAPI (SDK officiel anthropic, dépendance
optionnelle --extra generation, absente des tests/CI). Consigne :
citations [n] obligatoires depuis les extraits anonymisés, jamais
d'invention, rappel de validation humaine. Tout échec (refus du
modèle, quota, réseau) retombe silencieusement sur l'extractif.

5 tests ajoutés (19 pytest) + .env.example. Vérifié en réel : boot
refusé api-sans-clé, générateur construit, healthz sans secret.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 14:38:36 +01:00
pr-daaif
d4d73a6f76 fix(ci): PostgreSQL de CI = l'image infra (pgvector + PostGIS)
La migration r5_ia exige pgvector, absent de postgis/postgis — la
bascule annoncée en R1 (« pgvector arrivera en R5 ») est faite : les
jobs api et e2e construisent infra/postgres et le démarrent par étape
(les services GitHub ne savent pas builder). La migration crée aussi
l'extension elle-même (idempotent, même patron que PostGIS en r1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:27:03 +01:00
pr-daaif
837dcba1db feat(r5.1): socle apps/ai — ingestion anonymisée + recherche sémantique
ADR-004 : 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
(RagChunk possédé par Prisma, migration r5_ia + état de corpus sur
Document), génération opt-in (mode extractif par défaut : la recette
passe sans clé API), service siop2-ai jamais exposé — joint par l'API
NestJS seule (X-Service-Token).

apps/ai (FastAPI + uv) : pipeline PDF MinIO → texte paginé (pypdf) →
anonymisation D4 (e-mails, téléphones marocains, noms connus de la
base, insensible casse/accents — fonction pure testée) → découpage
avec chevauchement (testé) → embeddings → RagChunk localisé (« p. 42 »,
« bilan du 17/07 »). Bilans codés clôturés ingérés. Exclusion de
corpus (D3) appliquée à l'ingestion ET à la lecture.

14 pytest + ruff, embeddeur déterministe en CI (aucun téléchargement),
job CI ai (uv), deploy en dépend. Vérifié en réel avec le vrai modèle :
corpus seedé réindexé en 7 s (PDF réel → 31 extraits paginés + 3
bilans), recherche sémantique concluante, e-mails → ⟨contact⟩,
0 identité dans les chunks (contrôle SQL).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:22:15 +01:00
pr-daaif
c8b3c1769a feat(r4.3): file d'écriture, verrou optimiste D2, Synchro & conflits
Serveur : updatedAt exposé au détail OT ; baseUpdatedAt optionnel sur
transition/coche/bilan → 409 « Conflit de version » contextualisé (qui,
quand) ; toute écriture secondaire (coche, bilan, commentaire, conso,
MO) fait avancer la version — sans version fournie, le web est
inchangé. ADR-003 : sécurité & protocole de routage mobile (qui vit où
sur l'appareil, purge complète à la déconnexion — correctif réel : la
file et le cache persisté survivaient au logout).

Mobile : file persistée AsyncStorage rejouée dans l'ordre — succès →
propagation de la version fraîche aux saisies restantes du même OT
(nos écritures ne se conflictent pas entre elles, un écart étranger
reste détecté) ; coupure → tout attend ; refus → CONFLIT, la file
s'arrête, l'humain tranche (voir l'OT / rejouer sur version à jour /
abandonner). Transitions, coches, bilan et photos (D5, compressées
~1600 px) passent par la file avec patch optimiste du cache ; écran
Synchro (badge tabbar ambre/rouge) ; préchargement parc + référentiels
(le bilan hors-ligne a ses vocabulaires).

Recette « mode avion » 13/13 en Expo web piloté : gestes hors-ligne →
3 en file → modification concurrente de Salma → conflit tranché →
serveur Terminé avec bilan. 17 tests jest-expo, 74 tests API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 09:50:02 +01:00
pr-daaif
a22ea60f83 feat(r4.2): terrain mobile — scan QR, fiches ascenseur/OT, clôture, grille
Scanner expo-camera (QR seulement) : analyseScan testée — URL portail
…/q/REF des étiquettes A6 ou référence tapée, QR étrangers refusés ;
résolution D4 dans le parc en cache d'abord (sous-sol inclus), message
honnête sinon. Fiche ascenseur en consultation (D3, historique scopé
« voir autre »). Fiche OT : un bouton principal selon la machine à
états, coûts figés, garde alimentée par closureBlockers (source unique
API). Clôture terrain : bilan codé 6 champs au pouce (sélecteur plein
écran), garde visible, clôture en ligne. Préventif : mes grilles →
checklist cochable, appui long = N/A, aria-checked (leçon RN web :
accessibilityState.checked ne produit pas aria-checked).

Écritures en ligne assumées en R4.2 (bandeaux hors-ligne explicites) —
la file persistée et le verrou optimiste sont R4.3 (D1/D2).

Vérifié 12/12 en Expo web piloté : scan → fiche → OT-0341 (505 MAD,
garde), coche/décoche restituée, OT de test Démarrer → bilan → Terminé,
zéro erreur console, données purgées. 12 tests jest-expo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 03:47:04 +01:00
pr-daaif
5c8d5aac92 feat(r4.1): socle mobile Expo — connexion démo, Ma journée, lecture hors-ligne
apps/mobile (Expo SDK 57, TS strict, workspace pnpm) : connexion
e-mail/mdp + sélecteur démo ADR-002 (uniquement si l'API l'expose),
tabbar de la maquette validée (onglets futurs marqués R4.2/R4.3),
« Ma journée » triée priorité puis échéance (fonction pure testée),
urgence en tête, pastille de synchro.

D1 en actes (lecture) : cache TanStack persisté dans AsyncStorage
(7 jours), NetInfo → onlineManager + bandeau hors-ligne horodaté ;
jeton en SecureStore ; tokens light/dark répliqués et testés ; client
typé régénéré depuis docs/openapi.json (règle d'or).

API : CORS_ORIGINS opt-in (vide par défaut) — Expo web/debug seulement,
le web de prod reste derrière le proxy, les apps natives n'ont pas
d'Origin.

Vérifié 10/10 en Expo web piloté (connexion démo Ahmed → Ma journée
scopée → hors-ligne servie du cache → retour) ; 6 tests jest-expo ;
lint racine étendu (react-hooks) ; job CI mobile, deploy en dépend.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 03:12:34 +01:00
pr-daaif
460ef4a80e feat(r3.4): corrections de recette (8 écarts) + recherche globale ⌘K
Arbitrage du référent sur la revue pixel : tout corriger, activer la
recherche.

- Stock : filtre fournisseur, sous-seuil en tête, « Entrée de stock »
  depuis la liste ; fiche pièce : fournisseur → lien Tiers.
- Statistiques : période 3/6/12 mois (paramètre months au contrat).
- Tiers : rattachements syndic→site (migration r3_recette_fixes,
  Location.partnerId gardé CLIENT), éditable sur la fiche site, seedé.
- Bibliothèque : filtre « Rattaché à » + glisser-déposer (modale
  préremplie, rattachement toujours requis).
- Recherche globale : GET /search (73 opérations) — familles OT/
  ascenseurs/sites filtrées par la matrice, « voir autre » respecté ;
  topbar ⌘K, debounce, résultats groupés, navigation clavier.

74 tests API (8 nouveaux sur le scoping de la recherche), 14/14
Playwright dont un parcours « recette corrigée », 18/18 contrôles en
navigateur réel, zéro erreur console.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 02:14:43 +01:00
pr-daaif
c46a97ed08 feat(r3.3): aperçu réel des documents + ouverture dans le navigateur
Retour de pré-recette : les fichiers téléversés n'avaient ni aperçu ni
ouverture. Les images affichent leur vraie vignette (blob authentifié →
URL objet révoquée au démontage — un <img src> nu ne porte pas le jeton) ;
« Ouvrir » (bouton + clic vignette) affiche PDF et images dans un nouvel
onglet, ouvert dans le geste utilisateur pour passer les bloqueurs de
pop-up. « Télécharger » inchangé.

Vérifié en navigateur réel : vignette chargée, onglets blob (PDF rendu
dans la visionneuse), download intact, zéro erreur console.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 22:44:22 +01:00
pr-daaif
c5382a5538 feat(r3.3): écrans web de la gestion — stock, achats, coûts OT, documents, statistiques
- Stock : alertes sous seuil (strie ambre), « Préparer le BC » sur la ligne ;
  fiche pièce où les mouvements SONT le stock (entrée / ajustement motivé)
- Bons de commande : liste + panneau (envoi, annulation, réception → entrées
  de stock à PU figé) ; création préremplie depuis l'alerte de seuil
- Fiche OT : carte « Pièces & main-d'œuvre » réelle (consommation à prix figé
  annoncé avant le clic, temps au taux figé) + carte Documents (upload/download) ;
  même carte Documents sur la fiche ascenseur
- Statistiques : 4 KPI, pannes par organe (bilans codés), coûts par mois,
  top équipements ; le tableau de bord allume « Pannes par organe »
- Tiers, bibliothèque (upload rattaché, filtre par type), taux horaire
  éditable dans Personnes ; nav Ressources/Pilotage activée par la matrice
- Recette R3 Playwright (2 parcours autonomes) ; cleanup-e2e étendu aux
  entités R3 ; le formulaire BC attend la fin du refetch (stock périmé en cache)

13/13 e2e verts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 22:16:56 +01:00
pr-daaif
2ffdd13cf4 feat(r3.2): bibliothèque de documents (FileStorage réel) + analytics
- FileStorage : putObject/getObjectStream/removeObject, bucket créé au
  démarrage — MinIO confiné à son implémentation (règle ESLint intacte)
- documents : 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é),
  suppression ; e2e : octets téléchargés identiques aux octets envoyés
- analytics : GET /analytics/summary dérivé du réel — coûts/mois
  (mouvements + main-d'œuvre figés), pannes par organe (bilans codés),
  taux de préventif, durée moyenne de résolution, top équipements
- générateur OpenAPI : query params, multipart, réponse binaire (71 ops)
- CI : service MinIO (bitnami) sur les jobs api et e2e
- test de régression du tri « Interventions récentes » rendu déterministe
  (positions absolues instables sous 12 suites parallèles) ; 58 tests,
  6 runs complets consécutifs verts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:24:04 +01:00
pr-daaif
32eb20b5a0 fix(web): connexion — signaler clairement une API injoignable
Le formulaire email/mot de passe s'affichait seul, sans explication, quand
l'API locale n'était pas démarrée (la liste démo était masquée comme si
DEMO_MODE était absent). Une erreur réseau affiche désormais « L'API ne
répond pas — démarrez-la avec pnpm dev » ; le 404 (pas de mode démo)
reste silencieux, c'est le comportement ADR-002.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:11:46 +01:00
pr-daaif
dfdf8f7c18 feat(r3.1): socle backend gestion — stock dérivé, BC, coûts figés sur OT
- migration r3_gestion (7 tables) : Partner, Part (SANS colonne de
  quantité), StockMovement (signé, tracé, PU figé), PurchaseOrder/Line,
  LaborTime (taux figé), Document (R3.2) ; User.hourlyRate administrable
- API (66 opérations) : tiers ; pièces (stock = Σ mouvements, alerte sous
  seuil) ; entrée/ajustement (motif requis, stock jamais négatif, 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É) ; main-d'œuvre (TAUX FIGÉ, refus si taux non défini) ;
  WorkOrderDetail.costs ; coûts verrouillés après clôture (409)
- seed : tiers/pièces/BC/taux de la maquette — OT-0341 = 505 MAD (testé)
- durcissement : références OT/DEM/BC/P par SÉQUENCES Postgres (nextval)
  — fin des courses « max+1 » (500 sporadiques sous charge parallèle) ;
  3 runs Jest complets consécutifs verts
- 55 tests (92 % stmts / 74,9 % branches) dont la recette officielle :
  consommer sous seuil → BC → réception → réappro, prix/taux figés prouvés

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 20:42:03 +01:00
pr-daaif
f7c0da1b0f fix(r2): un OT clôturé disparaissait des « Interventions récentes » des rôles voyant tout
Anomalie de recette (référent) : OT assigné à Ahmed, traité, clôturé —
visible dans son tableau de bord mais pas dans celui de Salma ni de
l'admin. Cause : liste triée par statut (Terminés en queue) alors que le
tableau de bord prend les 5 premières lignes ; les listes scopées
(technicien) sont courtes, celles des rôles « voir autre » non.

- tri par dernière activité (updatedAt desc) : un OT fraîchement clôturé
  remonte en tête pour tous
- urgences « personne bloquée » ACTIVES épinglées au sommet (une urgence
  annulée ne squatte plus la tête — corrigé au passage)
- test de régression dans la recette e2e (position ≤ urgences actives)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 18:01:17 +01:00
pr-daaif
50254daea4 feat(r2.4): portail public QR — signalement sans compte, suivi par jeton
- 3 routes publiques /portal (les seules @Public métier), throttlées
  (@nestjs/throttler : 10 signalements/min, 60 lectures/min) : résolution
  du QR, signalement (retourne un jeton de suivi opaque), suivi par
  référence + jeton — pas de jeton, pas de lecture, jamais de liste
- migration r2_portail : Request.publicToken (unique)
- page /q/{réf} fidèle à l'écran 6 validé R0 (mobile d'abord) : équipement
  prérempli « détecté par le QR », interrupteur personne bloquée,
  suivi sans jargon Reçu → Intervention → Résolu (dérivé de l'OT lié),
  rejet affiché « Sans suite : {motif} », photo annoncée (upload R3) ;
  signalements gardés sur le téléphone (localStorage, 10 max)
- e2e : LA boucle produit — gardien sans compte → traitement → ✓ Résolu ;
  11 tests Playwright verts, 50 tests API, 52 opérations au contrat
- générateur OpenAPI : paramètres non-« id » plus typés uuid

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:33:10 +01:00
pr-daaif
6a2dae1710 feat(r2.3): écrans web exploitation — OT, demandes, préventif, compteurs, urgence
- liste OT (filtres, strie rouge, « immédiat ») ; fiche OT : transitions
  pilotées par allowedTransitions, clôture grisée avec la garde expliquée,
  bilan codé (6 selects sur référentiels), checklist Fait→N-A→à faire,
  activité + commentaires, assignation, annulation motivée
- nouvel OT : interrupteur « personne bloquée » qui force la priorité
- demandes : table + panneau d'approbation (priorité, assignation,
  approuver → fiche OT), rejet en modale à motif obligatoire, signalement
  interne ; statut « Résolue » dérivé de l'OT lié
- préventif : tuiles réelles, générer + 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 ; accueil dédié aux rôles sans exploitation
- urgence traversante : chip topbar pulsante (60 s), badges de nav
- GET /assets/options (auth seule) : le Demandeur peut désigner l'appareil
  qu'il signale — trou débusqué par l'e2e (49 opérations au contrat)
- génération préventive durcie : collision de référence RETENTÉE (plus de
  saut silencieux), P2003 toléré ; 3 runs Jest complets consécutifs verts
- 9 tests Playwright (recette R2 officielle rejouée intégralement),
  50 tests API (94,6 % / 78,9 %)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:13:20 +01:00
pr-daaif
902efa6192 feat(r2.2): préventif — génération mensuelle idempotente — et compteurs
- migration : Asset.underContract + WorkOrder.periodKey avec unicité
  [assetId, periodKey] — l'idempotence de la génération est EN BASE
  (deux appels concurrents ne doublent jamais une grille)
- génération : une grille par appareil sous contrat, périodicités ancrées
  sur la mise en service (mensuelles toujours dues), premier contrôle
  (toutes les tâches) pour un appareil sans historique, échéance fin de
  mois, événement GENERATED ; déclenchement manuel (cron au durcissement)
- gabarits administrables (désactivé = exclu des générations suivantes) ;
  GET /preventive/status (générées, terminées, en retard, premiers contrôles)
- compteurs : GET meters (2 compteurs, récents d'abord) ; relevé
  strictement croissant, refus motivé sinon
- contrat +7 opérations (48) ; 50 tests verts (94,7 % / 78 %) ; smoke test
  prod : juillet généré (9 grilles, 8 premiers contrôles), 2e appel à zéro

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 15:15:57 +01:00
pr-daaif
f7702e4252 feat(r2.1): socle backend exploitation — machine à états, bilan codé, demandes 1-1
- migration r2_exploitation (10 tables) : WorkOrder (référence séquentielle,
  horodatages), WorkOrderEvent, Request (1-1, motif de rejet), ReferenceValue,
  InterventionReport (6 FK), TaskTemplate/ChecklistItem, Meter/MeterReading
- contrat : 15 opérations (41 total) ; la table des transitions et les champs
  requis du bilan vivent dans @siop/shared ; la fiche OT expose
  allowedTransitions + closureBlockers (messages métier)
- API : machine à états stricte ; garde de clôture (bilan 3 champs requis +
  checklist sans tâche en attente) ; approbation → OT lié 1-1 (409 si déjà
  traitée) ; rejet à motif obligatoire ; scoping « voir autre » sur listes et
  accès directs (404 sans fuite) ; validation des valeurs de bilan par champ ;
  « personne bloquée » triée en tête côté API
- seed : 31 valeurs de référentiels, 8 gabarits (parachute réglementaire),
  OT/demandes/compteurs de la maquette — idempotent
- 45 tests verts (95 % stmts / 79 % branches) dont la recette officielle
  rejouée de bout en bout ; smoke test sur build de prod

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 13:56:07 +01:00
pr-daaif
9778629827 feat(r1.2): écrans web du référentiel — 7 écrans fidèles à maquette-r1
- Sites : liste + carte Leaflet/OSM réelle (pins maquette, positions
  PostGIS), création en modale avec position posée au clic sur la carte
- Fiche site : arbre site→zones, appareils rattachés, ajout de zone
- Ascenseurs : table dense, filtres site/statut, strie rouge à l'arrêt ;
  fiche appareil (organes ± ajout/retrait, statut, QR réel) ; création
  identité → rattachement → organes ; étiquette A6 imprimable (objet
  papier, window.print n'imprime qu'elle)
- Personnes & équipes : invitation → lien d'activation affiché à copier
  (pas d'email en R1, décision explicite), renvoi de lien, équipes ;
  page /activation (choix du mot de passe, connexion directe)
- Catégories : ajout, renommage inline, désactivation
- navigation et actions pilotées par la matrice (usePermissions) —
  l'API re-vérifie chaque requête
- e2e Playwright : recette R1 officielle rejouée intégralement (site →
  zone → appareil+organe → étiquette → invitation → activation) +
  parcours « la matrice pilote l'UI » ; purge idempotente en globalSetup
- alias Vite @siop/shared → source TS (exports nommés CJS ↯ workspace lié)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 13:06:45 +01:00
pr-daaif
266ffaaf1b feat(r1.1): socle backend du référentiel — modèle, contrat, API, seed, tests
- migration r1_referentiel : Category (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, invitation sur User ; migration
  autosuffisante (CREATE EXTENSION IF NOT EXISTS postgis)
- contrat : 21 nouvelles opérations (26 total), générateur OpenAPI étendu
  aux paramètres de chemin ; spec + client web régénérés dans ce commit
- API : modules categories/locations/assets/teams + gestion des personnes
  (liste, rôles, invitation lien 7 j à usage unique, activation publique
  qui connecte directement, mise à jour rôle/équipes) — tout sous
  @RequirePermission ; invariants en service (profondeur 2, kinds,
  catégorie jamais supprimée)
- seed : parc de la maquette validée (5 sites + 8 zones, 8 appareils,
  organes A1/B2, 9 catégories, 2 équipes) — idempotent
- 36 tests verts (couverture 96 % stmts / 85 % branches) : recette
  site→zone→appareil→organes, matrice vivante, invitation→activation ;
  smoke test sur build de prod
- CI : postgres → postgis/postgis:18-3.6 (la migration R1 l'exige)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 12:27:48 +01:00
pr-daaif
ce24d9c32b build(r0.13): Dockerfiles api/web + compose Dokploy + runbook — R0 prêt à déployer
- image api : multi-stage (pnpm deploy --legacy --prod), client Prisma
  régénéré dans l'arborescence déployée, binaryTargets explicites
  (debian/arm64 openssl-3), entrypoint migrate deploy → seed optionnel
  (SEED_ON_START, compilé dist/seed) → API ; non-root, healthcheck /health
- image web : nginx alpine, statique Vite, proxy /api résolu À LA REQUÊTE
  (resolver Docker + variable — nginx démarre sans l'API), fallback SPA,
  cache immuable /assets, healthcheck IPv4 (127.0.0.1)
- infra/docker-compose.dokploy.yml : 5 services préfixés siop2-, secrets
  exigés, seul siop2-web rejoint dokploy-network (API jamais exposée)
- runbook docs/06-production/runbook-dokploy.md : topologie, profils
  d'environnement, checklist première prod, rollback, répétition locale
- répétition locale validée de bout en bout : migrate+seed au boot,
  parcours demo-login → /users/me à travers nginx conteneurisé, conteneurs
  healthy ; le double verrou ADR-002 (DEMO_MODE sans I_KNOW en production)
  a refusé de démarrer — observé en situation réelle
- prisma passe en dépendance de production (migrations au boot)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 22:58:42 +01:00
pr-daaif
36de93bb7e test(r0.12): ESLint monorepo + parcours démo Playwright — R0.12 clos
- ESLint 10 flat config racine (typescript-eslint, react-hooks) ; règle
  ADR-001 codée : no-restricted-imports interdit « minio » partout sauf
  minio-storage.service.ts (violation vérifiée) ; jobs lint par paquet
- Playwright apps/web/e2e : redirection sans jeton, connexion démo 1 clic,
  coquille (rail actif, chip DÉMO), bascule de rôle chronométrée < 3 s
  (ADR-002), /design, déconnexion avec purge ; webServer api dist + vite,
  seed idempotent en globalSetup, PW_CHANNEL=chrome en local
- CI : jobs lint + e2e (services PostgreSQL/Redis, artefact
  playwright-report sur échec) ; vitest restreint à src/

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 22:36:17 +01:00
pr-daaif
83efc33f0f ci(r0.12): pipeline GitHub Actions — ci-contract, api (couverture ≥ 70 %), web
- ci-contract : régénère docs/openapi.json + schema.d.ts et échoue au
  moindre diff — la règle d'or (ADR-001) devient bloquante
- api : services PostgreSQL 18 + Redis, prisma migrate deploy, typecheck,
  Jest avec coverageThreshold global 70 % (mesuré : 97,5 % stmts / 90,7 % branches)
- web : typecheck + vitest + build de production
- test unitaire PermissionsGuard (23 tests au total) ; badge CI au README

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 22:24:59 +01:00
pr-daaif
4f3af5af4f feat(r0.11): apps/web — connexion + sélecteur démo, coquille, /design, client typé
- React 19 + Vite + Tailwind v4 : tokens.css copié tel quel (source de
  vérité), classes composants extraites de la maquette validée, Manrope
  auto-hébergée, primitives shadcn-style possédées (Button cva, menu Radix)
- connexion fidèle à la maquette ; liste démo affichée seulement si
  /auth/demo-accounts répond (404 ⇒ rien — ADR-002, l'API seule source)
- coquille : sidebar 4 groupes avec rail safran actif (écrans futurs marqués
  R1-R3), topbar recherche/thème/chip DÉMO + sélecteur de rôle (bascule 101 ms,
  critère < 3 s) ; page /design = référence vivante des revues pixel
- règle d'or : schema.d.ts généré depuis docs/openapi.json et committé
  (openapi-typescript + openapi-fetch)
- tokens.css : tokens --nav-* ajoutés (valeurs de la maquette validée) ;
  seed réaligné sur les personas de la maquette
- vérifié en navigateur réel (Playwright + Chrome headless) : 7 captures,
  parcours démo complet, bi-thème, zéro erreur console ; typecheck + vitest + build OK

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 22:09:40 +01:00
pr-daaif
8adb561b63 feat(r0.10): apps/api — auth fermée par défaut, matrice en base, démo-login ADR-002, seed
- packages/shared : rôles/catégories, schémas Zod, contrat d'API ;
  pnpm contract → 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 + @Public() ; PermissionsGuard (@RequirePermission,
  matrice relue en base, cache 60 s) ; FileStorage (seul import MinIO) ; /health
- démo-login ADR-002 : module conditionnel DEMO_MODE (404 sinon, testé e2e),
  double verrou production, refus des comptes isDemo=false
- seed idempotent : 7 rôles, matrice complète (70 lignes), 7 comptes démo
- 19 tests Jest (unit + e2e) ; smoke test sur build de prod

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 21:55:04 +01:00