feat(api): alignement back/front + support functionalType/mimeType + modes simple/complete (VITE_BACKEND_MODE)

This commit is contained in:
Nicolas Cantu 2025-09-11 12:28:31 +02:00
parent 013a6dda0a
commit 6600308d61
5 changed files with 100 additions and 139 deletions

View File

@ -13,174 +13,80 @@ d'analyse de documents notariaux.
http://localhost:8000 (développement) http://localhost:8000 (développement)
``` ```
### Modes d'API pris en charge
- **Mode simple** (`VITE_BACKEND_MODE=simple`) : endpoints sous `/api/notary/...`
- **Mode complet** (`VITE_BACKEND_MODE=complete`) : endpoints sous `/api/documents/...`
La variable d'environnement `VITE_BACKEND_MODE` pilote dynamiquement le choix des endpoints côté front.
### Endpoints ### Endpoints
#### Upload de document #### Upload de document
```http ```http
POST /api/documents/upload POST /api/notary/upload # mode simple
POST /api/documents/upload # mode complet
Content-Type: multipart/form-data Content-Type: multipart/form-data
Body: FormData avec le fichier Body: FormData avec le fichier
``` ```
**Réponse :** **Réponse (champs attendus et utilisés par le front) :**
```json
{
"document_id": "doc_123456", // ou "id"
"mime_type": "application/pdf", // ou "mimeType"
"functional_type": "CNI" // ou "functionalType" (type fonctionnel métier)
}
```
Le front mappe en `Document`:
```json ```json
{ {
"id": "doc_123456", "id": "doc_123456",
"name": "acte_vente.pdf", "name": "acte_vente.pdf",
"type": "application/pdf", "mimeType": "application/pdf",
"functionalType": "CNI",
"size": 1024000, "size": 1024000,
"uploadDate": "2024-01-15T10:30:00Z", "uploadDate": "<date locale>",
"status": "completed" "status": "completed",
"previewUrl": "blob:..."
} }
``` ```
#### Extraction de données #### Extraction de données
```http ```http
GET /api/documents/{documentId}/extract GET /api/notary/documents/{documentId} # mode simple (détails document + résultats)
``` GET /api/documents/{documentId}/extract # mode complet
**Réponse :**
```json
{
"documentId": "doc_123456",
"text": "Texte extrait du document...",
"language": "fr",
"documentType": "Acte de vente",
"identities": [
{
"id": "1",
"type": "person",
"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",
"address": {
"street": "123 Rue de la Paix",
"city": "Paris",
"postalCode": "75001",
"country": "France"
},
"surface": 75,
"cadastralReference": "1234567890AB",
"value": 250000
}
],
"contracts": [
{
"id": "1",
"type": "sale",
"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 #### Analyse du document
```http ```http
GET /api/documents/{documentId}/analyze GET /api/documents/{documentId}/analyze # mode complet
``` ```
**Réponse :** (mode simple: synthèse construite côté front à partir de `/api/notary/documents/{documentId}`)
```json
{
"documentId": "doc_123456",
"documentType": "Acte de vente",
"isCNI": false,
"credibilityScore": 0.88,
"summary": "Document analysé avec succès. Toutes les informations semblent cohérentes.",
"recommendations": [
"Vérifier l'identité des parties auprès des autorités compétentes",
"Contrôler la validité des documents cadastraux"
]
}
```
#### Données contextuelles #### Données contextuelles
```http ```http
GET /api/documents/{documentId}/context GET /api/documents/{documentId}/context # mode complet
``` ```
**Réponse :** (mode simple: non disponible — fallback de démonstration)
```json
{
"documentId": "doc_123456",
"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": "2024-01-15T10:30:00Z"
}
```
#### Conseil IA #### Conseil IA
```http ```http
GET /api/documents/{documentId}/conseil GET /api/documents/{documentId}/conseil # mode complet
``` ```
**Réponse :** (mode simple: non disponible — fallback de démonstration)
```json
{
"documentId": "doc_123456",
"analysis": "Ce document présente toutes les caractéristiques d'un acte notarial standard.",
"recommendations": [
"Procéder à la vérification d'identité des parties",
"Contrôler la validité des documents fournis"
],
"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": "2024-01-15T10:30:00Z"
}
```
## APIs Externes ## APIs Externes
@ -235,6 +141,7 @@ En cas d'erreur, l'application bascule automatiquement vers des données de dém
```env ```env
VITE_API_URL=http://localhost:8000 VITE_API_URL=http://localhost:8000
VITE_BACKEND_MODE=simple
VITE_CADASTRE_API_URL=https://api.cadastre.gouv.fr VITE_CADASTRE_API_URL=https://api.cadastre.gouv.fr
VITE_GEORISQUES_API_URL=https://www.georisques.gouv.fr/api VITE_GEORISQUES_API_URL=https://www.georisques.gouv.fr/api
VITE_GEOFONCIER_API_URL=https://api.geofoncier.fr VITE_GEOFONCIER_API_URL=https://api.geofoncier.fr

View File

@ -59,7 +59,7 @@ export const FilePreview: React.FC<FilePreviewProps> = ({ document, onClose }) =
} }
} }
const isPDF = document.type.includes('pdf') || document.name.toLowerCase().endsWith('.pdf') const isPDF = document.mimeType.includes('pdf') || document.name.toLowerCase().endsWith('.pdf')
if (!isPDF) { if (!isPDF) {
return ( return (
@ -71,7 +71,7 @@ export const FilePreview: React.FC<FilePreviewProps> = ({ document, onClose }) =
</IconButton> </IconButton>
</Box> </Box>
<Alert severity="info"> <Alert severity="info">
Aperçu non disponible pour ce type de fichier ({document.type}) Aperçu non disponible pour ce type de fichier ({document.functionalType || document.mimeType})
</Alert> </Alert>
</Paper> </Paper>
) )

View File

@ -2,6 +2,7 @@ import axios from 'axios'
import type { Document, ExtractionResult, AnalysisResult, ContextResult, ConseilResult } from '../types' import type { Document, ExtractionResult, AnalysisResult, ContextResult, ConseilResult } from '../types'
const BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' const BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
const BACKEND_MODE = (import.meta.env.VITE_BACKEND_MODE || 'simple').toLowerCase() as 'simple' | 'complete'
export const apiClient = axios.create({ export const apiClient = axios.create({
baseURL: BASE_URL, baseURL: BASE_URL,
@ -54,7 +55,8 @@ export const documentApi = {
return { return {
id: data.document_id || data.id || 'upload-' + Date.now(), id: data.document_id || data.id || 'upload-' + Date.now(),
name: file.name, name: file.name,
type: file.type || 'application/pdf', mimeType: data.mime_type || data.mimeType || file.type || 'application/pdf',
functionalType: data.functional_type || data.functionalType || undefined,
size: file.size, size: file.size,
uploadDate: new Date(), uploadDate: new Date(),
status: 'completed', status: 'completed',
@ -69,7 +71,8 @@ export const documentApi = {
return { return {
id: 'demo-' + Date.now(), id: 'demo-' + Date.now(),
name: file.name, name: file.name,
type: file.type || 'application/pdf', mimeType: file.type || 'application/pdf',
functionalType: undefined,
size: file.size, size: file.size,
uploadDate: new Date(), uploadDate: new Date(),
status: 'completed', status: 'completed',
@ -189,8 +192,24 @@ export const documentApi = {
// Analyse du document // Analyse du document
analyze: async (documentId: string): Promise<AnalysisResult> => { analyze: async (documentId: string): Promise<AnalysisResult> => {
try { try {
if (BACKEND_MODE === 'complete') {
const { data } = await apiClient.get<AnalysisResult>(`/api/documents/${documentId}/analyze`) const { data } = await apiClient.get<AnalysisResult>(`/api/documents/${documentId}/analyze`)
return data return data
}
// Mode simple: pas d'endpoint d'analyse dédié -> utiliser le détail et synthétiser
const { data } = await apiClient.get(`/api/notary/documents/${documentId}`)
return {
documentId,
documentType: data?.results?.document_type || 'Document',
isCNI: (data?.results?.document_type || '').toLowerCase().includes('cni'),
credibilityScore: data?.results?.verification_score ?? 0.85,
summary: 'Analyse synthétique basée sur les métadonnées documentaires disponibles.',
recommendations: [
"Vérifier l'identité des parties",
"Contrôler la validité des documents cadastraux",
"S'assurer de la conformité des clauses contractuelles",
],
}
} catch { } catch {
// Données de démonstration // Données de démonstration
return { return {
@ -211,8 +230,20 @@ export const documentApi = {
// Données contextuelles // Données contextuelles
getContext: async (documentId: string): Promise<ContextResult> => { getContext: async (documentId: string): Promise<ContextResult> => {
try { try {
if (BACKEND_MODE === 'complete') {
const { data } = await apiClient.get<ContextResult>(`/api/documents/${documentId}/context`) const { data } = await apiClient.get<ContextResult>(`/api/documents/${documentId}/context`)
return data return data
}
// Mode simple: pas d'endpoint context -> démo
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()
}
} catch { } catch {
// Données de démonstration // Données de démonstration
return { return {
@ -230,8 +261,30 @@ export const documentApi = {
// Conseil LLM // Conseil LLM
getConseil: async (documentId: string): Promise<ConseilResult> => { getConseil: async (documentId: string): Promise<ConseilResult> => {
try { try {
if (BACKEND_MODE === 'complete') {
const { data } = await apiClient.get<ConseilResult>(`/api/documents/${documentId}/conseil`) const { data } = await apiClient.get<ConseilResult>(`/api/documents/${documentId}/conseil`)
return data return data
}
// Mode simple: pas d'endpoint conseil -> démo
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()
}
} catch { } catch {
// Données de démonstration // Données de démonstration
return { return {

View File

@ -1,7 +1,8 @@
export interface Document { export interface Document {
id: string id: string
name: string name: string
type: string mimeType: string
functionalType?: string
size: number size: number
uploadDate: Date uploadDate: Date
status: 'uploading' | 'processing' | 'completed' | 'error' status: 'uploading' | 'processing' | 'completed' | 'error'

View File

@ -136,7 +136,7 @@ export default function UploadView() {
<Box display="flex" gap={1} flexWrap="wrap"> <Box display="flex" gap={1} flexWrap="wrap">
<Chip <Chip
label={doc.type} label={doc.functionalType || doc.mimeType}
size="small" size="small"
variant="outlined" variant="outlined"
/> />