
- Infrastructure complète de traitement de documents notariaux - API FastAPI d'ingestion et d'orchestration - Pipelines Celery pour le traitement asynchrone - Support des formats PDF, JPEG, PNG, TIFF, HEIC - OCR avec Tesseract et correction lexicale - Classification automatique des documents avec Ollama - Extraction de données structurées - Indexation dans AnythingLLM et OpenSearch - Système de vérifications et contrôles métier - Base de données PostgreSQL pour le métier - Stockage objet avec MinIO - Base de données graphe Neo4j - Recherche plein-texte avec OpenSearch - Supervision avec Prometheus et Grafana - Scripts d'installation pour Debian - Documentation complète - Tests unitaires et de performance - Service systemd pour le déploiement - Scripts de déploiement automatisés
187 lines
5.5 KiB
Python
187 lines
5.5 KiB
Python
"""
|
|
Routes pour la gestion des documents
|
|
"""
|
|
from fastapi import APIRouter, UploadFile, File, Form, HTTPException, Depends, Query
|
|
from sqlalchemy.orm import Session
|
|
from typing import List, Optional
|
|
import uuid
|
|
import time
|
|
import logging
|
|
|
|
from domain.database import get_db, Document, ProcessingLog
|
|
from domain.models import DocumentResponse, DocumentInfo, DocumentStatus, DocumentType
|
|
from tasks.enqueue import enqueue_import
|
|
from utils.storage import store_document
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
@router.post("/import", response_model=DocumentResponse)
|
|
async def import_document(
|
|
file: UploadFile = File(...),
|
|
id_dossier: str = Form(...),
|
|
source: str = Form("upload"),
|
|
etude_id: str = Form(...),
|
|
utilisateur_id: str = Form(...),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Import d'un nouveau document dans le pipeline
|
|
"""
|
|
try:
|
|
# Vérification du type de fichier
|
|
if file.content_type not in [dt.value for dt in DocumentType]:
|
|
raise HTTPException(
|
|
status_code=415,
|
|
detail=f"Type de fichier non supporté: {file.content_type}"
|
|
)
|
|
|
|
# Génération d'un ID unique
|
|
doc_id = str(uuid.uuid4())
|
|
|
|
# Lecture du contenu du fichier
|
|
content = await file.read()
|
|
file_size = len(content)
|
|
|
|
# Stockage du document
|
|
storage_path = await store_document(doc_id, content, file.filename)
|
|
|
|
# Création de l'enregistrement en base
|
|
document = Document(
|
|
id=doc_id,
|
|
filename=file.filename or "unknown",
|
|
mime_type=file.content_type,
|
|
size=file_size,
|
|
status=DocumentStatus.PENDING.value,
|
|
id_dossier=id_dossier,
|
|
etude_id=etude_id,
|
|
utilisateur_id=utilisateur_id,
|
|
source=source
|
|
)
|
|
|
|
db.add(document)
|
|
db.commit()
|
|
db.refresh(document)
|
|
|
|
# Enqueue du traitement
|
|
meta = {
|
|
"id_dossier": id_dossier,
|
|
"source": source,
|
|
"etude_id": etude_id,
|
|
"utilisateur_id": utilisateur_id,
|
|
"filename": file.filename,
|
|
"mime": file.content_type,
|
|
"received_at": int(time.time())
|
|
}
|
|
|
|
enqueue_import(doc_id, meta)
|
|
|
|
logger.info(f"Document {doc_id} importé avec succès")
|
|
|
|
return DocumentResponse(
|
|
status="queued",
|
|
id_document=doc_id,
|
|
message="Document en cours de traitement"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Erreur lors de l'import du document: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.get("/documents/{document_id}", response_model=DocumentInfo)
|
|
async def get_document(
|
|
document_id: str,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Récupération des informations d'un document
|
|
"""
|
|
document = db.query(Document).filter(Document.id == document_id).first()
|
|
|
|
if not document:
|
|
raise HTTPException(status_code=404, detail="Document non trouvé")
|
|
|
|
return DocumentInfo(
|
|
id=document.id,
|
|
filename=document.filename,
|
|
mime_type=document.mime_type,
|
|
size=document.size,
|
|
status=DocumentStatus(document.status),
|
|
id_dossier=document.id_dossier,
|
|
etude_id=document.etude_id,
|
|
utilisateur_id=document.utilisateur_id,
|
|
created_at=document.created_at,
|
|
updated_at=document.updated_at,
|
|
processing_steps=document.processing_steps,
|
|
extracted_data=document.extracted_data,
|
|
errors=document.errors
|
|
)
|
|
|
|
@router.get("/documents", response_model=List[DocumentInfo])
|
|
async def list_documents(
|
|
etude_id: Optional[str] = Query(None),
|
|
id_dossier: Optional[str] = Query(None),
|
|
status: Optional[DocumentStatus] = Query(None),
|
|
limit: int = Query(50, le=100),
|
|
offset: int = Query(0, ge=0),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Liste des documents avec filtres
|
|
"""
|
|
query = db.query(Document)
|
|
|
|
if etude_id:
|
|
query = query.filter(Document.etude_id == etude_id)
|
|
|
|
if id_dossier:
|
|
query = query.filter(Document.id_dossier == id_dossier)
|
|
|
|
if status:
|
|
query = query.filter(Document.status == status.value)
|
|
|
|
documents = query.offset(offset).limit(limit).all()
|
|
|
|
return [
|
|
DocumentInfo(
|
|
id=doc.id,
|
|
filename=doc.filename,
|
|
mime_type=doc.mime_type,
|
|
size=doc.size,
|
|
status=DocumentStatus(doc.status),
|
|
id_dossier=doc.id_dossier,
|
|
etude_id=doc.etude_id,
|
|
utilisateur_id=doc.utilisateur_id,
|
|
created_at=doc.created_at,
|
|
updated_at=doc.updated_at,
|
|
processing_steps=doc.processing_steps,
|
|
extracted_data=doc.extracted_data,
|
|
errors=doc.errors
|
|
)
|
|
for doc in documents
|
|
]
|
|
|
|
@router.delete("/documents/{document_id}")
|
|
async def delete_document(
|
|
document_id: str,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Suppression d'un document
|
|
"""
|
|
document = db.query(Document).filter(Document.id == document_id).first()
|
|
|
|
if not document:
|
|
raise HTTPException(status_code=404, detail="Document non trouvé")
|
|
|
|
# Suppression des logs de traitement
|
|
db.query(ProcessingLog).filter(ProcessingLog.document_id == document_id).delete()
|
|
|
|
# Suppression du document
|
|
db.delete(document)
|
|
db.commit()
|
|
|
|
logger.info(f"Document {document_id} supprimé")
|
|
|
|
return {"message": "Document supprimé avec succès"}
|