diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8c72d07 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +node_modules +coverage +.git +.vscode +.idea +*.log +.env +.env.* +**/.DS_Store +**/*.test.* +**/*.spec.* +**/tests/** +**/test/** +dist +README.md +CHANGELOG.md +CODE_OF_CONDUCT.md +CONTRIBUTING.md diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..fc90998 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20.19.5 diff --git a/CHANGELOG.md b/CHANGELOG.md index ed68478..66c0cac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ # Changelog +## [0.1.3] - 2025-09-11 +### Ajouté +- Dockerfile multi-stage (build Node 20 LTS → Nginx) et `nginx.conf` +- `.dockerignore` pour un contexte minimal +- Scripts `scripts/docker-build.sh` et `scripts/docker-push.sh` + +### Modifié +- `ExtractionView.tsx`: correction du balisage pour éviter `
` (hydratation) +- Documentation: README et `docs/DEPLOYMENT.md` mis à jour (Docker, versions Node) + +### Technique +- `.nvmrc` ajouté et vérification `predev`/`prebuild` de la version Node + +## 0.1.2 - Suppression du mode démo et routage backend + +### 🔄 Changements majeurs + +- Suppression complète du « mode simple » et des fallbacks de démonstration dans + `src/services/api.ts`. +- Tous les appels aux APIs externes (Cadastre, Géorisques, Géofoncier, BODACC, + Infogreffe) sont désormais routés via le backend `4NK_IA_back` (`/api/context/...`). + +### 🧩 Code + +- `src/services/api.ts`: suppression de `VITE_BACKEND_MODE`, intercepteur Axios n’émet plus de + valeurs mockées, endpoints `analyze/context/conseil` strictement consommés sur + `/api/documents/...`. + +### 📚 Documentation + +- `README.md`: mise à jour des versions (React 19 / MUI 7 / Router 7), retrait de la section + « Mode démonstration », clarification de l’intégration backend. + +### ✅ Qualité + +- Lint, tests et markdownlint OK après modifications. + ## 0.1.1 - Maintenance lint/build et corrections ### ✅ Qualité et lint diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..98dcd34 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# syntax=docker/dockerfile:1.7-labs + +# ---- Build stage ---- +FROM node:20.19-alpine AS build +WORKDIR /app + +# Install dependencies +COPY package*.json ./ +RUN npm ci --ignore-scripts + +# Copy sources +COPY . . + +# Ensure correct Node version for build via prebuild script +RUN npm run build + +# ---- Runtime stage ---- +FROM nginx:alpine AS runtime + +# Copy SPA build +COPY --from=build /app/dist /usr/share/nginx/html + +# Nginx config for SPA routing and caching +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 +HEALTHCHECK CMD wget -qO- http://localhost/ || exit 1 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md index 61e6f61..70177d3 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,11 @@ Application front-end pour l'analyse intelligente de documents notariaux avec IA ## 🚀 Technologies -- **Frontend** : React 18 + TypeScript +- **Frontend** : React 19 + TypeScript - **Build** : Vite 7 -- **UI** : Material-UI (MUI) v6 +- **UI** : Material-UI (MUI) v7 - **State** : Redux Toolkit + React Redux -- **Routing** : React Router v6 +- **Routing** : React Router v7 - **HTTP** : Axios - **Tests** : Vitest + Testing Library - **Linting** : ESLint + Prettier + markdownlint @@ -51,13 +51,22 @@ Application front-end pour l'analyse intelligente de documents notariaux avec IA ### Prérequis -- Node.js >= 22.12.0 (recommandé) ou >= 20.19.0 +- Node.js >= 20.19.0 (LTS). Recommandé: 22.12+ - npm >= 10.0.0 +- nvm recommandé pour gérer les versions de Node + +Avec nvm: + +```bash +nvm use # utilise la version définie dans .nvmrc +# ou, si non installée +nvm install && nvm use +``` ### Installation des dépendances ```bash -npm install +npm ci ``` ### Configuration des environnements @@ -83,15 +92,18 @@ npm run preview # Prévisualisation du build # Qualité de code npm run lint # Vérification ESLint -npm run lint:fix # Correction automatique ESLint npm run format # Vérification Prettier npm run format:fix # Formatage automatique npm run mdlint # Vérification Markdown # Tests -npm run test # Tests unitaires +npm run test # Tests unitaires avec couverture npm run test:ui # Tests avec interface -npm run test:coverage # Tests avec couverture + +# Docker +./scripts/docker-build.sh # Construire l'image locale +IMAGE_TAG=0.1.3 ./scripts/docker-build.sh # Construire avec tag explicite +IMAGE_TAG=0.1.3 ./scripts/docker-push.sh # Pousser vers git.4nkweb.com ``` ## 🏗️ Architecture @@ -147,14 +159,10 @@ src/ - **Breadcrumbs** : Indication de la position actuelle - **Actions contextuelles** : Boutons d'action selon la vue -## 🔧 Mode démonstration +## 🔧 Intégration backend -L'application fonctionne parfaitement en mode démonstration : - -- **Données réalistes** : Exemples d'actes notariaux -- **Fonctionnalités complètes** : Toutes les vues opérationnelles -- **Gestion d'erreur** : Fallback automatique sans backend -- **Expérience utilisateur** : Interface identique au mode production +Toutes les fonctionnalités (upload, extraction, analyse, données contextuelles, conseil LLM) passent par le backend `4NK_IA_back`. +Les APIs externes (Cadastre, Géorisques, Géofoncier, BODACC, Infogreffe) sont appelées côté backend uniquement. ## 🧪 Tests @@ -188,6 +196,19 @@ npm run test:coverage # Rapport de couverture npm run build ``` +### Exécution via Docker + +```bash +# Construire l'image +./scripts/docker-build.sh + +# Lancer localement +docker run --rm -p 8080:80 git.4nkweb.com/4nk/4nk-ia-front:latest + +# Pousser vers le registry (requiert authentification préalable) +IMAGE_TAG=0.1.3 ./scripts/docker-push.sh +``` + ### Variables d'environnement Configurer les URLs des APIs externes selon l'environnement : @@ -233,5 +254,5 @@ Pour toute question ou problème : --- -**Version actuelle** : 0.1.0 -**Dernière mise à jour** : Janvier 2024 +**Version actuelle** : 0.1.3 +**Dernière mise à jour** : Septembre 2025 diff --git a/coverage/4NK_IA_front/src/services/api.ts.html b/coverage/4NK_IA_front/src/services/api.ts.html deleted file mode 100644 index 3d950cf..0000000 --- a/coverage/4NK_IA_front/src/services/api.ts.html +++ /dev/null @@ -1,1018 +0,0 @@ - - - - -
-- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import axios from 'axios' -import type { Document, ExtractionResult, AnalysisResult, ContextResult, ConseilResult } from '../types' - -const BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' - -export const apiClient = axios.create({ - baseURL: BASE_URL, - timeout: 60000, -}) - -// Intercepteur pour les erreurs -apiClient.interceptors.response.use( - (response) => response, - (error) => { - console.error('API Error:', error) - - // Gestion gracieuse des erreurs de connexion et méthodes non supportées - if (error.code === 'ERR_NETWORK' || - error.code === 'ERR_CONNECTION_REFUSED' || - error.response?.status === 405 || - error.response?.status === 404) { - console.warn('Backend non accessible ou endpoint non supporté, mode démo activé') - // Retourner des données de démonstration - return Promise.resolve({ - data: { - id: 'demo-' + Date.now(), - name: 'Document de démonstration', - type: 'pdf', - size: 1024, - uploadDate: new Date(), - status: 'completed' - } - }) - } - - return Promise.reject(error) - } -) - -// Services API pour les documents -export const documentApi = { - // Téléversement de document - upload: async (file: File): Promise<Document> => { - try { - const formData = new FormData() - formData.append('file', file) - const { data } = await apiClient.post('/api/notary/upload', formData, { - headers: { 'Content-Type': 'multipart/form-data' }, - }) - - // L'API retourne {message, document_id, status} - // On doit mapper vers le format Document attendu - const fileUrl = URL.createObjectURL(file) - return { - id: data.document_id || data.id || 'upload-' + Date.now(), - name: file.name, - type: file.type || 'application/pdf', - size: file.size, - uploadDate: new Date(), - status: 'completed', - previewUrl: fileUrl - } - } catch { - console.warn('Upload failed, using demo data:', error) - // Créer une URL locale pour le fichier - const fileUrl = URL.createObjectURL(file) - - // Retourner des données de démonstration en cas d'erreur - return { - id: 'demo-' + Date.now(), - name: file.name, - type: file.type || 'application/pdf', - size: file.size, - uploadDate: new Date(), - status: 'completed', - previewUrl: fileUrl - } - } - }, - - // Extraction des données - extract: async (documentId: string): Promise<ExtractionResult> => { - try { - const { data } = await apiClient.get(`/api/notary/documents/${documentId}`) - - // Mapper les données de l'API vers le format ExtractionResult - const results = data.results || {} - return { - documentId, - text: results.ocr_text || "Texte extrait du document...", - language: "fr", - documentType: results.document_type || "Document", - identities: results.entities?.persons?.map((name: string, index: number) => ({ - id: `person-${index}`, - type: "person" as const, - firstName: name.split(' ')[0] || name, - lastName: name.split(' ').slice(1).join(' ') || "", - birthDate: "", - nationality: "Française", - confidence: 0.9 - })) || [], - addresses: results.entities?.addresses?.map((address: string) => ({ - street: address, - city: "Paris", - postalCode: "75001", - country: "France" - })) || [], - properties: results.entities?.properties?.map((prop: string, index: number) => ({ - id: `prop-${index}`, - type: "apartment" as const, - address: { - street: "123 Rue de la Paix", - city: "Paris", - postalCode: "75001", - country: "France" - }, - surface: 75, - cadastralReference: "1234567890AB", - value: 250000 - })) || [], - contracts: [{ - id: "contract-1", - type: "sale" as const, - parties: [], - amount: 250000, - date: "2024-01-15", - clauses: ["Clause de garantie", "Clause de condition suspensive"] - }], - signatures: results.entities?.persons || [], - confidence: results.verification_score || 0.85 - } - } catch { - // Données de démonstration - return { - documentId, - text: "Ceci est un exemple de texte extrait d'un document notarial. Il contient des informations sur les parties, les biens, et les clauses contractuelles.", - language: "fr", - documentType: "Acte de vente", - identities: [ - { - id: "1", - type: "person" as const, - firstName: "Jean", - lastName: "Dupont", - birthDate: "1980-05-15", - nationality: "Française", - confidence: 0.95 - } - ], - addresses: [ - { - street: "123 Rue de la Paix", - city: "Paris", - postalCode: "75001", - country: "France" - } - ], - properties: [ - { - id: "1", - type: "apartment" as const, - address: { - street: "123 Rue de la Paix", - city: "Paris", - postalCode: "75001", - country: "France" - }, - surface: 75, - cadastralReference: "1234567890AB", - value: 250000 - } - ], - contracts: [ - { - id: "1", - type: "sale" as const, - parties: [], - amount: 250000, - date: "2024-01-15", - clauses: ["Clause de garantie", "Clause de condition suspensive"] - } - ], - signatures: ["Jean Dupont", "Marie Martin"], - confidence: 0.92 - } - } - }, - - // Analyse du document - analyze: async (documentId: string): Promise<AnalysisResult> => { - try { - const { data } = await apiClient.get<AnalysisResult>(`/api/documents/${documentId}/analyze`) - return data - } catch { - // Données de démonstration - return { - documentId, - documentType: "Acte de vente", - isCNI: false, - credibilityScore: 0.88, - summary: "Document analysé avec succès. Toutes les informations semblent cohérentes et le document présente un bon niveau de fiabilité.", - recommendations: [ - "Vérifier l'identité des parties auprès des autorités compétentes", - "Contrôler la validité des documents cadastraux", - "S'assurer de la conformité des clauses contractuelles" - ] - } - } - }, - - // Données contextuelles - getContext: async (documentId: string): Promise<ContextResult> => { - try { - const { data } = await apiClient.get<ContextResult>(`/api/documents/${documentId}/context`) - return data - } catch { - // Données de démonstration - return { - documentId, - cadastreData: { status: "disponible", reference: "1234567890AB" }, - georisquesData: { status: "aucun risque identifié" }, - geofoncierData: { status: "données disponibles" }, - bodaccData: { status: "aucune procédure en cours" }, - infogreffeData: { status: "entreprise en règle" }, - lastUpdated: new Date() - } - } - }, - - // Conseil LLM - getConseil: async (documentId: string): Promise<ConseilResult> => { - try { - const { data } = await apiClient.get<ConseilResult>(`/api/documents/${documentId}/conseil`) - return data - } catch { - // Données de démonstration - return { - documentId, - analysis: "Ce document présente toutes les caractéristiques d'un acte notarial standard. Les informations sont cohérentes et les parties semblent légitimes. Aucun élément suspect n'a été détecté.", - recommendations: [ - "Procéder à la vérification d'identité des parties", - "Contrôler la validité des documents fournis", - "S'assurer de la conformité réglementaire" - ], - risks: [ - "Risque faible : Vérification d'identité recommandée", - "Risque moyen : Contrôle cadastral nécessaire" - ], - nextSteps: [ - "Collecter les pièces d'identité des parties", - "Vérifier les documents cadastraux", - "Préparer l'acte final" - ], - generatedAt: new Date() - } - } - }, - - // Détection du type de document - detectType: async (file: File): Promise<{ type: string; confidence: number }> => { - const formData = new FormData() - formData.append('file', file) - const { data } = await apiClient.post('/api/ocr/detect', formData, { - headers: { 'Content-Type': 'multipart/form-data' }, - }) - return data - }, -} - -// Services API pour les données externes -export const externalApi = { - // Cadastre - cadastre: async (address: string) => { - const cadastreUrl = import.meta.env.VITE_CADASTRE_API_URL - if (!cadastreUrl) throw new Error('Cadastre API URL not configured') - const { data } = await axios.get(`${cadastreUrl}/parcelle`, { params: { q: address } }) - return data - }, - - // Géorisques - georisques: async (coordinates: { lat: number; lng: number }) => { - const georisquesUrl = import.meta.env.VITE_GEORISQUES_API_URL - if (!georisquesUrl) throw new Error('Géorisques API URL not configured') - const { data } = await axios.get(`${georisquesUrl}/risques`, { params: coordinates }) - return data - }, - - // Géofoncier - geofoncier: async (address: string) => { - const geofoncierUrl = import.meta.env.VITE_GEOFONCIER_API_URL - if (!geofoncierUrl) throw new Error('Géofoncier API URL not configured') - const { data } = await axios.get(`${geofoncierUrl}/dossiers`, { params: { address } }) - return data - }, - - // BODACC - bodacc: async (companyName: string) => { - const bodaccUrl = import.meta.env.VITE_BODACC_API_URL - if (!bodaccUrl) throw new Error('BODACC API URL not configured') - const { data } = await axios.get(`${bodaccUrl}/annonces`, { params: { q: companyName } }) - return data - }, - - // Infogreffe - infogreffe: async (siren: string) => { - const infogreffeUrl = import.meta.env.VITE_INFOGREFFE_API_URL - if (!infogreffeUrl) throw new Error('Infogreffe API URL not configured') - const { data } = await axios.get(`${infogreffeUrl}/infogreffe/rcs/extrait`, { params: { siren } }) - return data - }, -} - |