Objectif de l'outil

Equilibre LOGOS est un outil de gestion des préconisations techniques (CCTP) pour les études de restauration et de restructuration de bâtiments patrimoniaux (monuments historiques, musées, édifices classés). Il couvre le cycle complet d'une étude : de la saisie des préconisations à la production de documents contractuels.

Problème résolu

Les économistes de la construction spécialisés en patrimoine gèrent des études complexes multi-lots, avec des descriptifs techniques longs, des repères codifiés, des quantités multi-zones et des aléas variables. Les outils génériques (Excel, Word) ne permettent pas de structurer, filtrer, ni de réutiliser ces données d'une étude à l'autre.

Utilisateurs cibles

Périmètre fonctionnel (Phase 1)

Note : LOGOS V2 est une réécriture complète de LOGOS V1 (projet equilibre-logos), avec un schéma de données enrichi, un stack technique modernisé et une architecture extensible (colonnes dynamiques, Server Actions, tRPC).

Stack technique

Framework
Next.js 16.2.6
App Router · RSC · Server Actions · flag --webpack (réseau Z:\)
ORM
Prisma 7.8.0
Adapter PrismaPg · prisma.config.ts · pas de url dans schema
Base de données
PostgreSQL
localhost:5432 · db : equilibre_logos_v2
UI
React 19 · Tailwind 4
lucide-react · clsx · class-variance-authority
API / État
tRPC v11 · TanStack Query v5
Server Actions pour les mutations d'édition
Outils import
ExcelJS · tsx
Scripts prisma/ pour import DPGF Excel et Word (docx → XML)
TypeScript
TypeScript 5
Strict · IDs cuid() · Decimals sérialisés en Number
Export documents
docx · exceljs
Génération CCTP Word et tableaux Excel (à venir)
Visualisation
Apache ECharts 5
Treemap hiérarchique + bar chart horizontal · import dynamique SSR-safe · ResizeObserver
Environnement
Windows 11 / Z:\ réseau
Port 3001 · Turbopack incompatible (junction points)
Déploiement
Docker · Docker Compose
3 services (app, db Postgres 16, weasyprint) · Dockerfile multi-stage (node:22-alpine) · migrations Prisma au démarrage (docker-entrypoint.sh) · projet figé equilibre-logos-v3
PDF / Impression
WeasyPrint
Service HTTP interne (port 5000) · génération PDF des CCTP depuis HTML · palette transmise via POST · port publié sur l'hôte en dev via docker-compose.override.yml

Base de données — Vue d'ensemble

Le modèle de données suit une hiérarchie stricte du bâtiment à la quantité :

Edifice
Affaire
Etude
NoeudHierarchique
Preconisation
Quantite

NoeudHierarchique représente l'arborescence Lot → Chapitre → Sous-chapitre… d'une étude. Chaque préconisation est rattachée à un nœud feuille via noeudId. La position dans l'arbre est codée par le champ chemin (notation pointée zéro-paddée : "01", "01.02").

Deux entités transverses enrichissent ce modèle : Categorie (classification des affaires), Salarie (responsable d'étude), MotCle (indexation des préconisations), et EtudeColonne (définition des champs dynamiques par étude).

Toutes les tables utilisent des identifiants CUID (chaîne aléatoire sécurisée), des timestamps createdAt / updatedAt automatiques, et un champ deletedAt (soft delete) sur les entités principales.

Tables détaillées

Edifice

RACINE

Référentiel des bâtiments patrimoniaux (monuments historiques, musées, édifices classés).

  • idString CUIDPK
  • villeString
  • nomString
  • programmeString?description libre du programme de travaux
Relations : Affaire[]

Affaire

PROJET

Projet de travaux rattaché à un édifice (APS, APD, DCE…). Peut appartenir à plusieurs catégories.

  • idString CUIDPK
  • edificeId→ Edifice
  • referenceStringex : "25-018"
  • nomString
Relations : Edifice Categorie[] M2M Etude[]

Categorie

RÉFÉRENTIEL

Classification des affaires. Relation M2M avec Affaire. Exemples : "Monument historique", "Restauration", "Restructuration".

  • idString CUIDPK
  • titreString
  • descriptionString?
Relations : Affaire[] M2M

Salarie

UTILISATEUR

Utilisateurs de l'application. Aucune authentification en phase 1. Assigné comme responsable sur les études.

  • idString CUIDPK
  • prenomString
  • nomString
  • emailString@unique
Relations : Etude[]

MotCle

INDEXATION

Mots-clés hiérarchiques (N niveaux) pour indexer et rechercher les préconisations. Auto-référentiel via parentId.

  • idString CUIDPK
  • termeString
  • parentId→ MotCle?hiérarchie
Relations : MotCle? (parent) MotCle[] (enfants) Preconisation[] M2M Etude[] M2M

Etude

CHIFFRAGE

Étude (APS, APD, DCE…) rattachée à une affaire. Pilotée par un responsable. Cycle de vie : BROUILLON → EN_COURS → DIFFUSE / REFERENCE.

  • idString CUIDPK
  • affaireId→ Affaire
  • responsableId→ Salarie
  • nomString
  • statutEtudeStatutBROUILLON par défaut
  • dateCreationDateTimeauto
  • dateModifDateTime@updatedAt
Relations : Affaire Salarie EtudeColonne[] NoeudHierarchique[] Preconisation[] MotCle[] M2M

NoeudHierarchique

HIÉRARCHIE CCTP

Arbre Lot → Chapitre → Sous-chapitre… d'une étude. Chaque préconisation est attachée à un nœud feuille. Le champ chemin code la position en notation pointée zéro-paddée ("01", "01.02", "01.02.03"). La logique métier (création, réordonnement, suppression, copie) est centralisée dans src/server/lib/hierarchie.ts. Exposé via tRPC router noeud.*.

  • idString CUIDPK
  • etudeId→ Etude
  • parentId→ NoeudHierarchique?null = nœud racine (Lot)
  • positionIntrang 1-based parmi les frères
  • nomString
  • descriptionString? TEXT
  • cheminString@unique(etudeId, chemin) — ex : "02.03"
Relations : Etude NoeudHierarchique? (parent) NoeudHierarchique[] (enfants) Preconisation[]

EtudeColonne

CHAMPS DYNAMIQUES

Définition des colonnes personnalisées par étude. Permet d'adapter le formulaire de saisie à chaque marché sans modifier le schéma. Les valeurs saisies sont stockées en JSON dans champsAdditionnels sur Preconisation et Quantite.

  • idString CUIDPK
  • etudeId→ Etude
  • contexteColonneContextePRECONISATION ou QUANTITE — indique sur quelle entité s'applique la colonne
  • nomStringclé utilisée dans champsAdditionnels (ex : "lot", "chapitre", "zone")
  • typeChampColonneTypeChampTEXT · NUMBER · DATE · SELECT · BOOLEAN · FILE
  • optionsJson?liste des valeurs possibles pour les champs de type SELECT (ex : noms des lots)
  • requisBooleanfalse par défaut
  • ordreIntordre d'affichage dans le formulaire

Preconisation

CŒUR MÉTIER · CCTP

Ligne de travaux CCTP — entité centrale de l'application. Chaque préconisation appartient à une étude, peut être rattachée à des mots-clés, et dispose de quantités de localisation. Les champs techniques longs (description, normes, critères) sont stockés en TEXT.

  • idString CUIDPK
  • etudeId→ Etude
  • noeudId→ NoeudHierarchiquenœud feuille — obligatoire
  • titreStringdésignation courte
  • descriptionString? TEXTdescriptif technique détaillé
  • uniteString?unité de mesure : m², ml, u, m³…
  • prixUnitaireDecimal(10,2)prix par unité HT · sérialisé en Number côté client
  • repereString?code court identifiant la prestation (ex : SN, CT, VM)
  • normeAppliqueeString? TEXT
  • critereMetreString? TEXT
  • clauseReemploiString? TEXT
  • champsAdditionnelsJson?valeurs des EtudeColonnes PRECONISATION (ex : { lot, chapitre, description_prestation })
  • commentaireString? TEXT
  • ordreIntordre d'affichage dans l'étude
  • etatPreconisationEtatIMPORTE par défaut · VALIDE / BROUILLON / IMPORTE
Relations : Etude NoeudHierarchique MotCle[] M2M Quantite[]

Quantite

MÉTRÉ

Ligne de métré pour une préconisation. Permet de décomposer les quantités par zone, niveau ou local. L'aléa (%) s'applique sur la quantité réelle pour calculer la quantité retenue : qté retenue = qté réelle × (1 + aléa / 100). Le champ commentaire sert généralement de repère de quantité (ex : "SN-01").

  • idString CUIDPK
  • preconisationId→ Preconisation
  • quantiteReelleDecimal(10,2)quantité de base mesurée
  • aleaDecimal(5,2)?pourcentage d'aléa · null si non applicable
  • champsAdditionnelsJson?valeurs des EtudeColonnes QUANTITE (ex : { zone, niveau, dimensions, objectif })
  • commentaireString?repère de la quantité (ex : "SN-01", "Grande Galerie N1")

AgentConversation

AGENT IA

Conversation de l'assistant IA, une par étude (mémoire partagée). La source de vérité de la mémoire est en base ; un RECAP.md serait exporté à la demande, jamais l'inverse.

  • idString CUIDPK
  • etudeId→ Etude@unique (1–1)
  • recapString? Textrécap condensé (optionnel)

AgentMessage

AGENT IA

Message append-only d'une conversation. authorSalarieId reste null tant que l'authentification (P-1) n'est pas livrée — traçabilité par horodatage.

  • idString CUIDPK
  • conversationId→ AgentConversationcascade delete
  • roleAgentRoleUSER | ASSISTANT
  • contentString Text
  • authorSalarieIdString?null (pas d'auth en phase 1)

AgentDocument

AGENT IA

PDF joint à une conversation (phase 2). On stocke uniquement la couche texte extraite, pas le binaire. Les scans sans texte sont rejetés à l'upload (pas d'OCR).

  • idString CUIDPK
  • conversationId→ AgentConversationcascade delete
  • filenameString
  • mimeTypeString
  • textString Textcouche texte extraite
  • charCountIntcaractères signifiants

ProposedChange

AGENT IA

Modification proposée par l'agent (phase 3). L'agent n'écrit jamais directement : il émet un diff whitelisté (validé zod, scopé à l'étude), persisté « en attente », prévisualisé, puis appliqué en UNE transaction à la validation humaine. Les snapshots before/after assurent l'audit et la réversibilité (annulation).

  • idString CUIDPK
  • conversationId→ AgentConversationcascade delete
  • etudeId→ Etude
  • statusProposedChangeStatusPENDING · APPLIED · REJECTED · REVERTED
  • summaryString Textrésumé lisible
  • diffJsonopérations whitelistées (jamais de SQL)
  • before / afterJson?snapshots audit + revert
  • proposedBy / validatedByString?null tant que pas d'auth (P-1)
  • decidedAtDateTime?horodatage de la décision

Énumérations

EtudeStatut
  • BROUILLON
  • EN_COURS
  • DIFFUSE
  • REFERENCE
ColonneContexte
  • PRECONISATION
  • QUANTITE
ColonneTypeChamp
  • TEXT
  • NUMBER
  • DATE
  • SELECT
  • BOOLEAN
  • FILE
AgentRole
  • USER
  • ASSISTANT
ProposedChangeStatus
  • PENDING
  • APPLIED
  • REJECTED
  • REVERTED

Système de colonnes dynamiques

Chaque étude peut définir ses propres champs supplémentaires via EtudeColonne, sans modifier le schéma. Les valeurs sont stockées dans le champ JSON champsAdditionnels de Preconisation et Quantite.

Exemple — Rennes APD 25-018

Exemple — Louvre Sondages

Convention : La clé JSON dans champsAdditionnels correspond au champ nom de l'EtudeColonne. L'application utilise ce même nom pour relier la définition de la colonne à sa valeur stockée.

Fonctionnalités implémentées

Fonctionnalité Date d'impl. Date de modif. Contenu
Ingestion PACMAN — ordre hiérarchique manuel (Phase A) 2026-07-06 Dans la Phase A de l'ingestion Excel (/ingest, sélection des colonnes hiérarchiques), l'ordre des niveaux N1→N2→N3 était jusque-là imposé par la position des colonnes dans le fichier (tri par index dans toggleCol) : l'utilisateur ne pouvait pas décider qu'une colonne située à droite soit le niveau racine. Désormais l'ordre de sélection définit les niveaux (1ʳᵉ colonne cliquée = N1, etc.) et le récapitulatif « Hiérarchie sélectionnée » expose des boutons ↑/↓ par niveau pour réordonner manuellement (libellés ARIA, désactivés aux extrémités, focus ring-current). La suggestion heuristique du parseur (suggestedHierarchyCols) reste le point de départ, désormais ajustable. Toute la chaîne aval (badge de niveau en colonne, buildPreview, arbre de prévisualisation Phase C) suivait déjà l'ordre du tableau hierarchyCols : seul le tri positionnel a été retiré. Fichier _ingest/phase-a-view.tsx. Vérifié : tsc --noEmit OK.
Authentification locale + RBAC (P-1) 2026-07-03 Portage de l'auth locale + droits (RBAC) de l'app sœur OFFRES vers LOGOS (monolithe Next.js), en 8 phases. Auth 100 % locale — aucune dépendance à l'API/SSO (prévue ~dans 1 mois ; pont dolibarrId prêt) : mots de passe PBKDF2-SHA512 (100k itérations) + sessions Postgres (token SHA-256, TTL 30 j, cookie httpOnly logos-session). Modèle : Salarie étendu (passwordHash?/role/active/mustChangePassword) + tables Session/Groupe/Permission/GroupePermission/SalarieGroupe + enum Role (migration additive 20260702000000_auth_rbac, procédure anti-drift). RBAC : 31 permissions <domaine>.<action>, 3 groupes ADMIN/EDITEUR/LECTEUR (le rôle ADMIN court-circuite les permissions fines). Gardes enforcement-aware (no-op tant que AUTH_ENFORCE=false) : les 18 procédures tRPC (permissionProcedure), les 12 route handlers non-auth (requireApiSession ; /api/sync reste gardée par SYNC_SECRET) et toutes les server actions (requirePermission, dont les 27 actions d'édition/paramètres d'étude). Front : page /login, menu utilisateur (déconnexion, changement de mot de passe), masquage UI par permission (CurrentUserProvider/useCurrentUser), rideau src/middleware.ts. Seed npm run seed:auth (roster équipe, 19 comptes, même liste qu'OFFRES ; admins R. Perrot-Minot + N. Fryder ; mot de passe initial à changer au 1er login). Écrans /admin (réservés ADMIN) : gestion des comptes (rôle, groupes, statut actif/inactif, réinitialisation du mot de passe avec révocation des sessions) et des groupes (matrice de permissions par catégorie, créer/renommer/supprimer). Garde-fous : jamais retirer le dernier ADMIN actif ; désactivation/reset révoquent les sessions. Enforcement vérifié au runtime (401/403, redirection /login, 200 selon droits). Reste : basculer AUTH_ENFORCE=true au déploiement (secret Coolify). Détail : Doc/refonte-auth.md.
Page Ingestion PACMAN — convergence design OFFRES + a11y (clavier P0, onglets sémantiques, tokens/contraste) 2026-07-02 Traitement de l'audit Doc/audit-ingest-2026-07-01.md sur /ingest, 7ᵉ et dernière page du chantier P-5 (ingest-tabs.tsx, ingest-view.tsx, _ingest/{ui,phase-a-view,phase-b-view,phase-c-view,json-import-view,affaire-picker}.tsx). Accessibilité clavier (P0) : la zone de dépôt (upload Excel et JSON, jusque-là <div onClick> + <input hidden> non focusable, WCAG 2.1.1) devient un role="button" focusable (Entrée/Espace) avec input sr-only ; la sélection d'une colonne hiérarchique (Phase A), un <tr onClick> souris-seul qui bloquait « Suivant », reçoit un <button aria-pressed> dans la cellule Niveau (anneau de focus ring-current visible sur ligne normale et inversée). Onglets Excel/JSON : <button> + border-b-2 décoratif → contrôle segmenté sémantique (role=tablist/tab/tabpanel, aria-selected, roving tabindex + flèches ←/→). Contraste & tokens : migration zinc-400/300 (2,6:1 / 1,5:1) → text-muted (AA), surfaces/filets → border-border, texte → text-foreground ; anneaux de focus ring-1 ring-zinc-400 faibles retirés → outline global foreground. Couleur ≠ décor : teintes amber/indigo de catégorisation des champs (Phase B), la catégorie restant portée par le <select>, neutralisées (fond neutre). ARIA : renommage de nœud (Phase C) atteignable au clavier (Entrée/F2), role="alert" sur les erreurs, role="status"/aria-live sur les écrans de chargement/succès, aria-hidden sur les SVG décoratifs (mascotte, spinner, icônes), labels associés (htmlFor), role="search"/aria-label sur les recherches, aria-pressed sur la bascule de source d'affaire, <h1> de page (sr-only). Reduced-motion : le chomp de la mascotte Pacman respecte prefers-reduced-motion. Nettoyage ESLint : 3 erreurs (apostrophes non échappées) + 2 avertissements (props quantites/colonnes inutilisées de PhaseCView) corrigés. Mascotte PACMAN conservée (identité établie de la fonctionnalité, décision produit hors périmètre a11y). Vérifié : tsc --noEmit OK, ESLint ingest 0 problème (vs 5 sur HEAD), 0 régression hors périmètre. (Différé : split Phase C non replié, cibles tactiles < 24 px, micro-typo 10 px — P3.)
Page Hiérarchie — refonte accessibilité (clavier P0/P1), tokens/contraste, bleu → neutre, initiale V/B/I 2026-07-01 Traitement de l'audit Doc/audit-hierarchie-2026-07-01.md sur /etudes/[id]/hierarchie (hierarchie-view.tsx + _hierarchie/{noeud-row,preco-panel,node-menu,import-panel}.tsx + page.tsx). Accessibilité clavier (P0/P1) : la sélection d'un nœud et la sélection/édition d'une préconisation (jusque-là <div onClick> souris-seuls, WCAG 2.1.1) passent par des <button> atteignables au clavier (aria-pressed/aria-expanded) ; KeyboardSensor dnd-kit ajouté pour le réordonnancement des nœuds + poignée en <button> ; alternative clavier au glisser-déposer des préconisations — nouveau NodePickerModal (role=dialog + piège de focus) branché sur « Déplacer vers… » (menu préco) et un bouton « Importer » par ligne de bibliothèque ; actions (ajouter sous-nœud / ⋮ / ↑↓) révélées au focus clavier (plus seulement au survol, WCAG 2.4.7). Contraste & tokens : migration complète zinc-400/300text-muted (AA), surfaces/filets → bg-background/border-border, texte → text-foreground ; focus sobre (retrait de focus:outline-none + anneaux focus:ring-blue-* → outline global foreground) ; accent bleu décoratif (survol du titre, poignée de resize, badge de compteur, surbrillance de dépôt, préco sélectionnée) → neutres ; destructif → token negative. Couleur ≠ seul canal (WCAG 1.4.1) : stripe d'état de préconisation doublée de l'initiale V/B/I (PRECO_ETAT_INITIAL) + title. ARIA : aria-label/aria-pressed sur les boutons d'outil, role="search" + aria-label sur les recherches, role="menu" + Échap sur les menus contextuels, MoveNodeModal passé en role=dialog/aria-modal + piège de focus, poignée de resize role="separator" focusable + flèches ←/→. Vérifié : tsc --noEmit OK, 0 nouvelle erreur/avertissement ESLint vs HEAD. (Différé : cibles tactiles < 24 px, sémantique role=tree/treeitem + roving tabindex, repli responsive du panneau, micro-typo — P2/P3.)
Page Synthèse — convergence design OFFRES + a11y (tokens/contraste, bleu décoratif → neutre, ARIA filtres & treemap) 2026-06-30 Traitement de l'audit Doc/audit-synthese-2026-06-30.md sur /etudes/[id]/synthese (synthese-client.tsx + components/filtres-quantite.tsx + components/treemap-cout.tsx). Tokens & contraste : surfaces/filets bg-white/zincbg-background/border-border, libellés & compteurs zinc-400/500text-muted (AA), total → text-foreground, mention « dont … d'aléas » amber-600 (~3,9:1) → token text-warning (~4,7:1). Couleur ≠ décor : l'état « filtre actif » signalé en bleu (chips de lot, déclencheurs de dropdown, badge de compteur, accent des cases à cocher) → neutre (sélection = foreground : bg-foreground/text-background, border-foreground, accent-foreground), focus bleu des cases retiré → outline global. La palette catégorielle du treemap est conservée (encodage de données légitime, pas de la décoration). ARIA : aria-pressed sur les bascules de lot, aria-expanded sur les dropdowns de filtre, et le treemap passe de aria-hidden à role="img" + aria-label (total HT) — il n'est plus invisible aux lecteurs d'écran. Vérifié : tsc --noEmit OK, 0 nouvelle erreur/avertissement ESLint vs HEAD. (Différé : équivalent-tableau détaillé du treemap, couleurs de graphique non adaptatives au thème sombre, composant mort breakdown-alea.tsx non monté — P2/P3.)
Page Tableur — convergence design OFFRES + a11y (tokens/contraste, focus sobre, réordonnancement clavier) 2026-06-30 Traitement de l'audit Doc/audit-tableur-2026-06-30.md sur /etudes/[id]/tableur. Nuance vérifiée : contrairement à l'Édition, les cellules sont des <input>/<textarea> natifs déjà éditables au clavier — aucun P0 « cellule souris-seule » ne s'y reproduit. Accessibilité clavier (P1) : le réordonnancement par glisser-déposer HTML5 (souris seule, WCAG 2.1.1) reçoit une alternative clavier — la poignée devient un <button> focusable, ↑/↓ monte/descend la préconisation dans son nœud (parité exacte avec la souris, mêmes patches ordre). Contraste & tokens : migration zinc-400/300text-muted (AA ; placeholders zinc-300 1,47:1 corrigés), surfaces/filets → bg-background/border-border, montants → text-foreground ; focus sobre — retrait de outline-none + anneaux focus:ring-blue-400 (inputs, textarea, selects) → outline global foreground ; accent bleu décoratif (survol du titre, boutons « ajouter un métré », indicateur de dépôt, fond des nouvelles lignes) → neutres/tokens. Couleur ≠ seul canal (WCAG 1.4.1) : ligne modifiée doublée d'un marqueur non chromatique (+ title) ; fond de nouvelle ligne neutralisé. ARIA : role="search" + aria-label sur la recherche, aria-sort sur les en-têtes triables (flèche aria-hidden), aria-expanded/aria-label sur le dépli, aria-label sur les boutons icône (ajouter/supprimer/effacer), <caption> + scope="col", aria-current sur l'onglet actif, barre de sauvegarde role="status"/aria-live. Fichier tableur-client.tsx. Vérifié : tsc --noEmit OK, 0 nouvelle erreur/avertissement ESLint vs HEAD. (Différé : cibles tactiles < 24 px, sémantique role=tablist+flèches des onglets, micro-typo 10 px — P2/P3.)
Page Édition — refonte accessibilité (clavier P0/P1), tokens/contraste & largeur alignée 2026-06-30 Traitement de l'audit Doc/audit-edition-2026-06-29.md sur /etudes/[id]/edition. Largeur : le cadre de la vue Édition s'alignait sur la boîte 1600 px et débordait de 16 px à gauche du header ; px-4 remonté sur le conteneur racine (max-w-[1600px] mx-auto px-4) et px-4 internes redondants retirés → cadre aligné sur le header et les pages sœurs (Information/Hiérarchie). Accessibilité clavier (P0/P1) : les cellules repère / titre / unité / prix unitaire (le prix, donnée centrale, n'était éditable qu'au Ctrl+clic souris) deviennent des boutons éditables au clavier (Entrée / F2 / double-clic, Ctrl+clic conservé) avec restitution du focus à la cellule en sortie (WCAG 2.1.1/2.4.3) ; KeyboardSensor dnd-kit (réordonnancement et déplacement inter-nœud au clavier) + poignée en <button> ; carré d'état → bouton avec initiale V/B/I (canal non chromatique redondant, WCAG 1.4.1) ; actions Supprimer / ajouter sous-nœud / révélées au focus clavier (plus seulement au survol, WCAG 2.4.7) ; menu contextuel de nœud role=menu navigable (flèches/Home/End/Échap) doublé d'un déclencheur ; modale d'import role=dialog/aria-modal/Échap/clic-fond + piège de focus. Contraste & tokens : migration massive zinc-400/300text-muted (AA), focus sobre (retrait de focus:outline-none + anneaux bleus → outline global foreground), accent bleu décoratif (survols, zones de dépôt, poignée de resize) → neutres, bandeau de synchro + filtres d'état → tokens warning/positive/negative, aria-pressed sur les bascules (outils, peinture, filtres). Responsive : panneau latéral borné à 50vw + repli automatique sous 1100 px (la colonne d'arbre ne s'écrase plus). Fichiers edition-view.tsx, import-fields-modal.tsx, lib/colors.ts (PRECO_ETAT_INITIAL + import PRECO_ETAT_BADGE). Vérifié : tsc --noEmit OK, 0 nouvelle erreur ESLint. (overflow-x horizontal de l'arbre laissé de côté : overflow-x:auto forcerait l'axe vertical en conteneur de scroll et casserait le sticky des en-têtes ; mitigé par le repli auto.)
Refonte design — convergence OFFRES + a11y (accueil & référentiel) 2026-06-29 Alignement du design de LOGOS sur l'app sœur OFFRES (sobre, noir/blanc inversé en sombre, couleur réservée aux états) et refonte accessibilité. Fondation (src/app/globals.css) : tokens de rôle en canaux RGB background/foreground/muted/border + états positive/negative/warning, override .dark, exposés en Tailwind v4 (@theme inline) → utilitaires bg-background/text-muted… ; focus clavier global en @layer base. Accueil : contraste AA (texte secondaire text-muted), sidebar (fond crème → neutre) et header (opaque) sobres, input de recherche role=search + aria-label, filtres aria-pressed, menu d'actions Dupliquer/Supprimer accessible clavier/tactile (bouton ⋮ role=menu, gestion du focus) remplaçant le clic-droit seul, grille grid, useDeferredValue sur la recherche. Référentiel réorganisé « dito accueil » : même barre de recherche/filtres (statuts + mots-clés), actions hover-only remplacées par le même menu ⋮ accessible, modales durcies (role=dialog/aria-modal/Échap/clic-fond, sans backdrop-blur), tokens + contraste AA. Documents de cadrage ajoutés : PRODUCT.md, DESIGN.md. Commits e56a48a (accueil) + réorg référentiel.
Assistant IA par étude — Phase 4 (récap/mémoire) 2026-06-28 Mémoire de conversation condensée, générée depuis la base (messages + documents joints + modifications appliquées) et stockée dans AgentConversation.recapjamais un fichier comme source de vérité (Postgres autoritatif). Génération à la demande (pas à chaque tour, pour éviter la dérive). Exposée dans une section « Mémoire » repliable du panneau, avec export RECAP.md (GET /api/etudes/[id]/agent/recap, lecture seule : aucune génération LLM ni écriture sur un GET ; 409 si le récap n'a pas encore été généré). Raffinements : rendu markdown sûr des réponses de l'assistant et du récap (composant mini-markdown.tsx, sans dépendance), bouton « Effacer la conversation » qui purge messages/documents/récap mais conserve les modifications appliquées (audit + réversibilité). Procédures tRPC generateRecap / clearConversation. Fichiers src/server/agent/recap.ts, src/app/api/etudes/[id]/agent/recap/route.ts, src/app/etudes/[id]/_agent/{mini-markdown,agent-panel}.tsx. Aucune migration (champ recap présent depuis la phase 1). Clôture P-4 (2026-06-28) : revue adversariale multi-agents (5 dimensions), 19 défauts corrigés avant clôture (export read-only, confinement structurel du récap, parseur markdown durci contre les identifiants/formules, état post-effacement cohérent).
Assistant IA par étude — Phase 3 (modifications validées) 2026-06-28 Mode « Proposer » du panneau : l'agent émet un diff structuré (opérations whitelistées updatePreconisation / updateQuantite, validées par zod), prévisualisé (avant → après), puis appliqué uniquement après validation humaine explicite ; une application est annulable. Garde-fous : aucune écriture silencieuse (staging ProposedChange PENDING) ; scoping serveur durci — chaque preconisationId/quantiteId revérifié comme appartenant à l'étude avant ET pendant l'application (updateMany scopé), ce qui comble le trou de saveBatch ; réversibilité par snapshots before/after ; jamais de SQL ni de champ libre. Procédures tRPC proposeChange / applyProposal / rejectProposal / revertProposal. Fichiers src/server/agent/{diff-schema,diff-apply,propose}.ts, src/server/routers/agent.ts. Périmètre : mises à jour de préconisation / quantité (extensible aux autres opérations).
Assistant IA par étude — Phase 2 (comparaison PDF) 2026-06-28 L'assistant accepte des PDF joints (bouton trombone du panneau) et les compare au projet en lecture seule : écarts (quantités, prix, libellés), manques (postes d'un côté et pas de l'autre), incohérences. Route POST /api/etudes/[id]/agent/document (multipart). PDF à couche texte uniquement : les scans sans texte sont détectés (texte extrait quasi vide) et rejetés avec un message clair, pas d'OCR (décision actée). Le texte extrait est persisté (table AgentDocument, pas le binaire) et reste disponible aux tours suivants de la conversation. Réutilise extractDocument (pdf-parse). Correctif associé : pdf-parse v2 (pdf.js) déclaré dans serverExternalPackages de next.config.ts — bundlé par webpack, son worker pdf.worker.mjs était introuvable (corrige aussi l'extraction PDF de l'ingest).
Assistant IA par étude — Phase 1 (lecteur) 2026-06-27 Agent conversationnel confiné à une étude (P-4, phase 1 : lecture seule, aucune écriture). Panneau slide-over (bouton flottant) monté dans src/app/etudes/[id]/layout.tsx donc persistant sur les 6 sous-pages (les layouts Next ne se re-montent pas à la navigation). Confinement par le code : le contexte de lecture (hiérarchie, préconisations, quantités, montants) est construit filtré par etudeId (src/server/agent/etude-context.ts) ; toute requête hors étude est rejetée (404). Mémoire de conversation persistée en Postgres (tables AgentConversation / AgentMessage), jamais un fichier. Moteur DeepSeek via interface OpenAI-compatible (src/server/agent/deepseek-chat.ts, variables DEEPSEEK_*). Router tRPC agent (getConversation, sendMessage). Phases suivantes : 2 (lecture/comparaison PDF), 3 (modifications proposées avec preview + validation), 4 (recap exporté).
Déploiement Docker (conteneurisation) 2026-06-16 Mise en conteneur de l'application complète via Docker Compose (docker-compose.yml) : 3 services — app (Next.js, Dockerfile multi-stage node:22-alpine), db (PostgreSQL 16-alpine, volume postgres_data, port hôte 5433), weasyprint (génération PDF, réseau interne). Migrations Prisma appliquées au démarrage via docker-entrypoint.sh (prisma migrate deploy). Nom de projet figé equilibre-logos-v3. Secrets passés par variables d'environnement (EQ_API_KEY, SYNC_SECRET, DEEPSEEK_API_KEY, POSTGRES_PASSWORD). En dev, docker-compose.override.yml publie le port 5000 de WeasyPrint pour l'appli lancée en local (npm run dev).
Export JSON du Référentiel (sauvegarde locale) 2026-06-08 Nouvelle route GET /api/reference/export qui télécharge en un seul fichier JSON (referentiel-logos-AAAA-MM-JJ.json) toutes les études du Référentiel (affaire isReference=true), chacune au format d'échange v1 (réutilise buildEtudeExport). Enveloppe : { logosExportVersion, kind:"referentiel", exportedAt, affaire, count, etudes[] }. Bouton « Exporter (JSON) » ajouté dans l'en-tête de la page /reference, à côté de la recherche — pensé comme sauvegarde locale des préconisations de référence. Fichiers src/app/api/reference/export/route.ts, src/app/reference/reference-view.tsx.
Initialisation projet V2 2026-05-15 Création Next.js 16 + Prisma 7 + PostgreSQL. Schéma complet (9 tables, 3 enums). Configuration webpack pour lecteur réseau Z:\. Adapter PrismaPg. Seed de démonstration (5 édifices, 10 affaires, 37 préconisations).
Page d'accueil — Liste des affaires 2026-05-15 2026-05-26 React Server Component. Affichage hiérarchique : édifice → affaires → études. Badge de statut coloré (BROUILLON/EN_COURS/DIFFUSE/REFERENCE). Lien vers la page de détail de chaque étude. Filtres cumulatifs par statut (boutons toggle entre Recherche et Mots-clés). Mise en page pleine largeur max-w-[1600px].
Page étude — Vue condensée (tableau) 2026-05-15 2026-05-19 Tableau groupé par lot puis par chapitre. Numérotation séquentielle continue. Colonnes : N°, repère, désignation, qté retenue, unité, P.U., total HT. Sous-totaux par lot et par chapitre. Total général HT. Sélection de ligne pour ouverture du panneau d'édition.
Page étude — Vue développée (cartes) 2026-05-15 2026-05-19 Cartes détaillées par préconisation : description, normes, critères, mots-clés, liste des quantités avec localisation et aléa. Sélection de carte pour ouverture du panneau d'édition.
Filtre et totaux par lots (TopBar) 2026-05-15 2026-05-19 Boutons toggle multi-sélection par lot avec montant HT par lot. Recherche textuelle (titre, description, repère, mots-clés). Migration du panneau latéral vers une barre horizontale au-dessus du contenu. Affichage du nombre de préconisations filtrées et du total HT.
Panneau d'édition rétractable 2026-05-19 Panneau latéral droit collapsible (36 px → 384 px, transition CSS). Ouverture automatique au clic sur une préconisation. Formulaire complet : titre, repère, unité, P.U., description, commentaire, norme, critère de métré, clause réemploi. Champs dynamiques EtudeColonne (PRECONISATION). Édition de toutes les quantités avec qté réelle, aléa, commentaire et champs dynamiques QUANTITE. Server Actions Next.js + revalidatePath pour persistence. Clé React par precoId pour réinitialisation automatique du formulaire.
Import Excel — Rennes APD 25-018 2026-05-16 Script tsx prisma/import-rennes.ts. Lecture ExcelJS feuille DATA (15 colonnes). 123 préconisations, 6 lots, 121 quantités, 4 EtudeColonnes. Normalisation des noms de lots. Calcul du prix unitaire (coutUnitaire → coutTotal/qté → forfait). Idempotent : suppression de l'étude existante avant re-création.
Import Word — Louvre Sondages 2026-05-16 Script tsx prisma/import-louvre.ts. Données extraites du fichier Projet Sondages.docx (extraction XML via .NET ZipFile + namespace manager). 7 types de sondages (SN, ST, CN, CT, VM, PB, RP), 3 lots, 8 EtudeColonnes. Quantités fictives numérotées (code repère + index). Total estimatif : 75 420 €.
Système de colonnes dynamiques 2026-05-15 Table EtudeColonne avec contexte (PRECONISATION/QUANTITE), typeChamp (6 types), options JSON pour les SELECT, ordre d'affichage. Valeurs persistées dans champsAdditionnels (Json) sur Preconisation et Quantite. Rendu dynamique dans le panneau d'édition selon le type de champ.
Server Actions — Édition préconisations 2026-05-19 Fichier src/app/etudes/[id]/actions.ts. Actions : updatePreconisation (tous les champs standards + champsAdditionnels) et updateQuantite (qté réelle, aléa, commentaire, champsAdditionnels). Invalidation cache via revalidatePath. Appel depuis le client via useTransition + router.refresh().
Page Paramètres de l'étude 2026-05-19 Route /etudes/[id]/settings. Édition des propriétés générales : nom, statut, responsable (sélection parmi les salariés), mots-clés (tag input avec recherche et création à la volée). Gestion CRUD des colonnes personnalisées (EtudeColonne) : ajout inline avec formulaire, modification, suppression avec confirmation. Colonnes affichées séparément par contexte (Préconisation / Quantité) avec badges de type. Migration schéma : M2M Etude ↔ MotCle ajoutée (migration add_etude_motscles). Accès via icône ⚙ dans le header de la page étude.
Navigation — Page documentation 2026-05-19 2026-05-26 Route /docs. Mise en page standard (icône + titre + sous-titre) avec 4 onglets (Présentation, Base de données, Fonctionnalités, Scripts) naviguant par ancre dans un iframe. Sidebar de l'HTML masquée via CSS. Bouton ⓘ discret dans les headers. Fichier HTML statique servi depuis public/docs/overview.html.
Barre de recherche — page d'accueil 2026-05-19 Recherche temps réel hiérarchique sur la liste des édifices. Logique : si l'édifice correspond → afficher toutes ses affaires ; si l'affaire correspond → afficher toutes ses études ; sinon filtrer les études individuellement. Compteur dynamique (résultats / total) dans le header. État vide avec bouton "Effacer la recherche". Composant client HomeView séparé du RSC page.tsx.
Filtre par lot (page Hiérarchie) 2026-06-05 Ajout d'un outil de filtre par lot (nœud de niveau 1) dans l'en-tête de la page /etudes/[id]/hierarchie : un sélecteur « Tous les lots / <lot> » qui restreint l'arbre affiché au sous-arbre du lot choisi (filtrage par préfixe de chemin), avec bouton de réinitialisation et compteur « N nœuds affichés sur M ». Le réordonnancement par glisser-déposer reste opérationnel dans le périmètre filtré ; le filtre se réinitialise automatiquement si le lot sélectionné est supprimé. Implémentation côté client dans hierarchie-view.tsx (état filterN1, dérivation du sous-arbre via chemin).
Réorganisation de la page Informations 2026-06-05 Trois ajustements de la page /etudes/[id]/settings. Boîte « Données projet » scindée en deux dans une colonne fixe à gauche : une sous-boîte « Données de l'étude » (toujours affichée — nombre de lots, de préconisations, montant HT, date de création, date du dernier enregistrement via dateModif/@updatedAt) au-dessus de la fiche projet issue de l'API Équilibre (affichée seulement si l'affaire est liée à un projet). Boîte « Documents PDF » renommée « Contexte » : le lien « Prévisualiser → » et les boutons d'export (Estimation / DPGF / CCTP / CCTP+) sont retirés ; ne restent que les deux champs Texte introductif CCTP et Texte introductif Estimation / DPGF (les exports restent accessibles depuis la page Preview). Carte centrale : la ligne d'infos génériques (lots, précos, montant, date de création) est retirée de son en-tête, qui ne conserve que les actions (Enregistrer, Exporter en JSON…).
Recentrage navigation projet + filtre d'état (Édition) 2026-06-05 La navigation d'une étude est recentrée sur 5 pages dans la sidebar : Informations, Édition, Hiérarchie, Synthèse, Preview (l'entrée « Prévisualiser » est renommée « Preview »). La page Édition devient le point d'entrée par défaut : la racine /etudes/[id] redirige (redirect()) vers /etudes/[id]/edition, et tous les points d'entrée (accueil : clic/création/duplication ; import JSON) pointent vers Édition. Les pages Tableur et Vue principale (ancienne racine « variante ») sont conservées mais retirées du menu : accessibles uniquement via un menu déroulant « Archives » dans l'en-tête de la page Édition. La vue principale est déplacée sous une route dédiée /etudes/[id]/variante ; les types PrecoItem/EtudeData sont extraits dans etudes/[id]/types.ts. Fil d'Ariane mis à jour (segment étude → Édition, « Preview »). Nouveau filtre par état à droite de la barre de recherche de l'Édition : trois boutons bascule Validé / Brouillon / Importé (couleurs d'état) qui filtrent les préconisations affichées (combinable avec la recherche texte ; masque les nœuds sans préco retenue).
Export Excel du chiffrage (par lot) 2026-06-04 Nouvelle route GET /api/etudes/[id]/export-excel?noeuds=<csv N1> qui télécharge un classeur .xlsx du chiffrage, avec la même structure et les mêmes filtres que l'export HTML (lots → sous-nœuds → préconisations, avec prix et totaux par préco / sous-nœud / lot / général). Généré avec exceljs (déjà présent) dans src/server/print/export-excel.ts, réutilisant le module data partagé build-print-data.ts. Détails de chaque préconisation (description, normes, champs additionnels, quantités) en lignes groupées repliables (outline Excel = équivalent du repli/dépli du HTML) ; autoFilter sur l'en-tête pour rechercher/trier nativement ; lots colorés via la palette N1. Bouton « Export Excel » ajouté dans la page d'aperçu, à côté d'« Export HTML », réutilisant le sélecteur de lots.
Export HTML autonome interactif (par lot) 2026-06-04 Nouvelle route GET /api/etudes/[id]/export-html?noeuds=<csv N1> qui télécharge un fichier .html unique et autonome (offline) du chiffrage d'une étude, filtré par lot(s) de niveau 1. Le fichier embarque tout inline (données JSON + CSS + JS vanilla) : aucune dépendance ni appel réseau, ouvrable d'un double-clic — pensé pour l'archivage et la transmission à un tiers. Interactif : repli/dépli des nœuds et préconisations, recherche live (titre + repère), tri des préconisations (numéro / titre / P.U. / total HT), avec prix et totaux par préco / sous-nœud / lot / général. Sécurité : échappement HTML systématique et JSON neutralisé (</script>, séparateurs de ligne). La logique de structuration des données (fetchEtude, buildWeasyData, applyNoeudN1Filter, qRetenue) a été extraite de la route print dans un module partagé src/server/print/build-print-data.ts (réutilisé tel quel, sans changement de comportement PDF) ; générateur dans src/server/print/standalone-html.ts. Bouton « Export HTML » ajouté dans la page d'aperçu, qui réutilise le sélecteur de lots existant.
Template PDF CCTP+ (champs additionnels) 2026-06-02 Nouveau template WeasyPrint cctp2 (libellé « CCTP+ ») : identique au CCTP mais affiche les champs additionnels de préconisation (bloc « Champs additionnels » sous les normes/critères) et de quantité (ajoutés à la ligne de détail de chaque quantité), qui n'étaient pas rendus dans le CCTP standard. Côté route.ts : buildWeasyData transmet désormais champsAdditionnels pour les précos et les quantités (hors zone/niveau déjà affichés) ; cctp2 ajouté aux templates autorisés. Côté service Python (app.py) : champ champsAdditionnels ajouté aux modèles Pydantic PrecoData/QuantiteData, cctp2 dans TEMPLATE_NAMES. Nouveau gabarit templates/cctp2.html.j2. UI : onglet « CCTP+ » dans l'aperçu et bouton de téléchargement dans la page Informations.
Export PDF — 3 modes 2026-05-20 2026-06-01 Route API GET /api/etudes/[id]/print?mode= avec 3 modes : Estimation (condensé avec P.U. et totaux HT, sous-totaux lot/chapitre, total général), Quantitatif (condensé avec quantités uniquement, sans prix), Descriptif (liste détaillée par preco : description, norme, critère de métré, clause de réemploi, commentaire, quantités localisées). Document HTML autonome avec CSS @media print A4. Badge mode dans l'en-tête. Dropdown "Exporter" dans la barre de la page étude (overlay pour fermeture click-outside). Aucune dépendance supplémentaire.
Export / Import JSON d'une étude (format v1) 2026-06-01 2026-06-01 Format d'échange JSON versionné et découplé des identifiants internes (liens par chemin hiérarchique zéro-paddé, ex. 01.02), pensé pour un flux : export robuste → archivage / transmission tiers / édition par LLM → réimport. Export (outil interne à l'étude) : GET /api/etudes/[id]/export télécharge l'étude complète (hiérarchie, préconisations, quantités imbriquées, colonnes dynamiques, mots-clés par terme, descriptions CCTP/Estimation ; affaire/édifice en informatif). Bouton dans la carte « Export JSON » de la page Informations. Import (page dédiée) : onglet « Import JSON » sous l'Ingestion PACMAN (/ingest) — upload d'un .json, choix de l'affaire de rattachement via le sélecteur complet (Local / API Équilibre / Référentiel) + responsable + nom. POST /api/etudes/import valide le JSON (Zod, logosExportVersion: 1) et crée toujours une nouvelle étude, en réutilisant une création transactionnelle (mots-clés résolus/créés par terme, chemins recalculés). Modules src/server/etude-io/ (format.ts, export.ts, import.ts, persist.ts) ; composant réutilisable _ingest/affaire-picker.tsx. Round-trip vérifié (préconisations, nœuds, quantités, colonnes conservés à l'identique). MAJ 2026-06-02 : l'onglet « Import JSON » reprend exactement la zone de dépôt de PACMAN Excel (logo Pacman animé, glisser-déposer) en variante verte avec « Déposez un Json » (IdleView paramétrable : accent, accept, label, hint) ; le formulaire de rattachement s'affiche après dépôt du fichier. Export JSON déplacé en bouton « Exporter en JSON » dans la barre d'actions de la page Informations.
Filtre des lots à l'impression 2026-06-01 2026-06-01 Sélection des nœuds de niveau 1 (lots) à inclure dans l'export, depuis la page d'aperçu /etudes/[id]/print/preview. Menu déroulant à cases à cocher (« Tout cocher / décocher »), tous les lots sélectionnés par défaut. Le paramètre ?noeuds= (CSV d'IDs N1) est transmis aux URLs d'aperçu HTML et de PDF ; il est omis quand tous les lots sont cochés (aucun filtre). Côté API route.ts, applyNoeudN1Filter ne conserve que les préconisations dont l'ancêtre racine est autorisé (résolution de la racine en remontant parentId, avec cache) — appliqué aux deux chemins de rendu (WeasyPrint cctp/estimation/dpgf et HTML legacy), tous les totaux étant recalculés en conséquence. Bouton PDF désactivé et message d'invite si aucun lot sélectionné. MAJ 2026-06-01 : la barre d'outils est fixée sous les breadcrumbs (page dimensionnée à 100vh − 3rem, toolbar sticky) — elle ne défile plus quand on atteint la fin du PDF en aperçu.
Drag-and-drop préconisations (page variante) 2026-05-23 Page /etudes/[id]/variante. Drag-and-drop HTML5 natif (pas de bibliothèque externe) entre lots (niveau 1) et entre chapitres (niveau 2). stopPropagation sur les zones chapitre pour ciblage précis. Mise à jour optimiste via useState + sync useEffect après router.refresh(). Fix graphique : draggingPrecoId réinitialisé avant la mise à jour optimiste pour éviter les lignes grisées persistantes.
Hiérarchie NoeudHierarchique — refactoring CCTP 2026-05-23 Nouveau modèle NoeudHierarchique (auto-référentiel, chemin zéro-paddé). Migration en deux phases : A (noeudId nullable) → populate script → B (noeudId obligatoire). Logique métier dans src/server/lib/hierarchie.ts : création, réordonnement (recomputerSousArbre récursif), suppression gardée, copie entre études, requêtes de lecture. tRPC router noeud.* (getArbre, create, update, reorder, delete, isFeuille). Scripts d'import (seed, import-louvre, import-rennes) mis à jour pour créer les nœuds à la volée et passer noeudId.
Navigation — Étude persistante dans le menu 2026-05-24 Nouveau contexte React ActiveEtudeContext (src/app/active-etude-context.tsx) avec persistance localStorage. Stocke ville, édifice, affaire, nom et statut de l'étude ouverte. VarianteView et SettingsView définissent ce contexte au montage via useEffect. Le bandeau d'en-tête (ref={headerRef}) et la barre flottante au scroll ont été supprimés de la page étude. Sidebar redessinée : réduit par défaut (collapsed=true), section "Étude en cours" avec liens Étude et Paramètres ; en mode réduit affiche icônes + texte Ville · Édifice · Étude en écriture verticale (writing-mode: vertical-lr). Lien Paramètres accessible directement depuis le menu.
État de préconisation 2026-05-24 Nouveau champ obligatoire etat de type PreconisationEtat (enum PostgreSQL : VALIDE, BROUILLON, IMPORTE ; défaut IMPORTE) sur le modèle Preconisation. Migration 20260524000000_add_preconisation_etat. Dans la page chiffrage : bande colorée verticale (w-1.5) en tête de chaque ligne (vert emerald = Validé, orange amber = Brouillon, rouge red = Importé). Dans le panneau d'édition latéral : groupe de 3 boutons pill dans l'en-tête du formulaire, cliquable pour changer l'état en direct. Enregistrement via updatePreconisation (nouveau paramètre etat) après clic sur Enregistrer. Nouvelles constantes ETAT_LABEL, ETAT_STRIPE, ETAT_BUTTON dans variante-view.tsx.
Page Synthèse projet 2026-05-24 Route /etudes/[id]/synthese. Trois sections synchronisées : Treemap hiérarchique (ECharts, 3 niveaux via colonnes dynamiques PRECONISATION, montants HT après filtres, breadcrumb, tooltip montant/part/nb préconisations), Drill-down aléas (ECharts bar chart horizontal empilé base+aléa, badge aléa moyen pondéré, mise en évidence des aléas >15 %, empty state avant sélection), Filtres dynamiques (un dropdown par colonne QUANTITE, OR intra-colonne AND inter-colonnes, option "non renseigné", compteur métrés). Agrégation 100 % serveur via tRPC router synthese.getDonnees (Zod, deux requêtes Prisma + JS). Types exportés : TreemapNode, DrillDownEntry, FiltreDisponible, SyntheseResult. ECharts monté via useEffect + ResizeObserver (import dynamique SSR-safe). Lien "Synthèse" ajouté dans la sidebar. Dépendance : echarts.
Page Tableur 2026-05-25 2026-05-26 Route /etudes/[id]/tableur. Tableur inline éditable pour toutes les préconisations et leurs métrés. Onglets par nœud racine (lots). Colonnes fixes : N° (séquentiel), Nœud, Repère (éditable, gras), Titre, Unité, P.U. HT, champs dynamiques PRECONISATION, Total HT. Chaque ligne est dépliable pour afficher/éditer les métrés (Quantité, Aléa %, champs dynamiques QUANTITE, Total ligne). Ajout et suppression de métrés depuis la vue. Suivi des modifications via état dirtyPrecos/dirtyQuantites/newQuantites/deletedQuantiteIds. Sauvegarde en lot via tRPC tableur.saveBatch (transaction Prisma). Raccourci Ctrl+S. Barre de sauvegarde flottante indiquant le nombre de modifications en attente. Lien "Tableur" ajouté dans la sidebar. Fix race condition post-sauvegarde : setInitialised(false) supprimé, remplacement par mise à jour directe depuis le résultat de refetch().
Mise à jour graphique — UI/UX 2026-05-25 Sidebar : suppression bouton recherche, labels en majuscules, fond #fcfbf5. Toutes les pages : fond blanc. Header : fond semi-transparent (backdrop-blur-sm). Page accueil : disposition masonry CSS (columns-1 sm:columns-2 lg:columns-3, break-inside-avoid). Page étude : TopBar lots redessinée en grille de cartes pleine largeur avec prix HT et nombre de préconisations sous le nom du lot. Page paramètres : suppression onglets → formulaire unique avec panneau colonnes collapsible à droite ; ajout actions "Rattacher à un autre projet" et "Supprimer l'étude". Page tableur : barre de recherche inline au-dessus du tableau.
Page Hiérarchie 2026-05-25 2026-06-01 Route /etudes/[id]/hierarchie. Éditeur d'arbre hiérarchique interactif : renommage inline, ajout de nœuds enfants, boutons ▲/▼ pour réordonner dans le même parent. Menu contextuel par nœud (clic droit ou bouton ⋮, sur tous les nœuds) : Remonter d'un niveau (rattache le nœud à son grand-parent ; un nœud de niveau 2 devient ainsi un lot racine), Déplacer dans un autre nœud… (modale de sélection de la destination — exclut le nœud lui-même, ses descendants, son parent actuel et les nœuds porteurs de préconisations ; option « Racine »), Supprimer le nœud (et tout son contenu, confirmation window.confirm). L'ancien bouton poubelle par ligne, redondant, a été retiré. Glisser-déposer des nœuds via @dnd-kit (voir ligne dédiée) : réordonnancement et reparentage en un geste. Clic sur un nœud feuille ouvre un panneau latéral droit affichant les préconisations associées (titre + repère) ; ctrl+clic pour multi-sélection ; drag des préconisations sélectionnées vers un autre nœud feuille pour les réaffecter. Panneau latéral redimensionnable à la souris (poignée de glissement, largeur 320–900 px). Par préconisation : flèches ▲/▼ de réordonnancement (au survol, masquées pendant une recherche) et bouton « + Ajouter une préconisation » en bas (crée une entrée vide en dernière position du nœud courant). Zone de description de la préco agrandie (8 lignes, redimensionnable verticalement). Server Actions : getPrecosByNoeud, movePrecoToNoeud, createPrecoInNoeud (ajout en fin de nœud feuille), reorderPrecos (ordre transactionnel), moveNoeud (reparentage transactionnel avec recalcul des chemins), deleteNoeud (suppression récursive du sous-arbre). Outil Bibliothèque : bouton « Réf. » à gauche du champ de recherche qui affiche directement les chiffrages des affaires de référence (getReferenceEtudes, bypass de la recherche) ; le second champ filtre les préconisations par contenu du titre uniquement.
Hiérarchie — Drag-and-drop @dnd-kit 2026-06-02 Le glisser-déposer des nœuds de la page Hiérarchie passe du HTML5 natif (barre bleue + anneau ambre) à @dnd-kit, au même rendu que la page Édition : item fantôme semi-transparent suivi par un DragOverlay, réagencement animé. L'arbre est aplati puis trié comme une liste plate avec projection horizontale (décalage souris → profondeur cible et parent résultant), façon « Sortable Tree » dnd-kit, ce qui unifie réordonnancement et reparentage en un seul geste. Le dépôt de préconisations sur les nœuds feuilles reste en HTML5 natif (dataTransfer). Fichiers _hierarchie/tree-dnd.ts (flatten / descendants / projection), _hierarchie/noeud-row.tsx, hierarchie-view.tsx. Réutilise les mêmes server actions (reorderNoeuds, moveNoeud…). Cette implémentation (initialement prototypée en page V2 parallèle) remplace désormais définitivement l'ancien DnD natif. Menu contextuel clic-droit sur les préconisations du panneau latéral (Dupliquer / Supprimer) clampé dans le viewport.
UX Batch 2 2026-05-25 Tableur : suppression des flèches expand/collapse (quantités toujours visibles) ; tri par colonnes Nœud / Titre / Unité (indicateur ↑/↓) ; colonnes Nœud élargie, Unité et Titre réduites ; badge statut en haut à droite. Page étude : clic sur une préconisation sélectionnée ferme le panneau droit ; ouverture panneau droit collapse automatiquement le menu latéral (événement sidebar-collapse-request) ; zone de contrôle redessinée en 2 colonnes (gauche : recherche + filtres état + condensé/développé + export ; droite : boutons lots compacts sans prix ni count). Informations sur l'étude : titre "Général" supprimé ; action "Rattacher" intégrée en bouton discret à côté du nom de l'affaire ; Supprimer et Dupliquer en petites icônes à côté du nom de l'étude ; bouton "Réinitialiser" supprimé (bouton "Enregistrer" vire à l'ambré quand les données sont modifiées) ; panneau statistiques ajouté (nb lots, nb préconisations, montant total HT, date de création). Action duplicateEtude dans settings/actions.ts : clone étude + colonnes + nœuds (map d'IDs) + préconisations + quantités dans une transaction Prisma.
Batch UX 3 — 2026-05-26 2026-05-26 Mise en page pleine largeur max-w-[1600px] uniformisée sur toutes les pages (Accueil, Hiérarchie, Paramètres, Variante, Synthèse). Statut d'étude REFERENCE ajouté (enum PostgreSQL + badge rouge dans la fiche et dans la home). Filtres cumulatifs par statut sur la page d'accueil : boutons toggle REFERENCE / EN_COURS / BROUILLON / DIFFUSE entre la recherche et les mots-clés. Page documentation refonte : composant DocsClient avec en-tête standard + 4 onglets naviguant par ancre dans l'iframe, sidebar HTML cachée par CSS.
Page Ingestion PACMAN (redesign tabulaire) 2026-05-27 2026-06-01 Route /ingest entièrement redessinée. Flux 3 phases pour fichiers Excel tabulaires (une ligne = une préconisation) : Phase A — détection heuristique des colonnes hiérarchiques (cardinalité, mots-clés, position) jusqu'à 3 niveaux, validation/correction par l'utilisateur. Renommage en ligne du nom de chaque colonne (le paramètre, pas les valeurs) via un champ éditable dans la cellule « Colonne » ; la modification mute parsedData.headers et se propage en aval (mapping Phase B et nom des colonnes champsAdditionnels créées en base). Phase B — mapping des colonnes restantes sur les champs Preconisation (titre, description, repère, unité, prix, quantité, critèreMètre, etc.) ; colonnes non reconnues → champsAdditionnels. Phase C — prévisualisation de l'arbre LOGOS généré client-side, renommage des nœuds, sélection affaire + nom étude + responsable → création. Nouveau endpoint POST /api/ingest/parse-tabular (parser excel-tabular.ts). Endpoint /api/ingest/commit enrichi : support des nouveaux champs (critereMetre, normeAppliquee, clauseReemploi, commentaire, champsAdditionnels, ordre). Nom d'étude par défaut : Pacman - [nom fichier].
Référentiel couleurs + Page Édition (Phase 1) 2026-05-29 2026-06-01 Référentiel couleurs (src/lib/colors.ts) : source unique pour palette N1 (3 variantes accent/dark/light), états préconisation (VALIDE/BROUILLON/IMPORTE) et statuts étude. Tous les composants existants migrés pour importer depuis ce fichier (variante-view, tableur-client, settings/page, hierarchie-view, home-view). WeasyPrint reçoit la palette via le corps POST (plus de dépendance aux constantes Python).
Numérotation WeasyPrint : correction du bug .00 — remplacement de p.ordre par un compteur positionnel par nœud feuille.
Page Édition Phase 1 (/etudes/[id]/edition) : barre d'onglets N1 sticky (accent couleur cyclique, total HT), bouton + pour créer N1, contrôles Déplier/Réduire + recherche + indicateur de sync. Zone principale : hiérarchie N2+ avec sticky par profondeur, renommage inline 800 ms debounce + optimistic update, clic droit → menu contextuel (Dupliquer complet, Dupliquer structure, Supprimer). Nœuds feuilles : expand/collapse préconisations, bouton + ajout rapide. Lignes préconisation : badge état (clic pour cycler VALIDE→BROUILLON→IMPORTE), repère inline éditable, titre inline éditable, quantité retenue (clic → lignes quantité), unité inline, prix unitaire inline, total HT calculé en temps réel, détail expandable (description, normes, clauses). Panneau droit : mode peinture (3 boutons état, clic = paint, Échap = sortie). Server actions ajoutées : duplicateNoeudComplet, duplicateNoeudStructure (décalage des frères + copie sous-arbre avec/sans précos). Breadcrumb header mis à jour.
MAJ 2026-06-01 : panneau droit redimensionnable (poignée de glissement, largeur 320–760 px, défaut élargi à 420) ; champ « Descriptif » de l'outil Détail préconisation agrandi (12 lignes, redimensionnable verticalement) ; l'outil Détail préconisation affiche les champs non vides (hors repère/titre) en mode éditable et propose un bouton « Ajouter champ » pour renseigner les champs vides disponibles (norme appliquée, critère de métré, clause de réemploi, commentaire + colonnes dynamiques de contexte PRECONISATION).
Header global + titres de page 2026-05-24 Nouveau composant client Header (src/app/header.tsx) : barre horizontale h-12 au-dessus du contenu. Gauche : logo "E" dark-mode-aware + breadcrumbs dynamiques via usePathname() + useActiveEtude() (/ → home, /docs → Docs, /etudes/[id] → Ville > Étude, /etudes/[id]/settings → Ville > Étude > Paramètres). Droite : bascule thème (déplacée depuis la sidebar) + avatar initiales + prénom du premier salarié (fetchServer-side dans layout.tsx via db.salarie.findFirst()). Titres de page structurés (icône dans carré zinc-900/100 + h1 + sous-titre) ajoutés dans home-view.tsx, variante-view.tsx et settings/page.tsx. Toggle thème supprimé du footer sidebar.

Scripts d'import

Commandes disponibles

Convention de nommage des repères (Louvre)

Idempotence : Tous les scripts d'import suppriment l'étude existante avant de la recréer, garantissant qu'une double exécution ne génère pas de doublons. L'édifice et l'affaire sont réutilisés si existants.