Edoruin commited on
Commit
41f3127
·
1 Parent(s): 2b07a83

setting FACES manager databases for persistence after docker down

Browse files
Files changed (2) hide show
  1. app/main.py +83 -48
  2. app/templates/prestamos.html +3 -1
app/main.py CHANGED
@@ -13,6 +13,7 @@ import uuid
13
  import threading
14
  import time
15
  import base64
 
16
  import requests
17
  import datetime
18
  from flask import Flask, render_template, request, jsonify, redirect, url_for, flash
@@ -355,7 +356,7 @@ class ClassroomManager(HFDatasetManager):
355
  """Registra asistencia buscando por ID o Nombre."""
356
  today = datetime.datetime.now().strftime("%Y-%m-%d")
357
  recorded = False
358
- student_name_found = None
359
 
360
  for course in self.classrooms:
361
  for student in course['students']:
@@ -365,12 +366,16 @@ class ClassroomManager(HFDatasetManager):
365
  if today not in student['attendance']:
366
  student['attendance'].append(today)
367
  recorded = True
368
- student_name_found = student['name']
 
 
 
 
369
 
370
  if recorded:
371
  self.save()
372
 
373
- return student_name_found # Retornamos el nombre para la notificación
374
 
375
  def delete_course(self, course_id):
376
  """Elimina un curso por ID."""
@@ -471,13 +476,21 @@ class FaceManager(HFDatasetManager):
471
  local_filename="faces.json"
472
  )
473
  self.faces = self._load()
 
 
 
 
 
 
 
 
474
 
475
  def _load(self):
476
  """Carga los rostros desde HF Dataset o archivo JSON local."""
477
  if self.use_hf:
478
  data = self._load_from_hf()
479
  if data: # Si hay datos (no vacío)
480
- print(f"[FaceManager] Loaded {len(data)} faces from HF Dataset")
481
  return data
482
 
483
  if data is not None:
@@ -486,7 +499,7 @@ class FaceManager(HFDatasetManager):
486
  print("[FaceManager] Failed to load from HF, checking local...")
487
 
488
  data = self._load_from_local()
489
- print(f"[FaceManager] Loaded {len(data)} faces from local JSON")
490
  return data
491
 
492
  def save(self):
@@ -498,19 +511,68 @@ class FaceManager(HFDatasetManager):
498
  if self.use_hf:
499
  success = self._save_to_hf(self.faces)
500
  if success:
501
- print(f"[FaceManager] Saved {len(self.faces)} faces to HF Dataset")
502
  else:
503
  print("[FaceManager] Failed to save to HF, data saved locally only")
504
 
505
- def add_face(self, label, descriptor):
506
- # El descriptor es una lista de 128 floats (face-api.js)
507
- self.faces.append({
 
 
508
  "label": label,
509
- "descriptor": descriptor
 
 
 
510
  })
511
  self.save()
512
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
  def get_all(self):
 
 
514
  return self.faces
515
 
516
  face_mgr = FaceManager()
@@ -562,7 +624,9 @@ def mark_as_delivered(loan_id):
562
  rd_now = utc_now - datetime.timedelta(hours=4)
563
  ahora = rd_now.strftime("%H:%M")
564
 
565
- loan_mgr.update_status(loan_id, "DELIVERED")
 
 
566
  socketio.emit('notification', {"text": f"{loan['Solicitante']} ha entregado", "color": "blue"})
567
  return True, loan, ahora
568
  return False, None, None
@@ -877,7 +941,8 @@ def api_prestamo():
877
  "devolucion": hora_retorno,
878
  "item": full_items_string,
879
  "status_loan": "PENDING",
880
- "timestamp": datetime.datetime.now().isoformat()
 
881
  }
882
 
883
  loan_mgr.add_loan(new_loan)
@@ -1023,41 +1088,11 @@ def api_faces():
1023
  course_id = data.get('course_id')
1024
 
1025
  if label and descriptor:
1026
- # Aquí idealmente guardaríamos el descriptor asociado al ID del estudiante
1027
- # Por simplicidad en este prototipo sin DB vectorial real,
1028
- # guardamos un JSON local "faces.json" o similar.
1029
-
1030
- face_data = {
1031
- "label": label, # Nombre para mostrar
1032
- "descriptor": descriptor,
1033
- "student_id": student_id,
1034
- "course_id": course_id
1035
- }
1036
-
1037
- # Cargar existentes
1038
- faces = []
1039
- if os.path.exists('faces.json'):
1040
- try:
1041
- with open('faces.json', 'r') as f:
1042
- faces = json.load(f)
1043
- except:
1044
- pass
1045
-
1046
- faces.append(face_data)
1047
-
1048
- with open('faces.json', 'w') as f:
1049
- json.dump(faces, f)
1050
-
1051
  return jsonify({"status": "success"})
1052
 
1053
  # GET: Devolver rostros guardados
1054
- if os.path.exists('faces.json'):
1055
- try:
1056
- with open('faces.json', 'r') as f:
1057
- return jsonify(json.load(f))
1058
- except:
1059
- return jsonify([])
1060
- return jsonify([])
1061
 
1062
  @app.route('/api/attendance', methods=['POST'])
1063
  def api_attendance():
@@ -1067,13 +1102,13 @@ def api_attendance():
1067
  if label:
1068
  print(f"[ASISTENCIA] Registrando: {label}")
1069
  # Registrar en el sistema de aulas
1070
- # ClassroomManager buscará por nombre o ID
1071
- if classroom_manager.record_attendance(label):
 
 
1072
  socketio.emit('notification', {'text': f'Bienvenido/a {label}', 'color': 'green'})
1073
  return jsonify({"status": "success", "message": f"Asistencia registrada para {label}"})
1074
  else:
1075
- # Si no se encuentra en una clase, igual notificar pero indicar advertencia?
1076
- # O asumimos que es un invitado.
1077
  socketio.emit('notification', {'text': f'Hola {label} (No inscrito)', 'color': 'blue'})
1078
  return jsonify({"status": "warning", "message": "Registrado pero no vinculado a curso"})
1079
 
 
13
  import threading
14
  import time
15
  import base64
16
+ import csv
17
  import requests
18
  import datetime
19
  from flask import Flask, render_template, request, jsonify, redirect, url_for, flash
 
356
  """Registra asistencia buscando por ID o Nombre."""
357
  today = datetime.datetime.now().strftime("%Y-%m-%d")
358
  recorded = False
359
+ student_info = None
360
 
361
  for course in self.classrooms:
362
  for student in course['students']:
 
366
  if today not in student['attendance']:
367
  student['attendance'].append(today)
368
  recorded = True
369
+ student_info = {
370
+ "id": s_id,
371
+ "name": student['name'],
372
+ "course_id": course['id']
373
+ }
374
 
375
  if recorded:
376
  self.save()
377
 
378
+ return student_info # Retornamos info del estudiante para el log
379
 
380
  def delete_course(self, course_id):
381
  """Elimina un curso por ID."""
 
476
  local_filename="faces.json"
477
  )
478
  self.faces = self._load()
479
+ # Migración: Si self.faces es una lista, convertirla a dict
480
+ if isinstance(self.faces, list):
481
+ print(f"[FaceManager] Migrating list to dict...")
482
+ self.faces = {
483
+ "descriptors": self.faces,
484
+ "attendance_log": []
485
+ }
486
+ self.save()
487
 
488
  def _load(self):
489
  """Carga los rostros desde HF Dataset o archivo JSON local."""
490
  if self.use_hf:
491
  data = self._load_from_hf()
492
  if data: # Si hay datos (no vacío)
493
+ print(f"[FaceManager] Loaded data from HF Dataset")
494
  return data
495
 
496
  if data is not None:
 
499
  print("[FaceManager] Failed to load from HF, checking local...")
500
 
501
  data = self._load_from_local()
502
+ print(f"[FaceManager] Loaded data from local JSON")
503
  return data
504
 
505
  def save(self):
 
511
  if self.use_hf:
512
  success = self._save_to_hf(self.faces)
513
  if success:
514
+ print(f"[FaceManager] Saved to HF Dataset")
515
  else:
516
  print("[FaceManager] Failed to save to HF, data saved locally only")
517
 
518
+ def add_face(self, label, descriptor, student_id=None, course_id=None):
519
+ if not isinstance(self.faces, dict):
520
+ self.faces = {"descriptors": [], "attendance_log": []}
521
+
522
+ self.faces["descriptors"].append({
523
  "label": label,
524
+ "descriptor": descriptor,
525
+ "student_id": student_id,
526
+ "course_id": course_id,
527
+ "timestamp": datetime.datetime.now().isoformat()
528
  })
529
  self.save()
530
 
531
+ def log_attendance(self, student_id, student_name, course_id, status="PRESENT"):
532
+ if not isinstance(self.faces, dict):
533
+ self.faces = {"descriptors": [], "attendance_log": []}
534
+
535
+ now = datetime.datetime.now()
536
+ date_str = now.strftime("%Y-%m-%d")
537
+ time_str = now.strftime("%H:%M:%S")
538
+
539
+ record = {
540
+ "student_id": student_id,
541
+ "student_name": student_name,
542
+ "course_id": course_id,
543
+ "date": date_str,
544
+ "time": time_str,
545
+ "status": status
546
+ }
547
+
548
+ self.faces["attendance_log"].append(record)
549
+ self.save()
550
+
551
+ # Guardar en CSV local
552
+ try:
553
+ csv_dir = os.path.join(os.getcwd(), "data", "attendance")
554
+ os.makedirs(csv_dir, exist_ok=True)
555
+ filename = f"{student_id}_{student_name.replace(' ', '_')}.csv"
556
+ filepath = os.path.join(csv_dir, filename)
557
+
558
+ file_exists = os.path.isfile(filepath)
559
+ with open(filepath, mode='a', newline='', encoding='utf-8') as f:
560
+ writer = csv.DictWriter(f, fieldnames=["Fecha", "Hora", "Estado", "Nombre", "Curso ID"])
561
+ if not file_exists:
562
+ writer.writeheader()
563
+ writer.writerow({
564
+ "Fecha": date_str,
565
+ "Hora": time_str,
566
+ "Estado": status,
567
+ "Nombre": student_name,
568
+ "Curso ID": course_id
569
+ })
570
+ except Exception as e:
571
+ print(f"[CSV ERROR] Error writing attendance CSV: {e}")
572
+
573
  def get_all(self):
574
+ if isinstance(self.faces, dict):
575
+ return self.faces.get("descriptors", [])
576
  return self.faces
577
 
578
  face_mgr = FaceManager()
 
624
  rd_now = utc_now - datetime.timedelta(hours=4)
625
  ahora = rd_now.strftime("%H:%M")
626
 
627
+ loan['status_loan'] = "DELIVERED"
628
+ loan['delivered_at'] = rd_now.isoformat()
629
+ loan_mgr.save() # Persistir cambios
630
  socketio.emit('notification', {"text": f"{loan['Solicitante']} ha entregado", "color": "blue"})
631
  return True, loan, ahora
632
  return False, None, None
 
941
  "devolucion": hora_retorno,
942
  "item": full_items_string,
943
  "status_loan": "PENDING",
944
+ "requested_at": datetime.datetime.now().isoformat(),
945
+ "timestamp": datetime.datetime.now().isoformat() # Mantener por retrocompatibilidad
946
  }
947
 
948
  loan_mgr.add_loan(new_loan)
 
1088
  course_id = data.get('course_id')
1089
 
1090
  if label and descriptor:
1091
+ face_mgr.add_face(label, descriptor, student_id, course_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1092
  return jsonify({"status": "success"})
1093
 
1094
  # GET: Devolver rostros guardados
1095
+ return jsonify(face_mgr.get_all())
 
 
 
 
 
 
1096
 
1097
  @app.route('/api/attendance', methods=['POST'])
1098
  def api_attendance():
 
1102
  if label:
1103
  print(f"[ASISTENCIA] Registrando: {label}")
1104
  # Registrar en el sistema de aulas
1105
+ info = classroom_manager.record_attendance(label)
1106
+ if info:
1107
+ # Registrar log de rostros (HF y CSV)
1108
+ face_mgr.log_attendance(info['id'], info['name'], info['course_id'])
1109
  socketio.emit('notification', {'text': f'Bienvenido/a {label}', 'color': 'green'})
1110
  return jsonify({"status": "success", "message": f"Asistencia registrada para {label}"})
1111
  else:
 
 
1112
  socketio.emit('notification', {'text': f'Hola {label} (No inscrito)', 'color': 'blue'})
1113
  return jsonify({"status": "warning", "message": "Registrado pero no vinculado a curso"})
1114
 
app/templates/prestamos.html CHANGED
@@ -202,9 +202,11 @@
202
  ESTADO: ${config.label}
203
  </div>
204
  <p><strong>SOLICITANTE:</strong> ${loan.Solicitante}</p>
205
- <p><strong>FECHA:</strong> ${loan.fecha || 'No especificada'}</p>
 
206
  <p><strong>SALIDA:</strong> ${loan.hora}</p>
207
  <p><strong>RETORNO:</strong> ${loan.devolucion}</p>
 
208
  <hr style="opacity:0.1; margin:1rem 0;">
209
  <p><strong>HERRAMIENTAS:</strong></p>
210
  <p style="white-space:pre-wrap; font-size:0.9rem;">${loan.item}</p>
 
202
  ESTADO: ${config.label}
203
  </div>
204
  <p><strong>SOLICITANTE:</strong> ${loan.Solicitante}</p>
205
+ <p><strong>SOLICITADO EL:</strong> ${loan.requested_at ? new Date(loan.requested_at).toLocaleString() : 'No registrada'}</p>
206
+ <p><strong>FECHA PRÉSTAMO:</strong> ${loan.fecha || 'No especificada'}</p>
207
  <p><strong>SALIDA:</strong> ${loan.hora}</p>
208
  <p><strong>RETORNO:</strong> ${loan.devolucion}</p>
209
+ ${loan.delivered_at ? `<p><strong>ENTREGADO EL:</strong> ${new Date(loan.delivered_at).toLocaleString()}</p>` : ''}
210
  <hr style="opacity:0.1; margin:1rem 0;">
211
  <p><strong>HERRAMIENTAS:</strong></p>
212
  <p style="white-space:pre-wrap; font-size:0.9rem;">${loan.item}</p>