root 5d8ad901d1 Initial commit: Pipeline notarial complet
- 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
2025-09-08 22:05:22 +02:00

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"}