PrashanthB461 commited on
Commit
d7291a8
Β·
verified Β·
1 Parent(s): a582d31

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +374 -685
app.py CHANGED
@@ -19,140 +19,39 @@ import queue
19
  import requests
20
  from simple_salesforce import Salesforce
21
  from dotenv import load_dotenv
22
- import logging
23
  from retrying import retry
24
- from reportlab.lib.pagesizes import letter
25
- from reportlab.pdfgen import canvas
26
- from reportlab.lib.units import inch
27
- import tempfile
28
- import shutil
29
-
30
- # Load environment variables from .env file
31
- load_dotenv()
32
 
33
  # Setup logging
34
  logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
35
  logger = logging.getLogger(__name__)
36
 
37
- # Enhanced Configuration
38
- CONFIG = {
39
- "SF_CREDENTIALS": {
40
- "username": os.getenv("SF_USERNAME", "smartlabour@attendance.system"),
41
- "password": os.getenv("SF_PASSWORD", "#Prashanth@123"),
42
- "security_token": os.getenv("SF_SECURITY_TOKEN", "pasQDqmWApzD0skgbv76gVgIs"),
43
- "domain": "login"
44
- },
45
- "PUBLIC_URL_BASE": "https://your-domain.com/static/output/",
46
- "RETRY_ATTEMPTS": 3,
47
- "RETRY_DELAY": 2000,
48
- "RECOGNITION_COOLDOWN": 5, # seconds between recognitions for same person
49
- "FACE_RECOGNITION_THRESHOLD": 10, # threshold for face recognition
50
- "FACE_CONFIDENCE_THRESHOLD": 0.9 # minimum confidence for face detection
51
- }
52
 
53
  # Hugging Face API configuration
54
  HF_API_URL = "https://api-inference.huggingface.co/models/Salesforce/blip-image-captioning-base"
55
  HF_API_TOKEN = os.getenv("HUGGINGFACE_API_TOKEN")
56
 
57
- @retry(stop_max_attempt_number=CONFIG["RETRY_ATTEMPTS"], wait_fixed=CONFIG["RETRY_DELAY"])
 
 
 
 
 
 
 
 
58
  def connect_to_salesforce():
59
- """Enhanced Salesforce connection with retry mechanism"""
60
  try:
61
- sf = Salesforce(**CONFIG["SF_CREDENTIALS"])
62
- logger.info("Connected to Salesforce successfully")
63
- # Test connection
64
  sf.describe()
65
  return sf
66
  except Exception as e:
67
  logger.error(f"Salesforce connection failed: {e}")
68
  raise
69
 
70
- def generate_attendance_pdf(attendance_records, workers_info, output_dir):
71
- """Generate PDF report for attendance records"""
72
- try:
73
- pdf_filename = f"attendance_report_{int(time.time())}.pdf"
74
- pdf_path = os.path.join(output_dir, pdf_filename)
75
- pdf_file = BytesIO()
76
- c = canvas.Canvas(pdf_file, pagesize=letter)
77
-
78
- # Header
79
- c.setFont("Helvetica-Bold", 16)
80
- c.drawString(1 * inch, 10 * inch, "Attendance System Report")
81
-
82
- c.setFont("Helvetica", 12)
83
- c.drawString(1 * inch, 9.5 * inch, f"Date: {time.strftime('%Y-%m-%d')}")
84
- c.drawString(1 * inch, 9.2 * inch, f"Time: {time.strftime('%H:%M:%S')}")
85
-
86
- # Summary
87
- y_position = 8.7 * inch
88
- c.setFont("Helvetica-Bold", 14)
89
- c.drawString(1 * inch, y_position, f"Total Workers: {len(workers_info)}")
90
- y_position -= 0.3 * inch
91
- c.drawString(1 * inch, y_position, f"Total Attendance Records: {len(attendance_records)}")
92
- y_position -= 0.5 * inch
93
-
94
- c.setFont("Helvetica-Bold", 12)
95
- c.drawString(1 * inch, y_position, "Attendance Records:")
96
- y_position -= 0.3 * inch
97
-
98
- c.setFont("Helvetica", 10)
99
- for record in attendance_records:
100
- if isinstance(record, dict):
101
- record_text = f"β€’ {record.get('name', 'Unknown')} ({record.get('worker_id', 'N/A')}) - {record.get('date', 'N/A')} {record.get('time', 'N/A')}"
102
- else:
103
- # Handle Salesforce record format
104
- record_text = f"β€’ {getattr(record, 'Name__c', 'Unknown')} ({getattr(record, 'Worker_ID__c', 'N/A')}) - {getattr(record, 'Date__c', 'N/A')} {getattr(record, 'Time__c', 'N/A')}"
105
-
106
- c.drawString(1 * inch, y_position, record_text)
107
- y_position -= 0.2 * inch
108
-
109
- if y_position < 1 * inch:
110
- c.showPage()
111
- c.setFont("Helvetica", 10)
112
- y_position = 10 * inch
113
-
114
- c.save()
115
- pdf_file.seek(0)
116
-
117
- # Save to file
118
- with open(pdf_path, "wb") as f:
119
- f.write(pdf_file.getvalue())
120
-
121
- public_url = f"{CONFIG['PUBLIC_URL_BASE']}{pdf_filename}"
122
- logger.info(f"PDF generated: {public_url}")
123
- return pdf_path, public_url, pdf_file
124
- except Exception as e:
125
- logger.error(f"Error generating PDF: {e}")
126
- return "", "", None
127
-
128
- def upload_pdf_to_salesforce(sf, pdf_file, report_id):
129
- """Upload PDF file to Salesforce"""
130
- try:
131
- if not pdf_file:
132
- logger.error("No PDF file provided for upload")
133
- return ""
134
-
135
- encoded_pdf = base64.b64encode(pdf_file.getvalue()).decode('utf-8')
136
- content_version_data = {
137
- "Title": f"Attendance_Report_{int(time.time())}",
138
- "PathOnClient": f"attendance_report_{int(time.time())}.pdf",
139
- "VersionData": encoded_pdf,
140
- "FirstPublishLocationId": report_id
141
- }
142
- content_version = sf.ContentVersion.create(content_version_data)
143
- result = sf.query(f"SELECT Id, ContentDocumentId FROM ContentVersion WHERE Id = '{content_version['id']}'")
144
-
145
- if not result['records']:
146
- logger.error("Failed to retrieve ContentVersion")
147
- return ""
148
-
149
- file_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/version/download/{content_version['id']}"
150
- logger.info(f"PDF uploaded to Salesforce: {file_url}")
151
- return file_url
152
- except Exception as e:
153
- logger.error(f"Error uploading PDF to Salesforce: {e}")
154
- return ""
155
-
156
  class AttendanceSystem:
157
  def __init__(self):
158
  self.known_face_embeddings = []
@@ -165,27 +64,25 @@ class AttendanceSystem:
165
  self.frame_queue = queue.Queue(maxsize=2)
166
  self.recognition_thread = None
167
  self.last_recognition_time = {}
168
- self.recognition_cooldown = CONFIG["RECOGNITION_COOLDOWN"]
169
  self.video_file_path = None
170
  self.video_processing = False
171
-
172
- # Enhanced Salesforce connection with retry
173
  try:
174
  self.sf = connect_to_salesforce()
175
- logger.info("Salesforce connection established successfully")
176
  except Exception as e:
177
- logger.error(f"Failed to establish Salesforce connection: {e}")
178
  self.sf = None
179
-
180
  # Create directories for data storage
181
  os.makedirs("data", exist_ok=True)
182
  os.makedirs("data/faces", exist_ok=True)
183
- os.makedirs("data/reports", exist_ok=True)
184
-
185
- self.load_data()
186
 
 
 
187
  def load_data(self):
188
- """Load all stored data with enhanced error handling"""
189
  try:
190
  # Load face embeddings and worker data
191
  if os.path.exists("data/workers.pkl"):
@@ -195,16 +92,12 @@ class AttendanceSystem:
195
  self.known_face_names = data.get("names", [])
196
  self.known_face_ids = data.get("ids", [])
197
  self.next_worker_id = data.get("next_id", 1)
198
-
199
  # Load attendance records
200
  if os.path.exists("data/attendance.json"):
201
  with open("data/attendance.json", "r") as f:
202
  self.attendance_records = json.load(f)
203
-
204
- # Sync with Salesforce if connected
205
- if self.sf:
206
- self.sync_with_salesforce()
207
-
208
  except Exception as e:
209
  logger.error(f"Error loading data: {e}")
210
  self.known_face_embeddings = []
@@ -212,33 +105,9 @@ class AttendanceSystem:
212
  self.known_face_ids = []
213
  self.attendance_records = []
214
  self.next_worker_id = 1
215
-
216
- def sync_with_salesforce(self):
217
- """Sync local data with Salesforce"""
218
- try:
219
- if not self.sf:
220
- return
221
-
222
- # Sync workers from Salesforce
223
- workers = self.sf.query_all("SELECT Name, Worker_ID__c, Face_Embedding__c FROM Worker__c")['records']
224
- for worker in workers:
225
- worker_id = worker['Worker_ID__c']
226
- if worker_id not in self.known_face_ids:
227
- self.known_face_ids.append(worker_id)
228
- self.known_face_names.append(worker['Name'])
229
- if worker['Face_Embedding__c']:
230
- embedding = json.loads(worker['Face_Embedding__c'])
231
- self.known_face_embeddings.append(embedding)
232
- else:
233
- self.known_face_embeddings.append([])
234
-
235
- logger.info(f"Synced {len(workers)} workers from Salesforce")
236
-
237
- except Exception as e:
238
- logger.error(f"Error syncing with Salesforce: {e}")
239
-
240
  def save_data(self):
241
- """Save all data to files with enhanced error handling"""
242
  try:
243
  # Save worker data
244
  worker_data = {
@@ -249,205 +118,162 @@ class AttendanceSystem:
249
  }
250
  with open("data/workers.pkl", "wb") as f:
251
  pickle.dump(worker_data, f)
252
-
253
  # Save attendance records
254
  with open("data/attendance.json", "w") as f:
255
  json.dump(self.attendance_records, f, indent=2)
256
-
257
  except Exception as e:
258
  logger.error(f"Error saving data: {e}")
259
-
260
  def get_image_caption(self, image):
261
- """Generate image caption using Hugging Face API with enhanced error handling"""
262
  try:
263
  # Convert PIL image to bytes
264
  if isinstance(image, Image.Image):
265
  img_byte_arr = BytesIO()
266
  image.save(img_byte_arr, format='JPEG')
267
  img_data = img_byte_arr.getvalue()
268
-
269
  # Make API request to Hugging Face
270
  headers = {"Authorization": f"Bearer {HF_API_TOKEN}"}
271
- response = requests.post(HF_API_URL, headers=headers, data=img_data, timeout=30)
272
-
273
  if response.status_code == 200:
274
  result = response.json()
275
  if isinstance(result, list) and len(result) > 0:
276
- return result[0].get("generated_text", "Professional worker photo")
277
- return "Professional worker photo"
278
  else:
279
- logger.warning(f"Hugging Face API error: {response.status_code} - {response.text}")
280
- return "Professional worker photo"
281
  except Exception as e:
282
  logger.error(f"Error in Hugging Face API call: {e}")
283
- return "Professional worker photo"
284
-
285
- def save_worker_to_salesforce(self, worker_data):
286
- """Save worker data to Salesforce with enhanced error handling"""
287
- if not self.sf:
288
- logger.warning("Salesforce not connected, skipping save")
289
- return False
290
-
291
- try:
292
- result = self.sf.Worker__c.create({
293
- 'Name': worker_data['name'],
294
- 'Worker_ID__c': worker_data['worker_id'],
295
- 'Face_Embedding__c': json.dumps(worker_data['embedding']),
296
- 'Image_Caption__c': worker_data.get('caption', ''),
297
- 'Registration_Date__c': worker_data.get('registration_date', date.today().isoformat()),
298
- 'Status__c': 'Active'
299
- })
300
- logger.info(f"Worker saved to Salesforce: {result['id']}")
301
- return True
302
- except Exception as e:
303
- logger.error(f"Error saving worker to Salesforce: {e}")
304
- return False
305
-
306
- def save_attendance_to_salesforce(self, attendance_data):
307
- """Save attendance data to Salesforce with enhanced error handling"""
308
- if not self.sf:
309
- logger.warning("Salesforce not connected, skipping save")
310
- return False
311
-
312
- try:
313
- result = self.sf.Attendance__c.create({
314
- 'Worker_ID__c': attendance_data['worker_id'],
315
- 'Name__c': attendance_data['worker_name'],
316
- 'Date__c': attendance_data['date'],
317
- 'Time__c': attendance_data['time'],
318
- 'Timestamp__c': attendance_data['timestamp'],
319
- 'Status__c': attendance_data['status'],
320
- 'Method__c': attendance_data['method'],
321
- 'Confidence__c': attendance_data.get('confidence', 0.0)
322
- })
323
- logger.info(f"Attendance saved to Salesforce: {result['id']}")
324
- return True
325
- except Exception as e:
326
- logger.error(f"Error saving attendance to Salesforce: {e}")
327
- return False
328
-
329
  def register_worker_manual(self, image, name):
330
- """Manual worker registration with enhanced Salesforce integration"""
331
  if image is None or not name.strip():
332
  return "❌ Please provide both image and name!", self.get_registered_workers_info()
333
-
334
  # Convert PIL image to RGB array
335
  if isinstance(image, Image.Image):
336
  image_array = np.array(image)
337
-
338
  try:
339
  # Verify the image contains a face
340
  face_analysis = DeepFace.analyze(img_path=image_array, actions=['emotion'], enforce_detection=True, detector_backend='opencv')
341
-
342
  # Get face embedding
343
  embedding = DeepFace.represent(img_path=image_array, model_name='Facenet')[0]['embedding']
344
-
345
  # Check if person already exists
346
  name = name.strip().title()
347
  if name in self.known_face_names:
348
  return f"❌ {name} is already registered!", self.get_registered_workers_info()
349
-
350
  # Generate new worker ID
351
  worker_id = f"W{self.next_worker_id:04d}"
352
-
353
  # Generate image caption using Hugging Face
354
  caption = self.get_image_caption(image)
355
-
356
  # Add the face embedding, name, and ID
357
  self.known_face_embeddings.append(embedding)
358
  self.known_face_names.append(name)
359
  self.known_face_ids.append(worker_id)
360
  self.next_worker_id += 1
361
-
362
- # Save face image
363
  face_image = Image.fromarray(image_array)
364
- face_image.save(f"data/faces/{worker_id}_{name.replace(' ', '_')}.jpg")
365
-
366
- # Prepare worker data for Salesforce
367
- worker_data = {
368
- 'name': name,
369
- 'worker_id': worker_id,
370
- 'embedding': embedding,
371
- 'caption': caption,
372
- 'registration_date': date.today().isoformat()
373
- }
374
-
375
  # Save to Salesforce
376
- salesforce_success = self.save_worker_to_salesforce(worker_data)
377
-
 
 
 
 
 
 
 
 
 
 
 
 
378
  self.save_data()
379
-
380
- status_message = f"βœ… {name} has been successfully registered with ID: {worker_id}! Caption: {caption}"
381
- if salesforce_success:
382
- status_message += " (Synced with Salesforce)"
383
- else:
384
- status_message += " (Local only - Salesforce sync failed)"
385
-
386
- return status_message, self.get_registered_workers_info()
387
 
 
 
388
  except ValueError as e:
389
  if "Face could not be detected" in str(e):
390
  return "❌ No face detected in the image! Please try again with a clear face image.", self.get_registered_workers_info()
391
  return f"❌ Error processing image: {str(e)}", self.get_registered_workers_info()
392
  except Exception as e:
393
- logger.error(f"Error during registration: {e}")
394
  return f"❌ Error during registration: {str(e)}", self.get_registered_workers_info()
395
-
396
  def register_worker_auto(self, face_image):
397
- """Automatic worker registration for unrecognized faces with enhanced Salesforce integration"""
398
  try:
399
  # Generate new worker ID and name
400
  worker_id = f"W{self.next_worker_id:04d}"
401
  worker_name = f"Unknown_Worker_{self.next_worker_id}"
402
-
403
  # Get face embedding
404
  embedding = DeepFace.represent(img_path=face_image, model_name='Facenet')[0]['embedding']
405
-
406
  # Generate image caption
407
  face_pil = Image.fromarray(cv2.cvtColor(face_image, cv2.COLOR_BGR2RGB))
408
  caption = self.get_image_caption(face_pil)
409
-
410
  # Add to database
411
  self.known_face_embeddings.append(embedding)
412
  self.known_face_names.append(worker_name)
413
  self.known_face_ids.append(worker_id)
414
  self.next_worker_id += 1
415
-
416
- # Save face image
417
- face_pil.save(f"data/faces/{worker_id}_{worker_name}.jpg")
418
-
419
- # Prepare worker data for Salesforce
420
- worker_data = {
421
- 'name': worker_name,
422
- 'worker_id': worker_id,
423
- 'embedding': embedding,
424
- 'caption': caption,
425
- 'registration_date': date.today().isoformat()
426
- }
427
-
428
  # Save to Salesforce
429
- self.save_worker_to_salesforce(worker_data)
430
-
 
 
 
 
 
 
 
 
 
 
 
 
431
  self.save_data()
432
-
433
  return worker_id, worker_name
434
-
435
  except Exception as e:
436
  logger.error(f"Error in auto registration: {e}")
437
  return None, None
438
-
439
- def mark_attendance(self, worker_id, worker_name, confidence=0.0):
440
- """Mark attendance for a worker with enhanced Salesforce integration"""
441
  try:
442
  today = date.today().isoformat()
443
  current_time = datetime.now()
444
-
445
  # Check if already marked today
446
  already_marked = any(
447
  record["worker_id"] == worker_id and record["date"] == today
448
  for record in self.attendance_records
449
  )
450
-
451
  if not already_marked:
452
  # Create attendance record
453
  attendance_record = {
@@ -457,66 +283,60 @@ class AttendanceSystem:
457
  "time": current_time.strftime("%H:%M:%S"),
458
  "timestamp": current_time.isoformat(),
459
  "status": "Present",
460
- "method": "Auto",
461
- "confidence": confidence
462
  }
463
  self.attendance_records.append(attendance_record)
464
-
465
- # Prepare attendance data for Salesforce
466
- attendance_data = {
467
- 'worker_id': worker_id,
468
- 'worker_name': worker_name,
469
- 'date': today,
470
- 'time': current_time.strftime("%H:%M:%S"),
471
- 'timestamp': current_time.isoformat(),
472
- 'status': "Present",
473
- 'method': "Auto",
474
- 'confidence': confidence
475
- }
476
-
477
  # Save to Salesforce
478
- self.save_attendance_to_salesforce(attendance_data)
479
-
 
 
 
 
 
 
 
 
 
 
 
 
 
480
  self.save_data()
481
  return True
482
  return False
483
-
484
  except Exception as e:
485
  logger.error(f"Error marking attendance: {e}")
486
  return False
487
-
488
  def process_video_frame(self, frame):
489
- """Process a single video frame for face recognition with enhanced accuracy"""
490
  try:
491
  rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
492
-
493
  # Find faces in the frame
494
- face_objs = DeepFace.extract_faces(
495
- img_path=rgb_frame,
496
- target_size=(160, 160),
497
- enforce_detection=False,
498
- detector_backend='opencv'
499
- )
500
-
501
  current_time = time.time()
502
-
503
  for face_obj in face_objs:
504
- if face_obj['confidence'] > CONFIG["FACE_CONFIDENCE_THRESHOLD"]:
505
  face_area = face_obj['facial_area']
506
  x, y, w, h = face_area['x'], face_area['y'], face_area['w'], face_area['h']
507
-
508
  # Extract face image
509
  face_image = frame[y:y+h, x:x+w]
510
-
511
  try:
512
  # Get face embedding
513
  embedding = DeepFace.represent(img_path=face_image, model_name='Facenet')[0]['embedding']
514
-
515
  worker_id = None
516
  worker_name = "Unknown"
517
  color = (0, 0, 255) # Red for unknown
518
- confidence = 0.0
519
-
520
  # Compare with known faces
521
  if len(self.known_face_embeddings) > 0:
522
  # Calculate distances to known faces
@@ -524,24 +344,23 @@ class AttendanceSystem:
524
  for known_embedding in self.known_face_embeddings:
525
  distance = np.linalg.norm(np.array(embedding) - np.array(known_embedding))
526
  distances.append(distance)
527
-
528
  min_distance = min(distances)
529
  best_match_index = distances.index(min_distance)
530
- confidence = max(0, 100 - min_distance * 10) # Convert distance to confidence percentage
531
-
532
- if min_distance < CONFIG["FACE_RECOGNITION_THRESHOLD"]:
533
  worker_id = self.known_face_ids[best_match_index]
534
  worker_name = self.known_face_names[best_match_index]
535
  color = (0, 255, 0) # Green for known
536
-
537
  # Check cooldown period
538
  if worker_id not in self.last_recognition_time or \
539
  current_time - self.last_recognition_time[worker_id] > self.recognition_cooldown:
540
-
541
  # Mark attendance
542
- if self.mark_attendance(worker_id, worker_name, confidence):
543
- logger.info(f"βœ… Attendance marked for {worker_name} ({worker_id}) with confidence {confidence:.1f}%")
544
-
545
  self.last_recognition_time[worker_id] = current_time
546
  else:
547
  # Unknown face - auto register
@@ -551,150 +370,145 @@ class AttendanceSystem:
551
  worker_id = new_id
552
  worker_name = new_name
553
  color = (255, 165, 0) # Orange for newly registered
554
- logger.info(f"πŸ†• New worker registered: {new_name} ({new_id})")
555
-
556
  # Mark attendance for new worker
557
- self.mark_attendance(worker_id, worker_name, confidence)
558
-
559
  # Draw rectangle and label
560
  cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2)
561
  cv2.rectangle(frame, (x, y+h - 35), (x+w, y+h), color, cv2.FILLED)
562
-
563
  label = f"{worker_name}"
564
  if worker_id:
565
  label += f" ({worker_id})"
566
- if confidence > 0:
567
- label += f" {confidence:.1f}%"
568
-
569
  cv2.putText(frame, label, (x + 6, y+h - 6),
570
  cv2.FONT_HERSHEY_DUPLEX, 0.6, (255, 255, 255), 1)
571
-
572
  except Exception as e:
573
  logger.error(f"Error processing face: {e}")
574
  continue
575
-
576
  return frame
577
-
578
  except Exception as e:
579
  logger.error(f"Error processing frame: {e}")
580
  return frame
581
-
582
  def start_video_stream(self, camera_source=0):
583
- """Start video streaming and recognition with enhanced error handling"""
584
  try:
585
  if self.is_streaming:
586
  return "⚠️ Video stream is already running!"
587
-
588
  # Clear previous video file if switching from file to camera
589
  self.video_file_path = None
590
-
591
  self.video_capture = cv2.VideoCapture(camera_source)
592
  if not self.video_capture.isOpened():
593
  return "❌ Could not open camera/video source!"
594
-
595
  self.is_streaming = True
596
-
597
  def video_loop():
598
  while self.is_streaming:
599
  ret, frame = self.video_capture.read()
600
  if not ret:
601
  break
602
-
603
  # Process frame for face recognition
604
  processed_frame = self.process_video_frame(frame)
605
-
606
  # Add to queue for display
607
  if not self.frame_queue.full():
608
  try:
609
  self.frame_queue.put_nowait(processed_frame)
610
  except queue.Full:
611
  pass
612
-
613
  time.sleep(0.1) # Limit processing rate
614
-
615
  self.recognition_thread = threading.Thread(target=video_loop)
616
  self.recognition_thread.daemon = True
617
  self.recognition_thread.start()
618
-
619
  return "βœ… Live camera stream started successfully!"
620
-
621
  except Exception as e:
622
- logger.error(f"Error starting video stream: {e}")
623
  return f"❌ Error starting video stream: {e}"
624
-
625
  def process_uploaded_video(self, video_path):
626
- """Process an uploaded video file for face recognition with enhanced error handling"""
627
  try:
628
  if self.is_streaming:
629
  return "⚠️ Please stop current stream before processing a video file!"
630
-
631
  if not os.path.exists(video_path):
632
  return "❌ Video file not found!"
633
-
634
  self.video_file_path = video_path
635
  self.video_processing = True
636
-
637
  def video_processing_loop():
638
  cap = cv2.VideoCapture(video_path)
639
  fps = cap.get(cv2.CAP_PROP_FPS)
640
  frame_delay = 1.0 / fps if fps > 0 else 0.03
641
-
642
  while self.video_processing and cap.isOpened():
643
  ret, frame = cap.read()
644
  if not ret:
645
  break
646
-
647
  # Process frame for face recognition
648
  processed_frame = self.process_video_frame(frame)
649
-
650
  # Add to queue for display
651
  if not self.frame_queue.full():
652
  try:
653
  self.frame_queue.put_nowait(processed_frame)
654
  except queue.Full:
655
  pass
656
-
657
- time.sleep(frame_delay)
658
 
 
 
659
  cap.release()
660
  self.video_processing = False
661
-
662
  self.recognition_thread = threading.Thread(target=video_processing_loop)
663
  self.recognition_thread.daemon = True
664
  self.recognition_thread.start()
665
-
666
  return f"βœ… Video processing started successfully! ({os.path.basename(video_path)})"
667
-
668
  except Exception as e:
669
- logger.error(f"Error processing video: {e}")
670
  return f"❌ Error processing video: {e}"
671
-
672
  def stop_video_stream(self):
673
- """Stop video streaming or processing with enhanced cleanup"""
674
  try:
675
  self.is_streaming = False
676
  self.video_processing = False
677
-
678
  if self.video_capture:
679
  self.video_capture.release()
680
  self.video_capture = None
681
-
682
  if self.recognition_thread:
683
  self.recognition_thread.join(timeout=2)
684
-
685
  # Clear frame queue
686
  while not self.frame_queue.empty():
687
  try:
688
  self.frame_queue.get_nowait()
689
  except queue.Empty:
690
  break
691
-
692
  return "βœ… Video stream/processing stopped successfully!"
693
-
694
  except Exception as e:
695
- logger.error(f"Error stopping video: {e}")
696
  return f"❌ Error stopping video: {e}"
697
-
698
  def get_current_frame(self):
699
  """Get current frame for display"""
700
  try:
@@ -704,260 +518,186 @@ class AttendanceSystem:
704
  return None
705
  except queue.Empty:
706
  return None
707
-
708
  def get_registered_workers_info(self):
709
- """Get information about registered workers with enhanced Salesforce integration"""
710
- if self.sf:
711
- try:
712
- workers = self.sf.query_all("SELECT Name, Worker_ID__c, Image_Caption__c, Registration_Date__c FROM Worker__c ORDER BY Registration_Date__c DESC")['records']
713
- if not workers:
714
- return "No workers registered yet."
715
-
716
- info = f"**Registered Workers ({len(workers)}) - Synced with Salesforce:**\n\n"
717
- for i, worker in enumerate(workers, 1):
718
- reg_date = worker.get('Registration_Date__c', 'N/A')
719
- caption = worker.get('Image_Caption__c', 'N/A')
720
- info += f"{i}. **{worker['Name']}** (ID: {worker['Worker_ID__c']}) - Registered: {reg_date}\n"
721
- info += f" Caption: {caption}\n\n"
722
- return info
723
- except Exception as e:
724
- logger.error(f"Error fetching workers from Salesforce: {e}")
725
- return self._get_local_workers_info()
726
- else:
727
- return self._get_local_workers_info()
728
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
729
  def _get_local_workers_info(self):
730
  """Fallback to local worker info if Salesforce query fails"""
731
  if not self.known_face_names:
732
  return "No workers registered yet."
733
-
734
- info = f"**Registered Workers ({len(self.known_face_names)}) - Local Database:**\n\n"
735
  for i, (worker_id, name) in enumerate(zip(self.known_face_ids, self.known_face_names), 1):
736
  info += f"{i}. **{name}** (ID: {worker_id})\n"
737
  return info
738
-
739
  def get_today_attendance(self):
740
- """Get today's attendance records with enhanced Salesforce integration"""
 
 
 
741
  today = date.today().isoformat()
 
 
 
 
742
 
743
- if self.sf:
744
- try:
745
- records = self.sf.query_all(
746
- f"SELECT Name__c, Worker_ID__c, Time__c, Method__c, Confidence__c FROM Attendance__c WHERE Date__c = '{today}' ORDER BY Time__c DESC"
747
- )['records']
748
-
749
- if not records:
750
- return f"**Today's Attendance ({today}) - Synced with Salesforce:**\n\nNo attendance marked yet."
751
-
752
- info = f"**Today's Attendance ({today}) - Synced with Salesforce:**\n\n"
753
- for record in records:
754
- method_icon = "πŸ€–" if record['Method__c'] == "Auto" else "πŸ‘€"
755
- confidence = record.get('Confidence__c', 0)
756
- confidence_text = f" ({confidence:.1f}%)" if confidence > 0 else ""
757
- info += f"{method_icon} **{record['Name__c']}** (ID: {record['Worker_ID__c']}) - {record['Time__c']}{confidence_text}\n"
758
- return info
759
- except Exception as e:
760
- logger.error(f"Error fetching attendance from Salesforce: {e}")
761
- return self._get_local_today_attendance()
762
- else:
763
  return self._get_local_today_attendance()
764
-
765
  def _get_local_today_attendance(self):
766
  """Fallback to local attendance records if Salesforce query fails"""
767
  today = date.today().isoformat()
768
  today_records = [r for r in self.attendance_records if r["date"] == today]
769
-
770
  if not today_records:
771
- return f"**Today's Attendance ({today}) - Local Database:**\n\nNo attendance marked yet."
772
-
773
- info = f"**Today's Attendance ({today}) - Local Database:**\n\n"
774
  for record in today_records:
775
  method_icon = "πŸ€–" if record.get("method") == "Auto" else "πŸ‘€"
776
- confidence = record.get("confidence", 0)
777
- confidence_text = f" ({confidence:.1f}%)" if confidence > 0 else ""
778
- info += f"{method_icon} **{record['name']}** (ID: {record['worker_id']}) - {record['time']}{confidence_text}\n"
779
  return info
780
-
781
  def get_attendance_report(self, start_date, end_date):
782
- """Generate comprehensive attendance report with enhanced Salesforce integration and PDF generation"""
783
  if not start_date or not end_date:
784
  return "Please select both start and end dates."
785
-
786
  try:
787
  # Validate date format
788
  datetime.strptime(start_date, '%Y-%m-%d')
789
  datetime.strptime(end_date, '%Y-%m-%d')
790
  except ValueError:
791
  return "Invalid date format. Please use YYYY-MM-DD."
792
-
 
 
 
793
  try:
794
- if self.sf:
795
- # Query Salesforce for attendance records
796
- records = self.sf.query_all(
797
- f"SELECT Worker_ID__c, Name__c, Date__c, Time__c, Method__c, Confidence__c FROM Attendance__c "
798
- f"WHERE Date__c >= '{start_date}' AND Date__c <= '{end_date}' ORDER BY Date__c DESC, Time__c DESC"
799
- )['records']
800
-
801
- # Get worker info
802
- workers = self.sf.query_all("SELECT Name, Worker_ID__c FROM Worker__c")['records']
803
-
804
- data_source = "Salesforce"
805
- else:
806
- # Use local data
807
- records = [r for r in self.attendance_records if start_date <= r["date"] <= end_date]
808
- workers = [{'Name': name, 'Worker_ID__c': wid} for name, wid in zip(self.known_face_names, self.known_face_ids)]
809
- data_source = "Local Database"
810
-
811
  if not records:
812
  return f"No attendance records found between {start_date} and {end_date}."
813
-
814
- # Generate PDF report
815
- output_dir = "data/reports"
816
- pdf_path, pdf_url = self.generate_comprehensive_report(start_date, end_date, records, workers)
817
-
818
  # Create DataFrame for analysis
819
- if self.sf:
820
- df_data = []
821
- for record in records:
822
- df_data.append({
823
- 'Worker_ID': record['Worker_ID__c'],
824
- 'Name': record['Name__c'],
825
- 'Date': record['Date__c'],
826
- 'Time': record['Time__c'],
827
- 'Method': record['Method__c'],
828
- 'Confidence': record.get('Confidence__c', 0)
829
- })
830
- df = pd.DataFrame(df_data)
831
- else:
832
- df = pd.DataFrame(records)
833
- df = df.rename(columns={'worker_id': 'Worker_ID', 'name': 'Name', 'date': 'Date', 'time': 'Time', 'method': 'Method', 'confidence': 'Confidence'})
834
-
835
  # Summary statistics
836
  total_days = (pd.to_datetime(end_date) - pd.to_datetime(start_date)).days + 1
837
- unique_workers = df['Worker_ID'].nunique()
838
  total_attendances = len(df)
839
- auto_registrations = len(df[df['Method'] == 'Auto'])
840
- avg_confidence = df['Confidence'].mean() if 'Confidence' in df.columns else 0
841
-
842
- report = f"**πŸ“Š Comprehensive Attendance Report ({start_date} to {end_date})**\n"
843
- report += f"**Data Source:** {data_source}\n\n"
844
  report += f"**Summary:**\n"
845
  report += f"β€’ Total Days: {total_days}\n"
846
  report += f"β€’ Unique Workers: {unique_workers}\n"
847
  report += f"β€’ Total Attendances: {total_attendances}\n"
848
- report += f"β€’ Auto Detections: {auto_registrations}\n"
849
- report += f"β€’ Average Confidence: {avg_confidence:.1f}%\n\n"
850
-
851
  # Individual attendance counts
852
  if not df.empty:
853
- attendance_counts = df.groupby(['Worker_ID', 'Name']).size().reset_index(name='count')
854
  report += f"**πŸ‘₯ Individual Attendance:**\n"
855
  for _, row in attendance_counts.iterrows():
856
  percentage = (row['count'] / total_days) * 100
857
- report += f"β€’ **{row['Name']}** ({row['Worker_ID']}): {row['count']} days ({percentage:.1f}%)\n"
858
-
859
- if pdf_path:
860
- report += f"\n**πŸ“„ PDF Report Generated:** {os.path.basename(pdf_path)}\n"
861
-
862
  return report
863
  except Exception as e:
864
- logger.error(f"Error generating report: {e}")
865
- return f"❌ Error generating report: {e}"
 
 
 
 
 
 
 
 
866
 
867
- def generate_comprehensive_report(self, start_date, end_date, records, workers):
868
- """Generate comprehensive PDF report with enhanced features"""
869
- try:
870
- output_dir = "data/reports"
871
- pdf_path, pdf_url, pdf_file = generate_attendance_pdf(records, workers, output_dir)
872
-
873
- # Upload to Salesforce if connected
874
- if self.sf and pdf_file:
875
- try:
876
- # Create report record in Salesforce
877
- report_record = self.sf.Attendance_Report__c.create({
878
- 'Start_Date__c': start_date,
879
- 'End_Date__c': end_date,
880
- 'Total_Workers__c': len(workers),
881
- 'Total_Attendance__c': len(records),
882
- 'PDF_URL__c': pdf_url,
883
- 'Generated_Date__c': date.today().isoformat()
884
- })
885
- logger.info(f"Report record created in Salesforce: {report_record['id']}")
886
-
887
- # Upload PDF to Salesforce
888
- uploaded_url = upload_pdf_to_salesforce(self.sf, pdf_file, report_record['id'])
889
- if uploaded_url:
890
- # Update record with uploaded PDF URL
891
- self.sf.Attendance_Report__c.update(report_record['id'], {'PDF_URL__c': uploaded_url})
892
- pdf_url = uploaded_url
893
-
894
- except Exception as e:
895
- logger.error(f"Error creating report record in Salesforce: {e}")
896
-
897
- return pdf_path, pdf_url
898
- except Exception as e:
899
- logger.error(f"Error generating comprehensive report: {e}")
900
- return "", ""
901
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
902
  def export_attendance_csv(self):
903
- """Export attendance records to CSV with enhanced features"""
904
  try:
905
- if self.sf:
906
- # Export from Salesforce
907
- records = self.sf.query_all("SELECT Worker_ID__c, Name__c, Date__c, Time__c, Method__c, Confidence__c FROM Attendance__c ORDER BY Date__c DESC, Time__c DESC")['records']
908
- if not records:
909
- return None, "No attendance records to export from Salesforce."
910
-
911
- # Convert to DataFrame
912
- df_data = []
913
- for record in records:
914
- df_data.append({
915
- 'Worker_ID': record['Worker_ID__c'],
916
- 'Name': record['Name__c'],
917
- 'Date': record['Date__c'],
918
- 'Time': record['Time__c'],
919
- 'Method': record['Method__c'],
920
- 'Confidence': record.get('Confidence__c', 0)
921
- })
922
- df = pd.DataFrame(df_data)
923
- source = "Salesforce"
924
- else:
925
- # Export from local data
926
- if not self.attendance_records:
927
- return None, "No attendance records to export from local database."
928
-
929
- df = pd.DataFrame(self.attendance_records)
930
- source = "Local"
931
-
932
  timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
933
- csv_file = f"data/reports/attendance_report_{source}_{timestamp}.csv"
934
  df.to_csv(csv_file, index=False)
935
-
936
- return csv_file, f"βœ… Attendance exported to {os.path.basename(csv_file)} (Source: {source})"
937
-
938
  except Exception as e:
939
- logger.error(f"Error exporting data: {e}")
940
  return None, f"❌ Error exporting data: {e}"
941
-
942
- def get_salesforce_status(self):
943
- """Get current Salesforce connection status"""
944
- if self.sf:
945
- try:
946
- # Test connection
947
- self.sf.describe()
948
- return "βœ… Connected to Salesforce - Data syncing enabled"
949
- except Exception as e:
950
- logger.error(f"Salesforce connection test failed: {e}")
951
- return "❌ Salesforce connection lost - Using local data only"
952
- else:
953
- return "❌ Not connected to Salesforce - Using local data only"
954
 
955
  # Initialize the attendance system
956
  attendance_system = AttendanceSystem()
957
 
958
  def create_interface():
959
  with gr.Blocks(
960
- title="🎯 Enhanced Attendance System with Improved Salesforce Integration",
961
  theme=gr.themes.Soft(),
962
  css="""
963
  .gradio-container {
@@ -974,47 +714,31 @@ def create_interface():
974
  .video-option-tabs {
975
  margin-bottom: 15px;
976
  }
977
- .salesforce-status {
978
- background: linear-gradient(90deg, #f0f9ff, #e0f2fe);
979
- border: 1px solid #0ea5e9;
980
- border-radius: 8px;
981
- padding: 10px;
982
- margin: 10px 0;
983
- }
984
  """
985
  ) as demo:
986
-
987
  gr.Markdown(
988
  """
989
- # 🎯 Enhanced Attendance System with Improved Salesforce Integration
990
-
991
- **Comprehensive facial recognition system with live camera and video file processing, integrated with Hugging Face AI and Salesforce cloud storage**
992
-
993
- ## πŸš€ **Enhanced Features:**
994
- - **πŸŽ₯ Live Camera Recognition** - Real-time face detection with confidence scoring
995
- - **πŸ“Ή Video File Processing** - Process pre-recorded videos for batch attendance
996
- - **πŸ€– Automatic Worker Registration** - Auto-register unknown faces with AI-generated captions
997
- - **πŸ‘€ Manual Registration** - Register workers manually with Hugging Face image captioning
998
- - **πŸ“… Smart Attendance Rules** - One attendance mark per worker per day with cooldown periods
999
- - **πŸ“Š Advanced Analytics** - Detailed reports with PDF generation and CSV export
1000
- - **πŸ€— Hugging Face Integration** - AI-powered image captioning for worker profiles
1001
- - **☁️ Enhanced Salesforce Integration** - Robust data sync with retry mechanisms and error handling
1002
- - **πŸ“„ PDF Report Generation** - Comprehensive reports with automatic Salesforce upload
1003
- - **πŸ”„ Real-time Sync** - Automatic data synchronization between local and cloud storage
1004
  """
1005
  )
1006
-
1007
- # Salesforce Status Display
1008
- salesforce_status = gr.Markdown(
1009
- value=attendance_system.get_salesforce_status(),
1010
- elem_classes="salesforce-status"
1011
- )
1012
-
1013
  with gr.Tabs():
1014
  # Video Recognition Tab
1015
  with gr.Tab("πŸŽ₯ Video Recognition", elem_classes="tab-nav"):
1016
- gr.Markdown("### Enhanced Face Recognition from Live Camera or Video File")
1017
-
1018
  with gr.Row():
1019
  with gr.Column(scale=1):
1020
  with gr.Tabs(selected="live", elem_classes="video-option-tabs") as video_tabs:
@@ -1024,82 +748,80 @@ def create_interface():
1024
  value=0,
1025
  precision=0
1026
  )
1027
-
1028
  with gr.Row():
1029
  start_stream_btn = gr.Button(
1030
  "πŸŽ₯ Start Live Recognition",
1031
  variant="primary",
1032
  size="lg"
1033
  )
1034
-
1035
  with gr.Tab("Upload Video", id="upload"):
1036
  video_file = gr.Video(
1037
  label="Upload Video File",
1038
  sources=["upload"],
1039
  format="mp4"
1040
  )
1041
-
1042
  with gr.Row():
1043
  process_video_btn = gr.Button(
1044
  "πŸ“Ή Process Video File",
1045
  variant="primary",
1046
  size="lg"
1047
  )
1048
-
1049
  stop_stream_btn = gr.Button(
1050
  "⏹️ Stop Processing",
1051
  variant="stop",
1052
  size="lg"
1053
  )
1054
-
1055
  stream_status = gr.Textbox(
1056
  label="Processing Status",
1057
  value="Ready to start...",
1058
  interactive=False,
1059
  lines=2
1060
  )
1061
-
1062
  gr.Markdown(
1063
  """
1064
- **πŸ“‹ Enhanced Instructions:**
1065
  - **Live Camera:** Select camera source and click "Start Live Recognition"
1066
  - **Video File:** Upload a video file and click "Process Video File"
1067
- - **Confidence Scoring:** System shows recognition confidence percentages
1068
- - **Auto-Registration:** Unknown faces are automatically registered with unique IDs
1069
- - **Cooldown Period:** 5-second cooldown between recognitions for same person
1070
 
1071
- **🎨 Enhanced Color Coding:**
1072
- - 🟒 **Green:** Known worker (attendance marked with confidence score)
1073
- - 🟠 **Orange:** New worker (auto-registered with AI caption)
1074
- - πŸ”΄ **Red:** Face detected but processing/low confidence
1075
  """
1076
  )
1077
-
1078
  with gr.Column(scale=1):
1079
  video_output = gr.Image(
1080
- label="Enhanced Recognition Output",
1081
  streaming=True,
1082
  interactive=False
1083
  )
1084
-
1085
  live_attendance_display = gr.Markdown(
1086
  value=attendance_system.get_today_attendance(),
1087
- label="Live Attendance Updates (Synced with Salesforce)"
1088
  )
1089
-
1090
  refresh_attendance_btn = gr.Button(
1091
  "πŸ”„ Refresh Attendance",
1092
  variant="secondary"
1093
  )
1094
-
1095
  # Manual Registration Tab
1096
- with gr.Tab("πŸ‘€ Enhanced Registration", elem_classes="tab-nav"):
1097
- gr.Markdown("### Register Workers Manually with AI-Powered Captions")
1098
-
1099
  with gr.Row():
1100
  with gr.Column(scale=1):
1101
  register_image = gr.Image(
1102
- label="Upload Worker's Photo (High Quality Recommended)",
1103
  type="pil",
1104
  height=300
1105
  )
@@ -1109,22 +831,11 @@ def create_interface():
1109
  lines=1
1110
  )
1111
  register_btn = gr.Button(
1112
- "πŸ‘€ Register Worker with AI Caption",
1113
  variant="primary",
1114
  size="lg"
1115
  )
1116
-
1117
- gr.Markdown(
1118
- """
1119
- **✨ Enhanced Features:**
1120
- - **AI-Powered Captions:** Automatic image description using Hugging Face
1121
- - **Face Validation:** Ensures clear face detection before registration
1122
- - **Duplicate Prevention:** Checks for existing registrations
1123
- - **Salesforce Sync:** Automatic cloud backup with retry mechanisms
1124
- - **High-Quality Storage:** Optimized face embeddings for better recognition
1125
- """
1126
- )
1127
-
1128
  with gr.Column(scale=1):
1129
  register_output = gr.Textbox(
1130
  label="Registration Status",
@@ -1133,16 +844,16 @@ def create_interface():
1133
  )
1134
  registered_workers_info = gr.Markdown(
1135
  value=attendance_system.get_registered_workers_info(),
1136
- label="Enhanced Workers Database (Synced with Salesforce)"
1137
  )
1138
-
1139
  # Reports & Analytics Tab
1140
- with gr.Tab("πŸ“Š Advanced Reports & Analytics", elem_classes="tab-nav"):
1141
- gr.Markdown("### Comprehensive Attendance Reports with PDF Generation and Salesforce Integration")
1142
-
1143
  with gr.Row():
1144
  with gr.Column():
1145
- gr.Markdown("#### πŸ“… Generate Comprehensive Report")
1146
  start_date = gr.Textbox(
1147
  label="Start Date (YYYY-MM-DD)",
1148
  value=date.today().replace(day=1).strftime('%Y-%m-%d')
@@ -1152,13 +863,13 @@ def create_interface():
1152
  value=date.today().strftime('%Y-%m-%d')
1153
  )
1154
  generate_report_btn = gr.Button(
1155
- "πŸ“Š Generate Enhanced Report with PDF",
1156
  variant="primary"
1157
  )
1158
-
1159
- gr.Markdown("#### πŸ’Ύ Export Enhanced Data")
1160
  export_btn = gr.Button(
1161
- "πŸ“₯ Export to CSV (Salesforce + Local)",
1162
  variant="secondary"
1163
  )
1164
  export_status = gr.Textbox(
@@ -1170,105 +881,83 @@ def create_interface():
1170
  label="Download File",
1171
  visible=False
1172
  )
1173
-
1174
- gr.Markdown(
1175
- """
1176
- **πŸš€ Enhanced Analytics Features:**
1177
- - **PDF Report Generation:** Professional reports with charts and statistics
1178
- - **Salesforce Integration:** Real-time data from cloud storage
1179
- - **Confidence Analytics:** Average recognition confidence scores
1180
- - **Attendance Patterns:** Daily, weekly, and monthly trends
1181
- - **Worker Performance:** Individual attendance percentages
1182
- - **Auto-Upload:** PDF reports automatically uploaded to Salesforce
1183
- """
1184
- )
1185
-
1186
  with gr.Column():
1187
  report_output = gr.Markdown(
1188
- value="Select date range and click 'Generate Enhanced Report' to view comprehensive attendance analytics with PDF generation.",
1189
- label="Enhanced Attendance Report"
1190
  )
1191
-
1192
- # Enhanced Event handlers
1193
  start_stream_btn.click(
1194
  fn=attendance_system.start_video_stream,
1195
  inputs=[camera_source],
1196
  outputs=[stream_status]
1197
  )
1198
-
1199
  process_video_btn.click(
1200
  fn=attendance_system.process_uploaded_video,
1201
  inputs=[video_file],
1202
  outputs=[stream_status]
1203
  )
1204
-
1205
  stop_stream_btn.click(
1206
  fn=attendance_system.stop_video_stream,
1207
  outputs=[stream_status]
1208
  )
1209
-
1210
- def refresh_all_data():
1211
- attendance = attendance_system.get_today_attendance()
1212
- sf_status = attendance_system.get_salesforce_status()
1213
- return attendance, sf_status
1214
-
1215
  refresh_attendance_btn.click(
1216
- fn=refresh_all_data,
1217
- outputs=[live_attendance_display, salesforce_status]
1218
  )
1219
-
1220
  register_btn.click(
1221
  fn=attendance_system.register_worker_manual,
1222
  inputs=[register_image, register_name],
1223
  outputs=[register_output, registered_workers_info]
1224
  )
1225
-
1226
  generate_report_btn.click(
1227
  fn=attendance_system.get_attendance_report,
1228
  inputs=[start_date, end_date],
1229
  outputs=[report_output]
1230
  )
1231
-
1232
  def export_and_show():
1233
  file_path, status = attendance_system.export_attendance_csv()
1234
  if file_path:
1235
  return status, gr.update(visible=True, value=file_path)
1236
  else:
1237
  return status, gr.update(visible=False)
1238
-
1239
  export_btn.click(
1240
  fn=export_and_show,
1241
  outputs=[export_status, export_file]
1242
  )
1243
-
1244
- # Enhanced video frame update with error handling
1245
  def update_video_frame():
1246
  start_time = time.time()
1247
  while True:
1248
- try:
1249
- current_time = time.time()
1250
- if current_time - start_time >= 0.03: # Update every 0.03 seconds
1251
- frame = attendance_system.get_current_frame()
1252
- if frame is not None:
1253
- # Convert BGR to RGB
1254
- frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
1255
- return frame
1256
- start_time = current_time
1257
- time.sleep(0.01) # Small sleep to prevent busy-waiting
1258
- except Exception as e:
1259
- logger.error(f"Error in video frame update: {e}")
1260
- time.sleep(0.1)
1261
-
1262
  # Start the video frame update as a background thread
1263
  video_thread = threading.Thread(target=lambda: demo.queue()(update_video_frame)())
1264
  video_thread.daemon = True
1265
  video_thread.start()
1266
-
1267
  return demo
1268
 
1269
- # Create and launch the enhanced interface
1270
  if __name__ == "__main__":
1271
- logger.info("Launching Enhanced Attendance System with Improved Salesforce Integration...")
1272
  demo = create_interface()
1273
  demo.launch(
1274
  server_name="0.0.0.0",
 
19
  import requests
20
  from simple_salesforce import Salesforce
21
  from dotenv import load_dotenv
 
22
  from retrying import retry
23
+ import logging
 
 
 
 
 
 
 
24
 
25
  # Setup logging
26
  logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
27
  logger = logging.getLogger(__name__)
28
 
29
+ # Load environment variables from .env file
30
+ load_dotenv()
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  # Hugging Face API configuration
33
  HF_API_URL = "https://api-inference.huggingface.co/models/Salesforce/blip-image-captioning-base"
34
  HF_API_TOKEN = os.getenv("HUGGINGFACE_API_TOKEN")
35
 
36
+ # Salesforce configuration
37
+ SF_CREDENTIALS = {
38
+ "username": os.getenv("SF_USERNAME", "smartlabour@attendance.system"),
39
+ "password": os.getenv("SF_PASSWORD", "#Prashanth@123"),
40
+ "security_token": os.getenv("SF_SECURITY_TOKEN", "pasQDqmWApzD0skgbv76gVgIs"),
41
+ "domain": "login"
42
+ }
43
+
44
+ @retry(stop_max_attempt_number=3, wait_fixed=2000)
45
  def connect_to_salesforce():
 
46
  try:
47
+ sf = Salesforce(**SF_CREDENTIALS)
48
+ logger.info("Connected to Salesforce")
 
49
  sf.describe()
50
  return sf
51
  except Exception as e:
52
  logger.error(f"Salesforce connection failed: {e}")
53
  raise
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  class AttendanceSystem:
56
  def __init__(self):
57
  self.known_face_embeddings = []
 
64
  self.frame_queue = queue.Queue(maxsize=2)
65
  self.recognition_thread = None
66
  self.last_recognition_time = {}
67
+ self.recognition_cooldown = 5 # seconds between recognitions for same person
68
  self.video_file_path = None
69
  self.video_processing = False
70
+
71
+ # Initialize Salesforce connection
72
  try:
73
  self.sf = connect_to_salesforce()
 
74
  except Exception as e:
75
+ logger.error(f"Error connecting to Salesforce: {e}")
76
  self.sf = None
77
+
78
  # Create directories for data storage
79
  os.makedirs("data", exist_ok=True)
80
  os.makedirs("data/faces", exist_ok=True)
 
 
 
81
 
82
+ self.load_data()
83
+
84
  def load_data(self):
85
+ """Load all stored data"""
86
  try:
87
  # Load face embeddings and worker data
88
  if os.path.exists("data/workers.pkl"):
 
92
  self.known_face_names = data.get("names", [])
93
  self.known_face_ids = data.get("ids", [])
94
  self.next_worker_id = data.get("next_id", 1)
95
+
96
  # Load attendance records
97
  if os.path.exists("data/attendance.json"):
98
  with open("data/attendance.json", "r") as f:
99
  self.attendance_records = json.load(f)
100
+
 
 
 
 
101
  except Exception as e:
102
  logger.error(f"Error loading data: {e}")
103
  self.known_face_embeddings = []
 
105
  self.known_face_ids = []
106
  self.attendance_records = []
107
  self.next_worker_id = 1
108
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  def save_data(self):
110
+ """Save all data to files"""
111
  try:
112
  # Save worker data
113
  worker_data = {
 
118
  }
119
  with open("data/workers.pkl", "wb") as f:
120
  pickle.dump(worker_data, f)
121
+
122
  # Save attendance records
123
  with open("data/attendance.json", "w") as f:
124
  json.dump(self.attendance_records, f, indent=2)
125
+
126
  except Exception as e:
127
  logger.error(f"Error saving data: {e}")
128
+
129
  def get_image_caption(self, image):
130
+ """Generate image caption using Hugging Face API"""
131
  try:
132
  # Convert PIL image to bytes
133
  if isinstance(image, Image.Image):
134
  img_byte_arr = BytesIO()
135
  image.save(img_byte_arr, format='JPEG')
136
  img_data = img_byte_arr.getvalue()
137
+
138
  # Make API request to Hugging Face
139
  headers = {"Authorization": f"Bearer {HF_API_TOKEN}"}
140
+ response = requests.post(HF_API_URL, headers=headers, data=img_data)
141
+
142
  if response.status_code == 200:
143
  result = response.json()
144
  if isinstance(result, list) and len(result) > 0:
145
+ return result[0].get("generated_text", "No caption generated")
146
+ return "No caption generated"
147
  else:
148
+ logger.error(f"Hugging Face API error: {response.status_code} - {response.text}")
149
+ return "Error generating caption"
150
  except Exception as e:
151
  logger.error(f"Error in Hugging Face API call: {e}")
152
+ return "Error generating caption"
153
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  def register_worker_manual(self, image, name):
155
+ """Manual worker registration with Hugging Face and Salesforce integration"""
156
  if image is None or not name.strip():
157
  return "❌ Please provide both image and name!", self.get_registered_workers_info()
158
+
159
  # Convert PIL image to RGB array
160
  if isinstance(image, Image.Image):
161
  image_array = np.array(image)
162
+
163
  try:
164
  # Verify the image contains a face
165
  face_analysis = DeepFace.analyze(img_path=image_array, actions=['emotion'], enforce_detection=True, detector_backend='opencv')
166
+
167
  # Get face embedding
168
  embedding = DeepFace.represent(img_path=image_array, model_name='Facenet')[0]['embedding']
169
+
170
  # Check if person already exists
171
  name = name.strip().title()
172
  if name in self.known_face_names:
173
  return f"❌ {name} is already registered!", self.get_registered_workers_info()
174
+
175
  # Generate new worker ID
176
  worker_id = f"W{self.next_worker_id:04d}"
177
+
178
  # Generate image caption using Hugging Face
179
  caption = self.get_image_caption(image)
180
+
181
  # Add the face embedding, name, and ID
182
  self.known_face_embeddings.append(embedding)
183
  self.known_face_names.append(name)
184
  self.known_face_ids.append(worker_id)
185
  self.next_worker_id += 1
186
+
187
+ # Save face image locally
188
  face_image = Image.fromarray(image_array)
189
+ image_path = f"data/faces/{worker_id}_{name.replace(' ', '_')}.jpg"
190
+ face_image.save(image_path)
191
+
 
 
 
 
 
 
 
 
192
  # Save to Salesforce
193
+ if self.sf:
194
+ try:
195
+ # Create Worker__c record with image path
196
+ self.sf.Worker__c.create({
197
+ 'Name': name,
198
+ 'Worker_ID__c': worker_id,
199
+ 'Face_Embedding__c': json.dumps(embedding),
200
+ 'Image_Caption__c': caption,
201
+ 'Image_Path__c': image_path
202
+ })
203
+ logger.info(f"Worker {name} ({worker_id}) saved to Salesforce with image path: {image_path}")
204
+ except Exception as e:
205
+ logger.error(f"Error saving to Salesforce: {e}")
206
+
207
  self.save_data()
 
 
 
 
 
 
 
 
208
 
209
+ return f"βœ… {name} has been successfully registered with ID: {worker_id}! Caption: {caption}\nImage Path: {image_path}", self.get_registered_workers_info()
210
+
211
  except ValueError as e:
212
  if "Face could not be detected" in str(e):
213
  return "❌ No face detected in the image! Please try again with a clear face image.", self.get_registered_workers_info()
214
  return f"❌ Error processing image: {str(e)}", self.get_registered_workers_info()
215
  except Exception as e:
 
216
  return f"❌ Error during registration: {str(e)}", self.get_registered_workers_info()
217
+
218
  def register_worker_auto(self, face_image):
219
+ """Automatic worker registration for unrecognized faces"""
220
  try:
221
  # Generate new worker ID and name
222
  worker_id = f"W{self.next_worker_id:04d}"
223
  worker_name = f"Unknown_Worker_{self.next_worker_id}"
224
+
225
  # Get face embedding
226
  embedding = DeepFace.represent(img_path=face_image, model_name='Facenet')[0]['embedding']
227
+
228
  # Generate image caption
229
  face_pil = Image.fromarray(cv2.cvtColor(face_image, cv2.COLOR_BGR2RGB))
230
  caption = self.get_image_caption(face_pil)
231
+
232
  # Add to database
233
  self.known_face_embeddings.append(embedding)
234
  self.known_face_names.append(worker_name)
235
  self.known_face_ids.append(worker_id)
236
  self.next_worker_id += 1
237
+
238
+ # Save face image locally
239
+ image_path = f"data/faces/{worker_id}_{worker_name}.jpg"
240
+ face_pil.save(image_path)
241
+
 
 
 
 
 
 
 
 
242
  # Save to Salesforce
243
+ if self.sf:
244
+ try:
245
+ # Create Worker__c record with image path
246
+ self.sf.Worker__c.create({
247
+ 'Name': worker_name,
248
+ 'Worker_ID__c': worker_id,
249
+ 'Face_Embedding__c': json.dumps(embedding),
250
+ 'Image_Caption__c': caption,
251
+ 'Image_Path__c': image_path
252
+ })
253
+ logger.info(f"Worker {worker_name} ({worker_id}) saved to Salesforce with image path: {image_path}")
254
+ except Exception as e:
255
+ logger.error(f"Error saving to Salesforce: {e}")
256
+
257
  self.save_data()
258
+
259
  return worker_id, worker_name
260
+
261
  except Exception as e:
262
  logger.error(f"Error in auto registration: {e}")
263
  return None, None
264
+
265
+ def mark_attendance(self, worker_id, worker_name):
266
+ """Mark attendance for a worker and save to Salesforce"""
267
  try:
268
  today = date.today().isoformat()
269
  current_time = datetime.now()
270
+
271
  # Check if already marked today
272
  already_marked = any(
273
  record["worker_id"] == worker_id and record["date"] == today
274
  for record in self.attendance_records
275
  )
276
+
277
  if not already_marked:
278
  # Create attendance record
279
  attendance_record = {
 
283
  "time": current_time.strftime("%H:%M:%S"),
284
  "timestamp": current_time.isoformat(),
285
  "status": "Present",
286
+ "method": "Auto"
 
287
  }
288
  self.attendance_records.append(attendance_record)
289
+
 
 
 
 
 
 
 
 
 
 
 
 
290
  # Save to Salesforce
291
+ if self.sf:
292
+ try:
293
+ self.sf.Attendance__c.create({
294
+ 'Worker_ID__c': worker_id,
295
+ 'Name__c': worker_name,
296
+ 'Date__c': today,
297
+ 'Time__c': current_time.strftime("%H:%M:%S"),
298
+ 'Timestamp__c': current_time.isoformat(),
299
+ 'Status__c': "Present",
300
+ 'Method__c': "Auto"
301
+ })
302
+ logger.info(f"Attendance for {worker_name} ({worker_id}) saved to Salesforce")
303
+ except Exception as e:
304
+ logger.error(f"Error saving attendance to Salesforce: {e}")
305
+
306
  self.save_data()
307
  return True
308
  return False
309
+
310
  except Exception as e:
311
  logger.error(f"Error marking attendance: {e}")
312
  return False
313
+
314
  def process_video_frame(self, frame):
315
+ """Process a single video frame for face recognition"""
316
  try:
317
  rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
318
+
319
  # Find faces in the frame
320
+ face_objs = DeepFace.extract_faces(img_path=rgb_frame, target_size=(160, 160), enforce_detection=False, detector_backend='opencv')
321
+
 
 
 
 
 
322
  current_time = time.time()
323
+
324
  for face_obj in face_objs:
325
+ if face_obj['confidence'] > 0.9: # Only consider confident detections
326
  face_area = face_obj['facial_area']
327
  x, y, w, h = face_area['x'], face_area['y'], face_area['w'], face_area['h']
328
+
329
  # Extract face image
330
  face_image = frame[y:y+h, x:x+w]
331
+
332
  try:
333
  # Get face embedding
334
  embedding = DeepFace.represent(img_path=face_image, model_name='Facenet')[0]['embedding']
335
+
336
  worker_id = None
337
  worker_name = "Unknown"
338
  color = (0, 0, 255) # Red for unknown
339
+
 
340
  # Compare with known faces
341
  if len(self.known_face_embeddings) > 0:
342
  # Calculate distances to known faces
 
344
  for known_embedding in self.known_face_embeddings:
345
  distance = np.linalg.norm(np.array(embedding) - np.array(known_embedding))
346
  distances.append(distance)
347
+
348
  min_distance = min(distances)
349
  best_match_index = distances.index(min_distance)
350
+
351
+ if min_distance < 10: # Threshold for recognition
 
352
  worker_id = self.known_face_ids[best_match_index]
353
  worker_name = self.known_face_names[best_match_index]
354
  color = (0, 255, 0) # Green for known
355
+
356
  # Check cooldown period
357
  if worker_id not in self.last_recognition_time or \
358
  current_time - self.last_recognition_time[worker_id] > self.recognition_cooldown:
359
+
360
  # Mark attendance
361
+ if self.mark_attendance(worker_id, worker_name):
362
+ logger.info(f"Attendance marked for {worker_name} ({worker_id})")
363
+
364
  self.last_recognition_time[worker_id] = current_time
365
  else:
366
  # Unknown face - auto register
 
370
  worker_id = new_id
371
  worker_name = new_name
372
  color = (255, 165, 0) # Orange for newly registered
373
+ logger.info(f"New worker registered: {new_name} ({new_id})")
374
+
375
  # Mark attendance for new worker
376
+ self.mark_attendance(worker_id, worker_name)
377
+
378
  # Draw rectangle and label
379
  cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2)
380
  cv2.rectangle(frame, (x, y+h - 35), (x+w, y+h), color, cv2.FILLED)
381
+
382
  label = f"{worker_name}"
383
  if worker_id:
384
  label += f" ({worker_id})"
385
+
 
 
386
  cv2.putText(frame, label, (x + 6, y+h - 6),
387
  cv2.FONT_HERSHEY_DUPLEX, 0.6, (255, 255, 255), 1)
388
+
389
  except Exception as e:
390
  logger.error(f"Error processing face: {e}")
391
  continue
392
+
393
  return frame
394
+
395
  except Exception as e:
396
  logger.error(f"Error processing frame: {e}")
397
  return frame
398
+
399
  def start_video_stream(self, camera_source=0):
400
+ """Start video streaming and recognition"""
401
  try:
402
  if self.is_streaming:
403
  return "⚠️ Video stream is already running!"
404
+
405
  # Clear previous video file if switching from file to camera
406
  self.video_file_path = None
407
+
408
  self.video_capture = cv2.VideoCapture(camera_source)
409
  if not self.video_capture.isOpened():
410
  return "❌ Could not open camera/video source!"
411
+
412
  self.is_streaming = True
413
+
414
  def video_loop():
415
  while self.is_streaming:
416
  ret, frame = self.video_capture.read()
417
  if not ret:
418
  break
419
+
420
  # Process frame for face recognition
421
  processed_frame = self.process_video_frame(frame)
422
+
423
  # Add to queue for display
424
  if not self.frame_queue.full():
425
  try:
426
  self.frame_queue.put_nowait(processed_frame)
427
  except queue.Full:
428
  pass
429
+
430
  time.sleep(0.1) # Limit processing rate
431
+
432
  self.recognition_thread = threading.Thread(target=video_loop)
433
  self.recognition_thread.daemon = True
434
  self.recognition_thread.start()
435
+
436
  return "βœ… Live camera stream started successfully!"
437
+
438
  except Exception as e:
 
439
  return f"❌ Error starting video stream: {e}"
440
+
441
  def process_uploaded_video(self, video_path):
442
+ """Process an uploaded video file for face recognition"""
443
  try:
444
  if self.is_streaming:
445
  return "⚠️ Please stop current stream before processing a video file!"
446
+
447
  if not os.path.exists(video_path):
448
  return "❌ Video file not found!"
449
+
450
  self.video_file_path = video_path
451
  self.video_processing = True
452
+
453
  def video_processing_loop():
454
  cap = cv2.VideoCapture(video_path)
455
  fps = cap.get(cv2.CAP_PROP_FPS)
456
  frame_delay = 1.0 / fps if fps > 0 else 0.03
457
+
458
  while self.video_processing and cap.isOpened():
459
  ret, frame = cap.read()
460
  if not ret:
461
  break
462
+
463
  # Process frame for face recognition
464
  processed_frame = self.process_video_frame(frame)
465
+
466
  # Add to queue for display
467
  if not self.frame_queue.full():
468
  try:
469
  self.frame_queue.put_nowait(processed_frame)
470
  except queue.Full:
471
  pass
 
 
472
 
473
+ time.sleep(frame_delay)
474
+
475
  cap.release()
476
  self.video_processing = False
477
+
478
  self.recognition_thread = threading.Thread(target=video_processing_loop)
479
  self.recognition_thread.daemon = True
480
  self.recognition_thread.start()
481
+
482
  return f"βœ… Video processing started successfully! ({os.path.basename(video_path)})"
483
+
484
  except Exception as e:
 
485
  return f"❌ Error processing video: {e}"
486
+
487
  def stop_video_stream(self):
488
+ """Stop video streaming or processing"""
489
  try:
490
  self.is_streaming = False
491
  self.video_processing = False
492
+
493
  if self.video_capture:
494
  self.video_capture.release()
495
  self.video_capture = None
496
+
497
  if self.recognition_thread:
498
  self.recognition_thread.join(timeout=2)
499
+
500
  # Clear frame queue
501
  while not self.frame_queue.empty():
502
  try:
503
  self.frame_queue.get_nowait()
504
  except queue.Empty:
505
  break
506
+
507
  return "βœ… Video stream/processing stopped successfully!"
508
+
509
  except Exception as e:
 
510
  return f"❌ Error stopping video: {e}"
511
+
512
  def get_current_frame(self):
513
  """Get current frame for display"""
514
  try:
 
518
  return None
519
  except queue.Empty:
520
  return None
521
+
522
  def get_registered_workers_info(self):
523
+ """Get information about registered workers from Salesforce"""
524
+ if not self.sf:
525
+ return "❌ Salesforce connection not established."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
 
527
+ try:
528
+ workers = self.sf.query_all("SELECT Name, Worker_ID__c, Image_Caption__c, Image_Path__c FROM Worker__c")['records']
529
+ if not workers:
530
+ return "No workers registered yet."
531
+
532
+ info = f"**Registered Workers ({len(workers)}):**\n\n"
533
+ for i, worker in enumerate(workers, 1):
534
+ info += f"{i}. **{worker['Name']}** (ID: {worker['Worker_ID__c']}) - Caption: {worker['Image_Caption__c'] or 'N/A'}\n"
535
+ info += f" Image Path: {worker['Image_Path__c'] or 'N/A'}\n"
536
+ return info
537
+ except Exception as e:
538
+ logger.error(f"Error fetching workers from Salesforce: {e}")
539
+ return self._get_local_workers_info()
540
+
541
  def _get_local_workers_info(self):
542
  """Fallback to local worker info if Salesforce query fails"""
543
  if not self.known_face_names:
544
  return "No workers registered yet."
545
+
546
+ info = f"**Registered Workers ({len(self.known_face_names)}):**\n\n"
547
  for i, (worker_id, name) in enumerate(zip(self.known_face_ids, self.known_face_names), 1):
548
  info += f"{i}. **{name}** (ID: {worker_id})\n"
549
  return info
550
+
551
  def get_today_attendance(self):
552
+ """Get today’s attendance records from Salesforce"""
553
+ if not self.sf:
554
+ return "❌ Salesforce connection not established."
555
+
556
  today = date.today().isoformat()
557
+ try:
558
+ records = self.sf.query_all(
559
+ f"SELECT Name__c, Worker_ID__c, Time__c, Method__c FROM Attendance__c WHERE Date__c = '{today}'"
560
+ )['records']
561
 
562
+ if not records:
563
+ return f"**Today's Attendance ({today}):**\n\nNo attendance marked yet."
564
+
565
+ info = f"**Today's Attendance ({today}):**\n\n"
566
+ for record in records:
567
+ method_icon = "πŸ€–" if record['Method__c'] == "Auto" else "πŸ‘€"
568
+ info += f"{method_icon} **{record['Name__c']}** (ID: {record['Worker_ID__c']}) - {record['Time__c']}\n"
569
+ return info
570
+ except Exception as e:
571
+ logger.error(f"Error fetching attendance from Salesforce: {e}")
 
 
 
 
 
 
 
 
 
 
572
  return self._get_local_today_attendance()
573
+
574
  def _get_local_today_attendance(self):
575
  """Fallback to local attendance records if Salesforce query fails"""
576
  today = date.today().isoformat()
577
  today_records = [r for r in self.attendance_records if r["date"] == today]
578
+
579
  if not today_records:
580
+ return f"**Today's Attendance ({today}):**\n\nNo attendance marked yet."
581
+
582
+ info = f"**Today's Attendance ({today}):**\n\n"
583
  for record in today_records:
584
  method_icon = "πŸ€–" if record.get("method") == "Auto" else "πŸ‘€"
585
+ info += f"{method_icon} **{record['name']}** (ID: {record['worker_id']}) - {record['time']}\n"
 
 
586
  return info
587
+
588
  def get_attendance_report(self, start_date, end_date):
589
+ """Generate attendance report for date range from Salesforce"""
590
  if not start_date or not end_date:
591
  return "Please select both start and end dates."
592
+
593
  try:
594
  # Validate date format
595
  datetime.strptime(start_date, '%Y-%m-%d')
596
  datetime.strptime(end_date, '%Y-%m-%d')
597
  except ValueError:
598
  return "Invalid date format. Please use YYYY-MM-DD."
599
+
600
+ if not self.sf:
601
+ return "❌ Salesforce connection not established."
602
+
603
  try:
604
+ # Query Salesforce for attendance records
605
+ records = self.sf.query_all(
606
+ f"SELECT Worker_ID__c, Name__c, Date__c, Time__c, Method__c FROM Attendance__c "
607
+ f"WHERE Date__c >= '{start_date}' AND Date__c <= '{end_date}'"
608
+ )['records']
609
+
 
 
 
 
 
 
 
 
 
 
 
610
  if not records:
611
  return f"No attendance records found between {start_date} and {end_date}."
612
+
 
 
 
 
613
  # Create DataFrame for analysis
614
+ df = pd.DataFrame(records)
615
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
616
  # Summary statistics
617
  total_days = (pd.to_datetime(end_date) - pd.to_datetime(start_date)).days + 1
618
+ unique_workers = df['Worker_ID__c'].nunique()
619
  total_attendances = len(df)
620
+ auto_registrations = len(df[df['Method__c'] == 'Auto'])
621
+
622
+ report = f"**πŸ“Š Attendance Report ({start_date} to {end_date})**\n\n"
 
 
623
  report += f"**Summary:**\n"
624
  report += f"β€’ Total Days: {total_days}\n"
625
  report += f"β€’ Unique Workers: {unique_workers}\n"
626
  report += f"β€’ Total Attendances: {total_attendances}\n"
627
+ report += f"β€’ Auto Detections: {auto_registrations}\n\n"
628
+
 
629
  # Individual attendance counts
630
  if not df.empty:
631
+ attendance_counts = df.groupby(['Worker_ID__c', 'Name__c']).size().reset_index(name='count')
632
  report += f"**πŸ‘₯ Individual Attendance:**\n"
633
  for _, row in attendance_counts.iterrows():
634
  percentage = (row['count'] / total_days) * 100
635
+ report += f"β€’ **{row['Name__c']}** ({row['Worker_ID__c']}): {row['count']} days ({percentage:.1f}%)\n"
636
+
 
 
 
637
  return report
638
  except Exception as e:
639
+ logger.error(f"Error generating report from Salesforce: {e}")
640
+ return self._get_local_attendance_report(start_date, end_date)
641
+
642
+ def _get_local_attendance_report(self, start_date, end_date):
643
+ """Fallback to local attendance report if Salesforce query fails"""
644
+ # Filter records by date range
645
+ filtered_records = [
646
+ r for r in self.attendance_records
647
+ if start_date <= r["date"] <= end_date
648
+ ]
649
 
650
+ if not filtered_records:
651
+ return f"No attendance records found between {start_date} and {end_date}."
652
+
653
+ # Create DataFrame for analysis
654
+ df = pd.DataFrame(filtered_records)
655
+
656
+ # Summary statistics
657
+ total_days = (pd.to_datetime(end_date) - pd.to_datetime(start_date)).days + 1
658
+ unique_workers = df['worker_id'].nunique()
659
+ total_attendances = len(df)
660
+ auto_registrations = len(df[df['method'] == 'Auto'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
661
 
662
+ report = f"**πŸ“Š Attendance Report ({start_date} to {end_date})**\n\n"
663
+ report += f"**Summary:**\n"
664
+ report += f"β€’ Total Days: {total_days}\n"
665
+ report += f"β€’ Unique Workers: {unique_workers}\n"
666
+ report += f"β€’ Total Attendances: {total_attendances}\n"
667
+ report += f"β€’ Auto Detections: {auto_registrations}\n\n"
668
+
669
+ # Individual attendance counts
670
+ if not df.empty:
671
+ attendance_counts = df.groupby(['worker_id', 'name']).size().reset_index(name='count')
672
+ report += f"**πŸ‘₯ Individual Attendance:**\n"
673
+ for _, row in attendance_counts.iterrows():
674
+ percentage = (row['count'] / total_days) * 100
675
+ report += f"β€’ **{row['name']}** ({row['worker_id']}): {row['count']} days ({percentage:.1f}%)\n"
676
+
677
+ return report
678
+
679
  def export_attendance_csv(self):
680
+ """Export attendance records to CSV"""
681
  try:
682
+ if not self.attendance_records:
683
+ return None, "No attendance records to export."
684
+
685
+ df = pd.DataFrame(self.attendance_records)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
686
  timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
687
+ csv_file = f"attendance_report_{timestamp}.csv"
688
  df.to_csv(csv_file, index=False)
689
+
690
+ return csv_file, f"βœ… Attendance exported to {csv_file}"
691
+
692
  except Exception as e:
 
693
  return None, f"❌ Error exporting data: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
694
 
695
  # Initialize the attendance system
696
  attendance_system = AttendanceSystem()
697
 
698
  def create_interface():
699
  with gr.Blocks(
700
+ title="🎯 Advanced Attendance System with Video Recognition",
701
  theme=gr.themes.Soft(),
702
  css="""
703
  .gradio-container {
 
714
  .video-option-tabs {
715
  margin-bottom: 15px;
716
  }
 
 
 
 
 
 
 
717
  """
718
  ) as demo:
 
719
  gr.Markdown(
720
  """
721
+ # 🎯 Advanced Attendance System with Face Recognition
722
+
723
+ **Comprehensive facial recognition system with live camera and video file processing, integrated with Hugging Face and Salesforce**
724
+
725
+ ## πŸš€ **Key Features:**
726
+ - **πŸŽ₯ Live Camera Recognition** - Real-time face detection from camera/CCTV
727
+ - **πŸ“Ή Video File Processing** - Process pre-recorded videos for attendance
728
+ - **πŸ€– Automatic Worker Registration** - Auto-register unknown faces with unique IDs
729
+ - **πŸ‘€ Manual Registration** - Register workers manually with photos and AI-generated captions
730
+ - **πŸ“… 24-Hour Attendance Rule** - One attendance mark per worker per day
731
+ - **πŸ“Š Advanced Analytics** - Detailed reports and data export
732
+ - **πŸ€— Hugging Face Integration** - AI-powered image captioning
733
+ - **☁️ Salesforce Integration** - Store worker and attendance data in Salesforce
 
 
734
  """
735
  )
736
+
 
 
 
 
 
 
737
  with gr.Tabs():
738
  # Video Recognition Tab
739
  with gr.Tab("πŸŽ₯ Video Recognition", elem_classes="tab-nav"):
740
+ gr.Markdown("### Face Recognition from Live Camera or Video File")
741
+
742
  with gr.Row():
743
  with gr.Column(scale=1):
744
  with gr.Tabs(selected="live", elem_classes="video-option-tabs") as video_tabs:
 
748
  value=0,
749
  precision=0
750
  )
751
+
752
  with gr.Row():
753
  start_stream_btn = gr.Button(
754
  "πŸŽ₯ Start Live Recognition",
755
  variant="primary",
756
  size="lg"
757
  )
758
+
759
  with gr.Tab("Upload Video", id="upload"):
760
  video_file = gr.Video(
761
  label="Upload Video File",
762
  sources=["upload"],
763
  format="mp4"
764
  )
765
+
766
  with gr.Row():
767
  process_video_btn = gr.Button(
768
  "πŸ“Ή Process Video File",
769
  variant="primary",
770
  size="lg"
771
  )
772
+
773
  stop_stream_btn = gr.Button(
774
  "⏹️ Stop Processing",
775
  variant="stop",
776
  size="lg"
777
  )
778
+
779
  stream_status = gr.Textbox(
780
  label="Processing Status",
781
  value="Ready to start...",
782
  interactive=False,
783
  lines=2
784
  )
785
+
786
  gr.Markdown(
787
  """
788
+ **πŸ“‹ Instructions:**
789
  - **Live Camera:** Select camera source and click "Start Live Recognition"
790
  - **Video File:** Upload a video file and click "Process Video File"
791
+ - Click "Stop Processing" to stop current session
 
 
792
 
793
+ **🎨 Color Coding:**
794
+ - 🟒 **Green:** Known worker (attendance marked)
795
+ - 🟠 **Orange:** New worker (auto-registered)
796
+ - πŸ”΄ **Red:** Face detected but processing
797
  """
798
  )
799
+
800
  with gr.Column(scale=1):
801
  video_output = gr.Image(
802
+ label="Recognition Output",
803
  streaming=True,
804
  interactive=False
805
  )
806
+
807
  live_attendance_display = gr.Markdown(
808
  value=attendance_system.get_today_attendance(),
809
+ label="Live Attendance Updates"
810
  )
811
+
812
  refresh_attendance_btn = gr.Button(
813
  "πŸ”„ Refresh Attendance",
814
  variant="secondary"
815
  )
816
+
817
  # Manual Registration Tab
818
+ with gr.Tab("πŸ‘€ Manual Registration", elem_classes="tab-nav"):
819
+ gr.Markdown("### Register Workers Manually")
820
+
821
  with gr.Row():
822
  with gr.Column(scale=1):
823
  register_image = gr.Image(
824
+ label="Upload Worker's Photo",
825
  type="pil",
826
  height=300
827
  )
 
831
  lines=1
832
  )
833
  register_btn = gr.Button(
834
+ "πŸ‘€ Register Worker",
835
  variant="primary",
836
  size="lg"
837
  )
838
+
 
 
 
 
 
 
 
 
 
 
 
839
  with gr.Column(scale=1):
840
  register_output = gr.Textbox(
841
  label="Registration Status",
 
844
  )
845
  registered_workers_info = gr.Markdown(
846
  value=attendance_system.get_registered_workers_info(),
847
+ label="Registered Workers Database"
848
  )
849
+
850
  # Reports & Analytics Tab
851
+ with gr.Tab("πŸ“Š Reports & Analytics", elem_classes="tab-nav"):
852
+ gr.Markdown("### Attendance Reports and Data Export")
853
+
854
  with gr.Row():
855
  with gr.Column():
856
+ gr.Markdown("#### πŸ“… Generate Report")
857
  start_date = gr.Textbox(
858
  label="Start Date (YYYY-MM-DD)",
859
  value=date.today().replace(day=1).strftime('%Y-%m-%d')
 
863
  value=date.today().strftime('%Y-%m-%d')
864
  )
865
  generate_report_btn = gr.Button(
866
+ "πŸ“Š Generate Report",
867
  variant="primary"
868
  )
869
+
870
+ gr.Markdown("#### πŸ’Ύ Export Data")
871
  export_btn = gr.Button(
872
+ "πŸ“₯ Export to CSV",
873
  variant="secondary"
874
  )
875
  export_status = gr.Textbox(
 
881
  label="Download File",
882
  visible=False
883
  )
884
+
 
 
 
 
 
 
 
 
 
 
 
 
885
  with gr.Column():
886
  report_output = gr.Markdown(
887
+ value="Select date range and click 'Generate Report' to view attendance analytics.",
888
+ label="Attendance Report"
889
  )
890
+
891
+ # Event handlers
892
  start_stream_btn.click(
893
  fn=attendance_system.start_video_stream,
894
  inputs=[camera_source],
895
  outputs=[stream_status]
896
  )
897
+
898
  process_video_btn.click(
899
  fn=attendance_system.process_uploaded_video,
900
  inputs=[video_file],
901
  outputs=[stream_status]
902
  )
903
+
904
  stop_stream_btn.click(
905
  fn=attendance_system.stop_video_stream,
906
  outputs=[stream_status]
907
  )
908
+
 
 
 
 
 
909
  refresh_attendance_btn.click(
910
+ fn=attendance_system.get_today_attendance,
911
+ outputs=[live_attendance_display]
912
  )
913
+
914
  register_btn.click(
915
  fn=attendance_system.register_worker_manual,
916
  inputs=[register_image, register_name],
917
  outputs=[register_output, registered_workers_info]
918
  )
919
+
920
  generate_report_btn.click(
921
  fn=attendance_system.get_attendance_report,
922
  inputs=[start_date, end_date],
923
  outputs=[report_output]
924
  )
925
+
926
  def export_and_show():
927
  file_path, status = attendance_system.export_attendance_csv()
928
  if file_path:
929
  return status, gr.update(visible=True, value=file_path)
930
  else:
931
  return status, gr.update(visible=False)
932
+
933
  export_btn.click(
934
  fn=export_and_show,
935
  outputs=[export_status, export_file]
936
  )
937
+
938
+ # Video frame update
939
  def update_video_frame():
940
  start_time = time.time()
941
  while True:
942
+ current_time = time.time()
943
+ if current_time - start_time >= 0.03: # Update every 0.03 seconds
944
+ frame = attendance_system.get_current_frame()
945
+ if frame is not None:
946
+ # Convert BGR to RGB
947
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
948
+ return frame
949
+ start_time = current_time
950
+ time.sleep(0.01) # Small sleep to prevent busy-waiting
951
+
 
 
 
 
952
  # Start the video frame update as a background thread
953
  video_thread = threading.Thread(target=lambda: demo.queue()(update_video_frame)())
954
  video_thread.daemon = True
955
  video_thread.start()
956
+
957
  return demo
958
 
959
+ # Create and launch the interface
960
  if __name__ == "__main__":
 
961
  demo = create_interface()
962
  demo.launch(
963
  server_name="0.0.0.0",