Adzacam commited on
Commit
f8fe5c0
·
1 Parent(s): 11036a4

Fix: Autogeneración de Dimensiones Maestras para evitar Error 500 por Foreign Keys

Browse files
Files changed (2) hide show
  1. app.py +26 -5
  2. database.py +31 -0
app.py CHANGED
@@ -4,7 +4,7 @@ from typing import List
4
  from fastapi import FastAPI, Depends, HTTPException, status
5
  from pydantic import BaseModel
6
  from sqlalchemy.orm import Session
7
- from database import get_db, DimEstudiante, FactRendimientoAcademico
8
  from ner_engine import ner_engine
9
 
10
  logging.basicConfig(level=logging.INFO)
@@ -16,8 +16,6 @@ app = FastAPI(
16
  version="1.0.0"
17
  )
18
 
19
- # El resto del código de ProcessSheetPayload y endpoints se mantiene exactamente igual...
20
-
21
  class ProcessSheetPayload(BaseModel):
22
  texto_celda: str
23
  nota_detectada: float
@@ -39,6 +37,7 @@ def read_root():
39
 
40
  @app.post("/api/v1/ingesta/tabular", status_code=status.HTTP_201_CREATED)
41
  def procesar_registro_tabular(payload: ProcessSheetPayload, db: Session = Depends(get_db)):
 
42
  entidades = ner_engine.extract_entities(payload.texto_celda)
43
  confianza_ia = sum([e["score"] for e in entidades]) / len(entidades) if entidades else 1.0
44
 
@@ -46,21 +45,42 @@ def procesar_registro_tabular(payload: ProcessSheetPayload, db: Session = Depend
46
  if confianza_ia < 0.60:
47
  forzar_revision = True
48
 
 
49
  nombre_resuelto = payload.texto_celda.strip()
50
  estudiante = db.query(DimEstudiante).filter(DimEstudiante.nombre_completo == nombre_resuelto).first()
51
-
52
  if not estudiante:
53
  estudiante = DimEstudiante(nombre_completo=nombre_resuelto)
54
  db.add(estudiante)
55
  db.commit()
56
  db.refresh(estudiante)
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  alertas_disparadas = []
59
  if payload.nota_detectada <= 70.0:
60
  alertas_disparadas.append("RIESGO_ACADEMICO_CRITICO")
61
  if payload.asistencia < 70.0 or payload.incumplimiento_tareas > 30.0:
62
  alertas_disparadas.append("RIESGO_DESERCION_ALTA")
63
 
 
64
  try:
65
  nuevo_hecho = FactRendimientoAcademico(
66
  id_estudiante=estudiante.id_estudiante,
@@ -88,7 +108,8 @@ def procesar_registro_tabular(payload: ProcessSheetPayload, db: Session = Depend
88
  except Exception as err:
89
  db.rollback()
90
  logger.error(f"Fallo en persistencia: {err}")
91
- raise HTTPException(status_code=500, detail="Error al escribir en Supabase.")
 
92
 
93
  @app.get("/api/v1/riesgos/cruzado")
94
 
 
4
  from fastapi import FastAPI, Depends, HTTPException, status
5
  from pydantic import BaseModel
6
  from sqlalchemy.orm import Session
7
+ from database import get_db, DimEstudiante, DimDocente, DimModulo, DimTiempo, DimDocumento, DimUsuario, FactRendimientoAcademico
8
  from ner_engine import ner_engine
9
 
10
  logging.basicConfig(level=logging.INFO)
 
16
  version="1.0.0"
17
  )
18
 
 
 
19
  class ProcessSheetPayload(BaseModel):
20
  texto_celda: str
21
  nota_detectada: float
 
37
 
38
  @app.post("/api/v1/ingesta/tabular", status_code=status.HTTP_201_CREATED)
39
  def procesar_registro_tabular(payload: ProcessSheetPayload, db: Session = Depends(get_db)):
40
+ # 1. Extracción con BETO NLP
41
  entidades = ner_engine.extract_entities(payload.texto_celda)
42
  confianza_ia = sum([e["score"] for e in entidades]) / len(entidades) if entidades else 1.0
43
 
 
45
  if confianza_ia < 0.60:
46
  forzar_revision = True
47
 
48
+ # 2. Resolución de Estudiante
49
  nombre_resuelto = payload.texto_celda.strip()
50
  estudiante = db.query(DimEstudiante).filter(DimEstudiante.nombre_completo == nombre_resuelto).first()
 
51
  if not estudiante:
52
  estudiante = DimEstudiante(nombre_completo=nombre_resuelto)
53
  db.add(estudiante)
54
  db.commit()
55
  db.refresh(estudiante)
56
 
57
+ # 3. Control de Dimensiones (Evitar violación de Foreign Keys)
58
+ # Autogenerar IDs por defecto si no existen
59
+ if not db.query(DimDocente).filter(DimDocente.id_docente == payload.id_docente).first():
60
+ db.add(DimDocente(id_docente=payload.id_docente, nombre="Docente Generico", especialidad="Generico"))
61
+
62
+ if not db.query(DimModulo).filter(DimModulo.id_modulo == payload.id_modulo).first():
63
+ db.add(DimModulo(id_modulo=payload.id_modulo, nombre_modulo="Modulo Generico", version="1.0"))
64
+
65
+ if not db.query(DimTiempo).filter(DimTiempo.id_tiempo == payload.id_tiempo).first():
66
+ db.add(DimTiempo(id_tiempo=payload.id_tiempo, anio=2026, mes=5, dia=30, trimestre=2))
67
+
68
+ if not db.query(DimDocumento).filter(DimDocumento.id_documento == payload.id_documento).first():
69
+ db.add(DimDocumento(id_documento=payload.id_documento, tipo_documento="Consolidado Generico"))
70
+
71
+ if not db.query(DimUsuario).filter(DimUsuario.id_usuario == payload.id_usuario).first():
72
+ db.add(DimUsuario(id_usuario=payload.id_usuario, rol="Sistema", nombre="Admin"))
73
+
74
+ db.commit() # Asegurar que las FK existan antes del INSERT principal
75
+
76
+ # 4. Alertas Estratégicas
77
  alertas_disparadas = []
78
  if payload.nota_detectada <= 70.0:
79
  alertas_disparadas.append("RIESGO_ACADEMICO_CRITICO")
80
  if payload.asistencia < 70.0 or payload.incumplimiento_tareas > 30.0:
81
  alertas_disparadas.append("RIESGO_DESERCION_ALTA")
82
 
83
+ # 5. Persistencia del Hecho (Fact)
84
  try:
85
  nuevo_hecho = FactRendimientoAcademico(
86
  id_estudiante=estudiante.id_estudiante,
 
108
  except Exception as err:
109
  db.rollback()
110
  logger.error(f"Fallo en persistencia: {err}")
111
+ # Retornar detalles del error en consola puede ser útil para ti, aunque en producción no se expone el log del motor
112
+ raise HTTPException(status_code=500, detail=f"Error al escribir en Supabase: {str(err)}")
113
 
114
  @app.get("/api/v1/riesgos/cruzado")
115
 
database.py CHANGED
@@ -32,6 +32,37 @@ class DimEstudiante(Base):
32
  nombre_completo = Column(String(200), nullable=False)
33
  codigo_estudiante = Column(String(50))
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  class FactRendimientoAcademico(Base):
36
  __tablename__ = "fact_rendimiento_academico"
37
  id_hecho_aca = Column(Integer, primary_key=True, index=True)
 
32
  nombre_completo = Column(String(200), nullable=False)
33
  codigo_estudiante = Column(String(50))
34
 
35
+ class DimDocente(Base):
36
+ __tablename__ = "dim_docente"
37
+ id_docente = Column(Integer, primary_key=True)
38
+ nombre = Column(String(200), nullable=False)
39
+ especialidad = Column(String(100))
40
+
41
+ class DimModulo(Base):
42
+ __tablename__ = "dim_modulo"
43
+ id_modulo = Column(Integer, primary_key=True)
44
+ nombre_modulo = Column(String(200), nullable=False)
45
+ version = Column(String(50))
46
+
47
+ class DimTiempo(Base):
48
+ __tablename__ = "dim_tiempo"
49
+ id_tiempo = Column(Integer, primary_key=True)
50
+ anio = Column(Integer, nullable=False)
51
+ mes = Column(Integer, nullable=False)
52
+ dia = Column(Integer, nullable=False)
53
+ trimestre = Column(Integer, nullable=False)
54
+
55
+ class DimDocumento(Base):
56
+ __tablename__ = "dim_documento"
57
+ id_documento = Column(Integer, primary_key=True)
58
+ tipo_documento = Column(String(100), nullable=False)
59
+
60
+ class DimUsuario(Base):
61
+ __tablename__ = "dim_usuario"
62
+ id_usuario = Column(Integer, primary_key=True)
63
+ rol = Column(String(50), nullable=False)
64
+ nombre = Column(String(100), nullable=False)
65
+
66
  class FactRendimientoAcademico(Base):
67
  __tablename__ = "fact_rendimiento_academico"
68
  id_hecho_aca = Column(Integer, primary_key=True, index=True)