Adzacam
Feat: Endpoint GET para consulta cruzada de riesgos académicos
11036a4
Raw
History Blame
4.67 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, 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"
)
# El resto del código de ProcessSheetPayload y endpoints se mantiene exactamente igual...
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)):
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 = False
if confianza_ia < 0.60:
forzar_revision = True
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.commit()
db.refresh(estudiante)
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")
try:
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()
return {
"status": "processed",
"id_estudiante_asignado": estudiante.id_estudiante,
"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 en persistencia: {err}")
raise HTTPException(status_code=500, detail="Error al escribir en Supabase.")
@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")