""" Routes pour le traitement des documents notariaux """ from fastapi import APIRouter, UploadFile, File, Form, HTTPException, Depends, BackgroundTasks from fastapi.responses import JSONResponse from pydantic import BaseModel, Field from typing import Optional, List, Dict, Any import uuid import time import logging from enum import Enum from domain.models import DocumentStatus, DocumentType from tasks.notary_tasks import process_notary_document from utils.external_apis import ExternalAPIManager from utils.llm_client import LLMClient logger = logging.getLogger(__name__) router = APIRouter() class DocumentTypeEnum(str, Enum): """Types de documents notariaux supportés""" ACTE_VENTE = "acte_vente" ACTE_DONATION = "acte_donation" ACTE_SUCCESSION = "acte_succession" CNI = "cni" CONTRAT = "contrat" AUTRE = "autre" class ProcessingRequest(BaseModel): """Modèle pour une demande de traitement""" id_dossier: str = Field(..., description="Identifiant du dossier") etude_id: str = Field(..., description="Identifiant de l'étude") utilisateur_id: str = Field(..., description="Identifiant de l'utilisateur") source: str = Field(default="upload", description="Source du document") type_document_attendu: Optional[DocumentTypeEnum] = Field(None, description="Type de document attendu") class ProcessingResponse(BaseModel): """Réponse de traitement""" document_id: str status: str message: str estimated_processing_time: Optional[int] = None class DocumentAnalysis(BaseModel): """Analyse complète d'un document""" document_id: str type_detecte: DocumentTypeEnum confiance_classification: float texte_extrait: str entites_extraites: Dict[str, Any] verifications_externes: Dict[str, Any] score_vraisemblance: float avis_synthese: str recommandations: List[str] timestamp_analyse: str @router.post("/notary/upload", response_model=ProcessingResponse) async def upload_notary_document( background_tasks: BackgroundTasks, file: UploadFile = File(..., description="Document à traiter"), id_dossier: str = Form(..., description="Identifiant du dossier"), etude_id: str = Form(..., description="Identifiant de l'étude"), utilisateur_id: str = Form(..., description="Identifiant de l'utilisateur"), source: str = Form(default="upload", description="Source du document"), type_document_attendu: Optional[str] = Form(None, description="Type de document attendu") ): """ Upload et traitement d'un document notarial Supporte les formats : PDF, JPEG, PNG, TIFF, HEIC """ # Validation du type de fichier allowed_types = { "application/pdf": "PDF", "image/jpeg": "JPEG", "image/png": "PNG", "image/tiff": "TIFF", "image/heic": "HEIC" } if file.content_type not in allowed_types: raise HTTPException( status_code=415, detail=f"Type de fichier non supporté. Types acceptés: {', '.join(allowed_types.keys())}" ) # Génération d'un ID unique pour le document document_id = str(uuid.uuid4()) # Validation du type de document attendu type_attendu = None if type_document_attendu: try: type_attendu = DocumentTypeEnum(type_document_attendu) except ValueError: raise HTTPException( status_code=400, detail=f"Type de document invalide. Types supportés: {[t.value for t in DocumentTypeEnum]}" ) # Création de la demande de traitement request_data = ProcessingRequest( id_dossier=id_dossier, etude_id=etude_id, utilisateur_id=utilisateur_id, source=source, type_document_attendu=type_attendu ) try: # Enregistrement du document et lancement du traitement background_tasks.add_task( process_notary_document, document_id=document_id, file=file, request_data=request_data ) logger.info(f"Document {document_id} mis en file de traitement") return ProcessingResponse( document_id=document_id, status="queued", message="Document mis en file de traitement", estimated_processing_time=120 # 2 minutes estimées ) except Exception as e: logger.error(f"Erreur lors de l'upload du document {document_id}: {e}") raise HTTPException( status_code=500, detail="Erreur lors du traitement du document" ) @router.get("/notary/document/{document_id}/status") async def get_document_status(document_id: str): """ Récupération du statut de traitement d'un document """ try: # TODO: Récupérer le statut depuis la base de données # Pour l'instant, simulation return { "document_id": document_id, "status": "processing", "progress": 45, "current_step": "extraction_entites", "estimated_completion": time.time() + 60 } except Exception as e: logger.error(f"Erreur lors de la récupération du statut {document_id}: {e}") raise HTTPException( status_code=500, detail="Erreur lors de la récupération du statut" ) @router.get("/notary/document/{document_id}/analysis", response_model=DocumentAnalysis) async def get_document_analysis(document_id: str): """ Récupération de l'analyse complète d'un document """ try: # TODO: Récupérer l'analyse depuis la base de données # Pour l'instant, simulation d'une analyse complète return DocumentAnalysis( document_id=document_id, type_detecte=DocumentTypeEnum.ACTE_VENTE, confiance_classification=0.95, texte_extrait="Texte extrait du document...", entites_extraites={ "identites": [ {"nom": "DUPONT", "prenom": "Jean", "type": "vendeur"}, {"nom": "MARTIN", "prenom": "Marie", "type": "acheteur"} ], "adresses": [ {"adresse": "123 rue de la Paix, 75001 Paris", "type": "bien_vendu"} ], "biens": [ {"description": "Appartement 3 pièces", "surface": "75m²", "prix": "250000€"} ] }, verifications_externes={ "cadastre": {"status": "verified", "details": "Parcelle 1234 confirmée"}, "georisques": {"status": "checked", "risques": ["retrait_gonflement_argiles"]}, "bodacc": {"status": "checked", "result": "aucune_annonce"} }, score_vraisemblance=0.92, avis_synthese="Document cohérent et vraisemblable. Vérifications externes positives.", recommandations=[ "Vérifier l'identité des parties avec pièces d'identité", "Contrôler la conformité du prix au marché local" ], timestamp_analyse=time.strftime("%Y-%m-%d %H:%M:%S") ) except Exception as e: logger.error(f"Erreur lors de la récupération de l'analyse {document_id}: {e}") raise HTTPException( status_code=500, detail="Erreur lors de la récupération de l'analyse" ) @router.post("/notary/document/{document_id}/reprocess") async def reprocess_document( document_id: str, background_tasks: BackgroundTasks, force_reclassification: bool = False, force_reverification: bool = False ): """ Retraitement d'un document avec options """ try: # TODO: Implémenter le retraitement background_tasks.add_task( process_notary_document, document_id=document_id, reprocess=True, force_reclassification=force_reclassification, force_reverification=force_reverification ) return { "document_id": document_id, "status": "reprocessing_queued", "message": "Document mis en file de retraitement" } except Exception as e: logger.error(f"Erreur lors du retraitement {document_id}: {e}") raise HTTPException( status_code=500, detail="Erreur lors du retraitement" ) @router.get("/notary/documents") async def list_documents( etude_id: Optional[str] = None, id_dossier: Optional[str] = None, status: Optional[str] = None, limit: int = 50, offset: int = 0 ): """ Liste des documents avec filtres """ try: # TODO: Implémenter la récupération depuis la base de données return { "documents": [], "total": 0, "limit": limit, "offset": offset } except Exception as e: logger.error(f"Erreur lors de la récupération des documents: {e}") raise HTTPException( status_code=500, detail="Erreur lors de la récupération des documents" ) @router.get("/notary/stats") async def get_processing_stats(): """ Statistiques de traitement """ try: # TODO: Implémenter les statistiques réelles return { "documents_traites": 1250, "documents_en_cours": 15, "taux_reussite": 0.98, "temps_moyen_traitement": 95, "types_documents": { "acte_vente": 450, "acte_donation": 200, "acte_succession": 300, "cni": 150, "contrat": 100, "autre": 50 } } except Exception as e: logger.error(f"Erreur lors de la récupération des statistiques: {e}") raise HTTPException( status_code=500, detail="Erreur lors de la récupération des statistiques" )