
- 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
160 lines
4.7 KiB
Python
160 lines
4.7 KiB
Python
"""
|
|
Routes d'administration
|
|
"""
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.orm import Session
|
|
from typing import Dict, Any
|
|
import logging
|
|
|
|
from domain.database import get_db, Document, ProcessingLog
|
|
from domain.models import DocumentStatus
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
@router.get("/stats")
|
|
async def get_statistics(db: Session = Depends(get_db)):
|
|
"""
|
|
Statistiques générales du système
|
|
"""
|
|
try:
|
|
# Statistiques des documents
|
|
total_documents = db.query(Document).count()
|
|
|
|
status_counts = {}
|
|
for status in DocumentStatus:
|
|
count = db.query(Document).filter(Document.status == status.value).count()
|
|
status_counts[status.value] = count
|
|
|
|
# Statistiques des étapes de traitement
|
|
processing_stats = db.query(
|
|
ProcessingLog.step_name,
|
|
ProcessingLog.status,
|
|
db.func.count(ProcessingLog.id).label('count')
|
|
).group_by(
|
|
ProcessingLog.step_name,
|
|
ProcessingLog.status
|
|
).all()
|
|
|
|
# Statistiques par étude
|
|
etude_stats = db.query(
|
|
Document.etude_id,
|
|
db.func.count(Document.id).label('count')
|
|
).group_by(Document.etude_id).all()
|
|
|
|
return {
|
|
"documents": {
|
|
"total": total_documents,
|
|
"by_status": status_counts
|
|
},
|
|
"processing": [
|
|
{
|
|
"step": stat.step_name,
|
|
"status": stat.status,
|
|
"count": stat.count
|
|
}
|
|
for stat in processing_stats
|
|
],
|
|
"etudes": [
|
|
{
|
|
"etude_id": stat.etude_id,
|
|
"document_count": stat.count
|
|
}
|
|
for stat in etude_stats
|
|
]
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Erreur lors de la récupération des statistiques: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.post("/documents/{document_id}/retry")
|
|
async def retry_document_processing(
|
|
document_id: str,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Relancer le traitement 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é")
|
|
|
|
if document.status not in [DocumentStatus.FAILED.value, DocumentStatus.MANUAL_REVIEW.value]:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="Le document ne peut être relancé que s'il est en échec ou en révision manuelle"
|
|
)
|
|
|
|
# Réinitialisation du statut
|
|
document.status = DocumentStatus.PENDING.value
|
|
document.processing_steps = {}
|
|
document.errors = []
|
|
db.commit()
|
|
|
|
# Relance du traitement
|
|
from tasks.enqueue import enqueue_import
|
|
meta = {
|
|
"id_dossier": document.id_dossier,
|
|
"source": document.source,
|
|
"etude_id": document.etude_id,
|
|
"utilisateur_id": document.utilisateur_id,
|
|
"filename": document.filename,
|
|
"mime": document.mime_type,
|
|
"received_at": int(time.time())
|
|
}
|
|
|
|
enqueue_import(document_id, meta)
|
|
|
|
logger.info(f"Retraitement lancé pour le document {document_id}")
|
|
|
|
return {"message": "Retraitement lancé avec succès"}
|
|
|
|
@router.post("/documents/{document_id}/manual-review")
|
|
async def mark_for_manual_review(
|
|
document_id: str,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Marquer un document pour révision manuelle
|
|
"""
|
|
document = db.query(Document).filter(Document.id == document_id).first()
|
|
|
|
if not document:
|
|
raise HTTPException(status_code=404, detail="Document non trouvé")
|
|
|
|
document.status = DocumentStatus.MANUAL_REVIEW.value
|
|
document.manual_review = True
|
|
db.commit()
|
|
|
|
logger.info(f"Document {document_id} marqué pour révision manuelle")
|
|
|
|
return {"message": "Document marqué pour révision manuelle"}
|
|
|
|
@router.get("/processing-logs/{document_id}")
|
|
async def get_processing_logs(
|
|
document_id: str,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Récupération des logs de traitement d'un document
|
|
"""
|
|
logs = db.query(ProcessingLog).filter(
|
|
ProcessingLog.document_id == document_id
|
|
).order_by(ProcessingLog.started_at.desc()).all()
|
|
|
|
return [
|
|
{
|
|
"id": log.id,
|
|
"step_name": log.step_name,
|
|
"status": log.status,
|
|
"started_at": log.started_at,
|
|
"completed_at": log.completed_at,
|
|
"duration": log.duration,
|
|
"error_message": log.error_message,
|
|
"metadata": log.metadata
|
|
}
|
|
for log in logs
|
|
]
|