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
Files changed (1) hide show
  1. app.py +155 -2
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
- logger.error(f"Fallo KPI: {e}")
765
- raise HTTPException(status_code=500, detail=str(e))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }