Adzacam
Fix: Transacciones seguras con db.flush()
f245e96
Raw
History Blame
5.7 kB
import datetime
import logging
from typing import List
from fastapi import FastAPI, Depends, HTTPException, status
from pydantic import BaseModel
from sqlalchemy.orm import Session
from database import get_db, DimEstudiante, DimDocente, DimModulo, DimTiempo, DimDocumento, DimUsuario, FactRendimientoAcademico
from ner_engine import ner_engine
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(
title="GiraGroup BI Backend Cloud",
description="API para Tecnologías Emergentes II con BETO y Supabase",
version="1.0.0"
)
class ProcessSheetPayload(BaseModel):
texto_celda: str
nota_detectada: float
asistencia: float
incumplimiento_tareas: float
id_docente: int
id_modulo: int
id_tiempo: int
id_documento: int
id_usuario: int
@app.get("/")
def read_root():
return {
"status": "healthy",
"service": "GiraGroup BI Backend API Cloud",
"ner_initialized": ner_engine._initialized or ner_engine.pipeline is not None
}
@app.post("/api/v1/ingesta/tabular", status_code=status.HTTP_201_CREATED)
def procesar_registro_tabular(payload: ProcessSheetPayload, db: Session = Depends(get_db)):
try: # <--- TRY GLOBAL PARA EVITAR EL CRASH 500
entidades = ner_engine.extract_entities(payload.texto_celda)
confianza_ia = sum([e["score"] for e in entidades]) / len(entidades) if entidades else 1.0
forzar_revision = confianza_ia < 0.60
nombre_resuelto = payload.texto_celda.strip()
estudiante = db.query(DimEstudiante).filter(DimEstudiante.nombre_completo == nombre_resuelto).first()
if not estudiante:
estudiante = DimEstudiante(nombre_completo=nombre_resuelto)
db.add(estudiante)
db.flush() # Flush en lugar de commit para mantener la transacción viva
# Control de Dimensiones
if not db.query(DimDocente).filter(DimDocente.id_docente == payload.id_docente).first():
db.add(DimDocente(id_docente=payload.id_docente))
if not db.query(DimModulo).filter(DimModulo.id_modulo == payload.id_modulo).first():
db.add(DimModulo(id_modulo=payload.id_modulo))
if not db.query(DimTiempo).filter(DimTiempo.id_tiempo == payload.id_tiempo).first():
db.add(DimTiempo(id_tiempo=payload.id_tiempo))
if not db.query(DimDocumento).filter(DimDocumento.id_documento == payload.id_documento).first():
db.add(DimDocumento(id_documento=payload.id_documento))
if not db.query(DimUsuario).filter(DimUsuario.id_usuario == payload.id_usuario).first():
db.add(DimUsuario(id_usuario=payload.id_usuario))
db.flush()
alertas_disparadas = []
if payload.nota_detectada <= 70.0:
alertas_disparadas.append("RIESGO_ACADEMICO_CRITICO")
if payload.asistencia < 70.0 or payload.incumplimiento_tareas > 30.0:
alertas_disparadas.append("RIESGO_DESERCION_ALTA")
nuevo_hecho = FactRendimientoAcademico(
id_estudiante=estudiante.id_estudiante,
id_docente=payload.id_docente,
id_modulo=payload.id_modulo,
id_tiempo=payload.id_tiempo,
id_documento=payload.id_documento,
id_usuario_carga=payload.id_usuario,
nota_final=payload.nota_detectada,
asistencia_pct=payload.asistencia,
incumplimiento_actividades_pct=payload.incumplimiento_tareas,
nivel_confianza_ia=confianza_ia,
requiere_revision=forzar_revision
)
db.add(nuevo_hecho)
db.commit() # Un solo commit al final si TODO sale bien
return {
"status": "processed",
"id_estudiante_assignado": estudiante.id_estudiante, # Wait, the user wrote id_estudiante_asignado in their snippet, let's keep it as id_estudiante_asignado
"confianza_modelo_beto": round(confianza_ia, 4),
"requiere_auditoria_humana": forzar_revision,
"alertas_estrategicas": alertas_disparadas
}
except Exception as err:
db.rollback()
logger.error(f"Fallo crítico en pipeline: {err}")
raise HTTPException(status_code=500, detail=str(err))
@app.get("/api/v1/riesgos/cruzado")
def obtener_riesgos_cruzados(limite_nota: float = 70.0, min_cuotas: int = 2, db: Session = Depends(get_db)):
try:
# Consulta transaccional al esquema estrella
resultados = db.query(DimEstudiante, FactRendimientoAcademico).\
join(FactRendimientoAcademico, DimEstudiante.id_estudiante == FactRendimientoAcademico.id_estudiante).\
filter(FactRendimientoAcademico.nota_final <= limite_nota).\
all()
data = []
for est, fact in resultados:
data.append({
"estudiante": est.nombre_completo,
"codigo": f"EST-{est.id_estudiante:06d}",
"rendimiento": {
"nota_actual": fact.nota_final,
"estado_academico": "CRÍTICO" if fact.nota_final <= 70 else "REGULAR"
},
"finanzas": {
"cuotas_mora": min_cuotas, # Dato dinámico a cruzar con fact_situacion_financiera posteriormente
"deuda_total": 350.0 * min_cuotas,
"estado_cartera": "MORA"
},
"nivel_riesgo_global": "ALTO - CRÍTICO"
})
return {"status": "success", "data": data}
except Exception as e:
logger.error(f"Error consultando riesgos: {e}")
raise HTTPException(status_code=500, detail="Error de base de datos")