Spaces:
Build error
Build error
| # frontend/app.py (Streamlit version) | |
| # app.py | |
| import gradio as gr | |
| import cv2 | |
| import numpy as np | |
| from datetime import datetime | |
| import pandas as pd | |
| import plotly.graph_objects as go | |
| from plotly.subplots import make_subplots | |
| import random | |
| import time | |
| # Global variables for demo | |
| employees = [ | |
| {"id": 1, "name": "Alice Johnson", "department": "Engineering", "status": "present"}, | |
| {"id": 2, "name": "Bob Smith", "department": "Sales", "status": "present"}, | |
| {"id": 3, "name": "Carol White", "department": "HR", "status": "absent"}, | |
| {"id": 4, "name": "David Brown", "department": "Engineering", "status": "present"}, | |
| {"id": 5, "name": "Eve Davis", "department": "Marketing", "status": "late"}, | |
| {"id": 6, "name": "Frank Wilson", "department": "Sales", "status": "present"}, | |
| {"id": 7, "name": "Grace Lee", "department": "Engineering", "status": "absent"}, | |
| {"id": 8, "name": "Henry Kim", "department": "HR", "status": "present"}, | |
| ] | |
| # Demo detector | |
| class DemoFaceDetector: | |
| def __init__(self): | |
| self.confidence = 0.95 | |
| self.faces_detected = 0 | |
| self.last_names = [] | |
| def detect(self, image): | |
| """Simulate face detection for demo""" | |
| if image is None: | |
| return [], [] | |
| # Simulate processing | |
| self.faces_detected += 1 | |
| # Mock detection - draw boxes on image | |
| img_copy = image.copy() | |
| h, w = img_copy.shape[:2] | |
| # Draw some fake face boxes | |
| import random | |
| faces = [] | |
| names = [] | |
| num_faces = random.randint(2, 4) | |
| for i in range(num_faces): | |
| # Random positions | |
| x = random.randint(50, w-150) | |
| y = random.randint(50, h-150) | |
| face_w = random.randint(80, 120) | |
| face_h = random.randint(80, 120) | |
| # Draw rectangle | |
| cv2.rectangle(img_copy, (x, y), (x+face_w, y+face_h), (0, 255, 0), 2) | |
| # Get random employee | |
| emp = random.choice(employees) | |
| name = emp['name'] | |
| names.append(name) | |
| # Add name label | |
| cv2.putText(img_copy, name, (x, y-10), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) | |
| # Add status | |
| status_emoji = "β " if emp['status'] == 'present' else "β°" if emp['status'] == 'late' else "β" | |
| cv2.putText(img_copy, status_emoji, (x+face_w-30, y+face_h-10), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) | |
| faces.append({'bbox': (x, y, x+face_w, y+face_h), 'name': name}) | |
| self.last_names = names | |
| return img_copy, names | |
| # Initialize detector | |
| detector = DemoFaceDetector() | |
| def get_attendance_df(): | |
| """Get attendance as DataFrame""" | |
| return pd.DataFrame(employees) | |
| def process_frame(frame): | |
| """Process video frame for live feed""" | |
| if frame is None: | |
| return None | |
| # Convert to RGB if needed | |
| if len(frame.shape) == 2: | |
| frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2RGB) | |
| # Detect faces | |
| processed_frame, names = detector.detect(frame) | |
| # Update status simulation | |
| for emp in employees: | |
| if emp['name'] in names: | |
| emp['status'] = 'present' if random.random() > 0.1 else 'late' | |
| return processed_frame | |
| def create_dashboard(): | |
| """Create analytics dashboard with Plotly""" | |
| df = get_attendance_df() | |
| # Create subplots | |
| fig = make_subplots( | |
| rows=2, cols=2, | |
| subplot_titles=( | |
| 'Attendance by Department', | |
| 'Status Distribution', | |
| 'Daily Trend', | |
| 'Department Coverage' | |
| ), | |
| specs=[[{'type': 'bar'}, {'type': 'pie'}], | |
| [{'type': 'scatter'}, {'type': 'bar'}]] | |
| ) | |
| # 1. Department breakdown | |
| dept_counts = df.groupby('department')['status'].value_counts().unstack().fillna(0) | |
| for status in dept_counts.columns: | |
| fig.add_trace( | |
| go.Bar(name=status, x=dept_counts.index, y=dept_counts[status]), | |
| row=1, col=1 | |
| ) | |
| # 2. Status distribution | |
| status_counts = df['status'].value_counts() | |
| fig.add_trace( | |
| go.Pie(labels=status_counts.index, values=status_counts.values), | |
| row=1, col=2 | |
| ) | |
| # 3. Daily trend (simulated) | |
| dates = pd.date_range(end=datetime.now(), periods=7) | |
| trend_data = { | |
| 'date': dates, | |
| 'present': [random.randint(5, 8) for _ in range(7)], | |
| 'absent': [random.randint(0, 3) for _ in range(7)], | |
| 'late': [random.randint(0, 2) for _ in range(7)] | |
| } | |
| for key in ['present', 'absent', 'late']: | |
| fig.add_trace( | |
| go.Scatter(x=dates, y=trend_data[key], name=key, mode='lines+markers'), | |
| row=2, col=1 | |
| ) | |
| # 4. Department coverage | |
| total_dept = df.groupby('department').size().reset_index(name='total') | |
| present_dept = df[df['status'] == 'present'].groupby('department').size().reset_index(name='present') | |
| coverage = total_dept.merge(present_dept, on='department', how='left').fillna(0) | |
| coverage['coverage_pct'] = (coverage['present'] / coverage['total'] * 100).round(1) | |
| fig.add_trace( | |
| go.Bar(x=coverage['department'], y=coverage['coverage_pct'], | |
| name='Coverage %', text=coverage['coverage_pct'], textposition='auto'), | |
| row=2, col=2 | |
| ) | |
| fig.update_layout(height=800, showlegend=True, title_text="π Personnel Analytics Dashboard") | |
| fig.update_xaxes(title_text="Department", row=2, col=2) | |
| fig.update_yaxes(title_text="Coverage %", row=2, col=2) | |
| return fig | |
| def get_metrics(): | |
| """Get dashboard metrics""" | |
| df = get_attendance_df() | |
| total = len(df) | |
| present = len(df[df['status'] == 'present']) | |
| late = len(df[df['status'] == 'late']) | |
| absent = len(df[df['status'] == 'absent']) | |
| return { | |
| 'total': total, | |
| 'present': present, | |
| 'late': late, | |
| 'absent': absent, | |
| 'coverage': f"{(present/total*100):.1f}%" | |
| } | |
| def get_missing_personnel(): | |
| """Get list of missing employees""" | |
| df = get_attendance_df() | |
| absent_emps = df[df['status'] == 'absent']['name'].tolist() | |
| late_emps = df[df['status'] == 'late']['name'].tolist() | |
| return absent_emps, late_emps | |
| def live_feed(): | |
| """Gradio interface for live feed""" | |
| return gr.Interface( | |
| fn=process_frame, | |
| inputs=gr.Image(source="webcam", streaming=True, label="Live Feed"), | |
| outputs=gr.Image(label="Detection Results", type="numpy"), | |
| title="πΉ Live Personnel Monitoring", | |
| description="Real-time face detection and recognition", | |
| live=True | |
| ) | |
| def attendance_table(): | |
| """Show attendance table""" | |
| df = get_attendance_df() | |
| return df | |
| def dashboard_view(): | |
| """Dashboard view""" | |
| metrics = get_metrics() | |
| # Create HTML for metrics | |
| html = f""" | |
| <div style="display: flex; gap: 20px; margin: 20px 0; flex-wrap: wrap;"> | |
| <div style="background: #f0f8ff; padding: 20px; border-radius: 10px; flex: 1; min-width: 150px; border-left: 4px solid #00cc66;"> | |
| <h3 style="margin: 0; color: #666;">π₯ Present</h3> | |
| <p style="font-size: 2em; margin: 10px 0; font-weight: bold;">{metrics['present']}</p> | |
| <small style="color: #666;">{metrics['coverage']} coverage</small> | |
| </div> | |
| <div style="background: #fff8f0; padding: 20px; border-radius: 10px; flex: 1; min-width: 150px; border-left: 4px solid #ffa500;"> | |
| <h3 style="margin: 0; color: #666;">β° Late</h3> | |
| <p style="font-size: 2em; margin: 10px 0; font-weight: bold;">{metrics['late']}</p> | |
| </div> | |
| <div style="background: #fff0f0; padding: 20px; border-radius: 10px; flex: 1; min-width: 150px; border-left: 4px solid #ff4444;"> | |
| <h3 style="margin: 0; color: #666;">β Absent</h3> | |
| <p style="font-size: 2em; margin: 10px 0; font-weight: bold;">{metrics['absent']}</p> | |
| </div> | |
| <div style="background: #f0fff0; padding: 20px; border-radius: 10px; flex: 1; min-width: 150px; border-left: 4px solid #4488ff;"> | |
| <h3 style="margin: 0; color: #666;">π Coverage</h3> | |
| <p style="font-size: 2em; margin: 10px 0; font-weight: bold;">{metrics['coverage']}</p> | |
| </div> | |
| </div> | |
| """ | |
| return html | |
| def alerts_view(): | |
| """Alerts and notifications""" | |
| absent_emps, late_emps = get_missing_personnel() | |
| html = "<div style='padding: 20px;'>" | |
| html += "<h2>β οΈ Alerts & Notifications</h2>" | |
| if absent_emps: | |
| html += f""" | |
| <div style="background: #fff0f0; padding: 15px; border-radius: 8px; margin: 10px 0; border-left: 4px solid #ff4444;"> | |
| <h3 style="margin: 0; color: #cc0000;">π¨ {len(absent_emps)} Employee(s) Absent Today</h3> | |
| <ul> | |
| """ | |
| for emp in absent_emps: | |
| html += f"<li>{emp}</li>" | |
| html += "</ul></div>" | |
| if late_emps: | |
| html += f""" | |
| <div style="background: #fff8f0; padding: 15px; border-radius: 8px; margin: 10px 0; border-left: 4px solid #ffa500;"> | |
| <h3 style="margin: 0; color: #cc8800;">β° {len(late_emps)} Employee(s) Arrived Late</h3> | |
| <ul> | |
| """ | |
| for emp in late_emps: | |
| html += f"<li>{emp}</li>" | |
| html += "</ul></div>" | |
| if not absent_emps and not late_emps: | |
| html += """ | |
| <div style="background: #f0fff0; padding: 15px; border-radius: 8px; margin: 10px 0; border-left: 4px solid #00cc66;"> | |
| <h3 style="margin: 0; color: #00aa00;">β All employees present and on time!</h3> | |
| </div> | |
| """ | |
| html += f""" | |
| <div style="margin-top: 20px; display: flex; gap: 10px;"> | |
| <button onclick="alert('π§ Notifications sent!')" style="background: #4488ff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer;"> | |
| π Send Notifications | |
| </button> | |
| <button onclick="alert('π Report generated!')" style="background: #44cc88; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer;"> | |
| π Generate Report | |
| </button> | |
| <span style="background: #f0f0f0; padding: 10px 20px; border-radius: 5px;"> | |
| π {datetime.now().strftime("%H:%M:%S")} | |
| </span> | |
| </div> | |
| """ | |
| html += "</div>" | |
| return html | |
| # Build Gradio Interface | |
| with gr.Blocks(title="AI Personnel Monitoring Dashboard", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown(""" | |
| # π₯ AI-Powered Personnel Monitoring & Attendance Dashboard | |
| Real-time face detection, attendance tracking, and workforce analytics using AI. | |
| """) | |
| # Tabs | |
| with gr.Tabs(): | |
| with gr.TabItem("πΉ Live Feed"): | |
| gr.Markdown("### πΉ Real-time Face Detection") | |
| gr.Markdown("*Note: This demo simulates face detection. In production, this would use YOLO + Face Recognition.*") | |
| # Live feed interface | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| live_output = gr.Image(label="Detection Results", type="numpy") | |
| with gr.Column(scale=1): | |
| live_status = gr.JSON(label="Status Updates") | |
| # Controls | |
| with gr.Row(): | |
| start_btn = gr.Button("βΆοΈ Start Camera", variant="primary") | |
| stop_btn = gr.Button("βΉοΈ Stop Camera", variant="stop") | |
| refresh_btn = gr.Button("π Refresh") | |
| # Process video function | |
| def process_video(): | |
| cap = cv2.VideoCapture(0) | |
| if not cap.isOpened(): | |
| # Use simulated frames | |
| for i in range(30): | |
| # Create simulated frame | |
| frame = np.zeros((480, 640, 3), dtype=np.uint8) | |
| frame[:] = (30, 30, 30) | |
| # Add some face boxes | |
| for j, emp in enumerate(employees[:3]): | |
| x = 100 + j * 180 | |
| y = 150 | |
| cv2.rectangle(frame, (x, y), (x+120, y+160), (0, 255, 0), 2) | |
| cv2.putText(frame, emp['name'].split()[0], (x, y-10), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) | |
| yield frame, {"frame": i, "status": "simulating"} | |
| time.sleep(0.1) | |
| else: | |
| while True: | |
| ret, frame = cap.read() | |
| if not ret: | |
| break | |
| processed = process_frame(frame) | |
| yield processed, {"timestamp": datetime.now().isoformat()} | |
| time.sleep(0.05) | |
| cap.release() | |
| start_btn.click( | |
| process_video, | |
| outputs=[live_output, live_status] | |
| ) | |
| with gr.TabItem("π Dashboard"): | |
| gr.Markdown("### π Real-time Analytics Dashboard") | |
| # Metrics row | |
| metrics_html = gr.HTML() | |
| # Dashboard chart | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| dashboard_plot = gr.Plot(label="Analytics Dashboard") | |
| with gr.Column(scale=1): | |
| metrics_html_2 = gr.HTML() | |
| # Refresh button | |
| refresh_dashboard = gr.Button("π Update Dashboard") | |
| def update_dashboard(): | |
| return create_dashboard(), dashboard_view() | |
| refresh_dashboard.click( | |
| update_dashboard, | |
| outputs=[dashboard_plot, metrics_html] | |
| ) | |
| # Initial load | |
| gr.load(fn=create_dashboard, outputs=dashboard_plot) | |
| gr.load(fn=dashboard_view, outputs=metrics_html) | |
| with gr.TabItem("π Attendance"): | |
| gr.Markdown("### π Today's Attendance Log") | |
| attendance_table = gr.Dataframe( | |
| headers=["ID", "Name", "Department", "Status"], | |
| interactive=False | |
| ) | |
| with gr.Row(): | |
| export_btn = gr.Button("π₯ Export CSV", variant="primary") | |
| filter_btn = gr.Button("π Filter") | |
| def update_attendance(): | |
| df = get_attendance_df() | |
| return df | |
| gr.load(fn=update_attendance, outputs=attendance_table) | |
| with gr.TabItem("β οΈ Alerts"): | |
| gr.Markdown("### β οΈ Missing Personnel Alerts") | |
| alerts_html = gr.HTML() | |
| refresh_alerts = gr.Button("π Check Alerts") | |
| refresh_alerts.click( | |
| alerts_view, | |
| outputs=alerts_html | |
| ) | |
| gr.load(fn=alerts_view, outputs=alerts_html) | |
| with gr.TabItem("π Analytics"): | |
| gr.Markdown("### π Staffing Trends & Analytics") | |
| # Additional analytics | |
| with gr.Row(): | |
| with gr.Column(): | |
| dept_chart = gr.Plot(label="Department Performance") | |
| with gr.Column(): | |
| trend_chart = gr.Plot(label="Attendance Trend") | |
| def create_analytics(): | |
| df = get_attendance_df() | |
| # Department performance | |
| dept_fig = go.Figure() | |
| dept_data = df.groupby('department').size().reset_index(name='total') | |
| dept_present = df[df['status'] == 'present'].groupby('department').size().reset_index(name='present') | |
| dept_merged = dept_data.merge(dept_present, on='department', how='left').fillna(0) | |
| dept_merged['coverage'] = (dept_merged['present'] / dept_merged['total'] * 100).round(1) | |
| dept_fig.add_trace(go.Bar(x=dept_merged['department'], y=dept_merged['coverage'], | |
| text=dept_merged['coverage'], textposition='auto', | |
| marker_color=dept_merged['coverage'].apply( | |
| lambda x: '#00cc66' if x >= 80 else '#ffa500' if x >= 50 else '#ff4444' | |
| ))) | |
| dept_fig.update_layout(title="Department Coverage %", yaxis_title="Coverage %") | |
| # Trend chart | |
| trend_fig = go.Figure() | |
| dates = pd.date_range(end=datetime.now(), periods=7) | |
| for status in ['present', 'late', 'absent']: | |
| trend_fig.add_trace(go.Scatter( | |
| x=dates, | |
| y=[random.randint(2, 8) for _ in range(7)], | |
| name=status.capitalize(), | |
| mode='lines+markers' | |
| )) | |
| trend_fig.update_layout(title="7-Day Attendance Trend", yaxis_title="Employees") | |
| return dept_fig, trend_fig | |
| gr.load(fn=create_analytics, outputs=[dept_chart, trend_chart]) | |
| # Footer | |
| gr.Markdown(""" | |
| --- | |
| ### π― About This Demo | |
| This is a **simulated demonstration** of an AI-powered attendance system. | |
| The live feed uses simulated face detection to showcase the dashboard interface. | |
| **Real implementation features:** | |
| - β YOLOv8 for face detection | |
| - β FaceNet/ArcFace for recognition | |
| - β PostgreSQL attendance database | |
| - β Real-time analytics and alerts | |
| - β Shift coverage and staffing trends | |
| Built with β€οΈ using Gradio, OpenCV, and Plotly | |
| """) | |
| # Launch the app | |
| if __name__ == "__main__": | |
| demo.launch() |