Spaces:
Sleeping
Sleeping
Adzacam commited on
Commit ·
e772171
1
Parent(s): 2f7e5a3
feat: implement batch NLP analysis endpoint with automated entity resolution and data integration logic.
Browse files
app.py
CHANGED
|
@@ -143,6 +143,11 @@ class ProcessSheetPayloadRaw(BaseModel):
|
|
| 143 |
institucion: Optional[str] = None
|
| 144 |
tipo_fuente: Optional[str] = None
|
| 145 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
@app.get("/")
|
| 147 |
def read_root():
|
| 148 |
return {
|
|
@@ -761,5 +766,153 @@ def get_dashboard_kpis(db: Session = Depends(get_db)):
|
|
| 761 |
}
|
| 762 |
}
|
| 763 |
except Exception as e:
|
| 764 |
-
|
| 765 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
institucion: Optional[str] = None
|
| 144 |
tipo_fuente: Optional[str] = None
|
| 145 |
|
| 146 |
+
from typing import Union
|
| 147 |
+
|
| 148 |
+
class BatchPayload(BaseModel):
|
| 149 |
+
records: List[ProcessSheetPayloadRaw]
|
| 150 |
+
|
| 151 |
@app.get("/")
|
| 152 |
def read_root():
|
| 153 |
return {
|
|
|
|
| 766 |
}
|
| 767 |
}
|
| 768 |
except Exception as e:
|
| 769 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 770 |
+
|
| 771 |
+
def anonymize_name(name: str) -> str:
|
| 772 |
+
if not name or name.strip() == "":
|
| 773 |
+
return "Desconocido"
|
| 774 |
+
parts = name.strip().split()
|
| 775 |
+
anonymized_parts = []
|
| 776 |
+
for p in parts:
|
| 777 |
+
if len(p) > 1:
|
| 778 |
+
anonymized_parts.append(p[0] + "***")
|
| 779 |
+
else:
|
| 780 |
+
anonymized_parts.append(p + "***")
|
| 781 |
+
return " ".join(anonymized_parts)
|
| 782 |
+
|
| 783 |
+
@app.post("/api/v1/nlp/batch-analyze")
|
| 784 |
+
def batch_analyze_nlp(
|
| 785 |
+
payload: Union[BatchPayload, List[ProcessSheetPayload]],
|
| 786 |
+
db: Session = Depends(get_db)
|
| 787 |
+
):
|
| 788 |
+
if isinstance(payload, list):
|
| 789 |
+
records = payload
|
| 790 |
+
else:
|
| 791 |
+
records = payload.records
|
| 792 |
+
|
| 793 |
+
results = []
|
| 794 |
+
estudiantes_existentes = db.query(DimEstudiante).all()
|
| 795 |
+
|
| 796 |
+
for record in records:
|
| 797 |
+
# Extraer NLP
|
| 798 |
+
entidades = ner_engine.extract_entities(record.texto_celda)
|
| 799 |
+
confianza_ia = sum([e["score"] for e in entidades]) / len(entidades) if entidades else 1.0
|
| 800 |
+
nombre_resuelto = record.texto_celda[:200].strip()
|
| 801 |
+
|
| 802 |
+
# Consultar log_auditoria_nlp primero
|
| 803 |
+
log_memoria = db.query(LogAuditoriaNlp).filter(
|
| 804 |
+
LogAuditoriaNlp.texto_original == nombre_resuelto
|
| 805 |
+
).order_by(LogAuditoriaNlp.created_at.desc()).first()
|
| 806 |
+
|
| 807 |
+
estudiante = None
|
| 808 |
+
requiere_revision = False
|
| 809 |
+
|
| 810 |
+
if log_memoria and log_memoria.correccion_humana != "PENDIENTE":
|
| 811 |
+
nombre_resuelto = log_memoria.correccion_humana
|
| 812 |
+
confianza_ia = 1.0
|
| 813 |
+
best_match, _ = find_best_match(nombre_resuelto, estudiantes_existentes)
|
| 814 |
+
if best_match:
|
| 815 |
+
estudiante = best_match
|
| 816 |
+
else:
|
| 817 |
+
estudiante = DimEstudiante(nombre_completo=nombre_resuelto, codigo_estudiante=record.codigo_estudiante)
|
| 818 |
+
db.add(estudiante)
|
| 819 |
+
db.flush()
|
| 820 |
+
estudiantes_existentes.append(estudiante)
|
| 821 |
+
else:
|
| 822 |
+
best_match, score = find_best_match(nombre_resuelto, estudiantes_existentes)
|
| 823 |
+
if best_match and score >= 0.8:
|
| 824 |
+
estudiante = best_match
|
| 825 |
+
if score < confianza_ia:
|
| 826 |
+
confianza_ia = score
|
| 827 |
+
else:
|
| 828 |
+
if confianza_ia >= 0.60:
|
| 829 |
+
estudiante = DimEstudiante(nombre_completo=nombre_resuelto, codigo_estudiante=record.codigo_estudiante)
|
| 830 |
+
db.add(estudiante)
|
| 831 |
+
db.flush()
|
| 832 |
+
estudiantes_existentes.append(estudiante)
|
| 833 |
+
|
| 834 |
+
# Calculo de alertas
|
| 835 |
+
alertas = []
|
| 836 |
+
if record.nota_detectada <= 70.0:
|
| 837 |
+
alertas.append("RIESGO_ACADEMICO_CRITICO")
|
| 838 |
+
if record.asistencia < 70.0 or record.incumplimiento_tareas > 30.0:
|
| 839 |
+
alertas.append("RIESGO_DESERCION_ALTA")
|
| 840 |
+
|
| 841 |
+
if confianza_ia < 0.60:
|
| 842 |
+
log = LogAuditoriaNlp(
|
| 843 |
+
texto_original=nombre_resuelto,
|
| 844 |
+
prediccion_beto=nombre_resuelto,
|
| 845 |
+
confianza_ia=confianza_ia,
|
| 846 |
+
correccion_humana="PENDIENTE",
|
| 847 |
+
usuario_auditor=getattr(record, 'id_usuario', 1)
|
| 848 |
+
)
|
| 849 |
+
db.add(log)
|
| 850 |
+
db.flush()
|
| 851 |
+
requiere_revision = True
|
| 852 |
+
else:
|
| 853 |
+
# Insert into Constellation Schema
|
| 854 |
+
id_tiempo_val = getattr(record, 'id_tiempo', 1)
|
| 855 |
+
id_docente_val = getattr(record, 'id_docente', 1)
|
| 856 |
+
id_modulo_val = getattr(record, 'id_modulo', 1)
|
| 857 |
+
id_documento_val = getattr(record, 'id_documento', 1)
|
| 858 |
+
id_usuario_val = getattr(record, 'id_usuario', 1)
|
| 859 |
+
|
| 860 |
+
# Ensure dimensions exist
|
| 861 |
+
if not db.query(DimDocente).filter(DimDocente.id_docente == id_docente_val).first():
|
| 862 |
+
db.add(DimDocente(id_docente=id_docente_val, nombre_completo="Docente Generico", area_especialidad="Generico"))
|
| 863 |
+
if not db.query(DimModulo).filter(DimModulo.id_modulo == id_modulo_val).first():
|
| 864 |
+
db.add(DimModulo(id_modulo=id_modulo_val, nombre_modulo=getattr(record, 'modulo', "Modulo Generico") or "Modulo Generico", nombre_institucion=getattr(record, 'institucion', "GiraGroup") or "GiraGroup", programa=getattr(record, 'programa', "General") or "General"))
|
| 865 |
+
if not db.query(DimTiempo).filter(DimTiempo.id_tiempo == id_tiempo_val).first():
|
| 866 |
+
db.add(DimTiempo(id_tiempo=id_tiempo_val, gestion=2026, semestre=1, mes="Mayo"))
|
| 867 |
+
if not db.query(DimOrigenDocumental).filter(DimOrigenDocumental.id_documento == id_documento_val).first():
|
| 868 |
+
db.add(DimOrigenDocumental(id_documento=id_documento_val, tipo_documento="SHEET", nombre_archivo="carga_automatica"))
|
| 869 |
+
if not db.query(Users).filter(Users.id == id_usuario_val).first():
|
| 870 |
+
db.add(Users(id=id_usuario_val, username=f"sistema_{id_usuario_val}", hashed_password="$placeholder$", role="admin"))
|
| 871 |
+
db.flush()
|
| 872 |
+
|
| 873 |
+
if record.tipo_fuente == "FINANCE":
|
| 874 |
+
fact = FactSituacionFinanciera(
|
| 875 |
+
id_estudiante=estudiante.id_estudiante,
|
| 876 |
+
id_tiempo=id_tiempo_val,
|
| 877 |
+
monto_deuda=getattr(record, 'monto_deuda', 0),
|
| 878 |
+
cuotas_impagas=getattr(record, 'cuotas_impagas', 0),
|
| 879 |
+
estado_cartera="AL_DIA",
|
| 880 |
+
tipo_alerta="NINGUNA"
|
| 881 |
+
)
|
| 882 |
+
db.add(fact)
|
| 883 |
+
else:
|
| 884 |
+
fact = FactRendimientoAcademico(
|
| 885 |
+
id_estudiante=estudiante.id_estudiante,
|
| 886 |
+
id_docente=id_docente_val,
|
| 887 |
+
id_modulo=id_modulo_val,
|
| 888 |
+
id_tiempo=id_tiempo_val,
|
| 889 |
+
id_documento=id_documento_val,
|
| 890 |
+
id_usuario_carga=id_usuario_val,
|
| 891 |
+
nota_final=record.nota_detectada,
|
| 892 |
+
asistencia_pct=record.asistencia,
|
| 893 |
+
incumplimiento_actividades_pct=record.incumplimiento_tareas,
|
| 894 |
+
nivel_confianza_ia=confianza_ia,
|
| 895 |
+
requiere_revision=False
|
| 896 |
+
)
|
| 897 |
+
db.add(fact)
|
| 898 |
+
|
| 899 |
+
results.append({
|
| 900 |
+
"anonymized_name": anonymize_name(nombre_resuelto),
|
| 901 |
+
"nombre_resuelto": nombre_resuelto,
|
| 902 |
+
"confianza_ia": round(float(confianza_ia), 4),
|
| 903 |
+
"alertas": alertas,
|
| 904 |
+
"requiere_revision": requiere_revision,
|
| 905 |
+
"status": "pending_human_review" if requiere_revision else "inserted"
|
| 906 |
+
})
|
| 907 |
+
|
| 908 |
+
try:
|
| 909 |
+
db.commit()
|
| 910 |
+
except Exception as e:
|
| 911 |
+
db.rollback()
|
| 912 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 913 |
+
|
| 914 |
+
return {
|
| 915 |
+
"status": "success",
|
| 916 |
+
"processed_count": len(records),
|
| 917 |
+
"results": results
|
| 918 |
+
}
|