netra / backend /app /api /v1 /analytics.py
093xpku
Clean project layout deployment
9bc686b
Raw
History Blame Contribute Delete
6.23 kB
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session
from sqlalchemy import select, func, and_, desc
from datetime import date, datetime, timedelta
from typing import Dict, Any, List
import asyncio
import json
from app.core.database import get_db
from app.core import security
from app.core.security import RoleCheckerSSE
from app.core import event_bus
from app.crud import crud
from app.schemas import schemas
from app.models import models
router = APIRouter()
checker_view = security.RoleChecker(["Super Admin", "Admin", "HR"])
@router.get("/dashboard-summary")
def get_dashboard_summary(
db: Session = Depends(get_db),
current_user: models.User = Depends(checker_view)
):
today = date.today()
# 1. Total Employees
total_employees = db.query(func.count(models.Employee.id)).filter(models.Employee.status == "Active").scalar() or 0
# 2. Present Today (Status = Present or Late or Half Day)
present_today = db.query(func.count(models.Attendance.id)).filter(
and_(
models.Attendance.date == today,
models.Attendance.status.in_(["Present", "Late", "Half Day"])
)
).scalar() or 0
# 3. Late Today
late_today = db.query(func.count(models.Attendance.id)).filter(
and_(
models.Attendance.date == today,
models.Attendance.status == "Late"
)
).scalar() or 0
# 4. Checked Out
checked_out_today = db.query(func.count(models.Attendance.id)).filter(
and_(
models.Attendance.date == today,
models.Attendance.check_out.isnot(None)
)
).scalar() or 0
# 5. Absent Today
# Active employees minus present today
absent_today = max(0, total_employees - present_today)
# 6. Attendance Rate
attendance_percentage = (present_today / total_employees * 100) if total_employees > 0 else 0.0
return {
"total_employees": total_employees,
"present_today": present_today,
"absent_today": absent_today,
"late_today": late_today,
"checked_out_today": checked_out_today,
"attendance_percentage": round(attendance_percentage, 1)
}
@router.get("/attendance-trends")
def get_attendance_trends(
days: int = 7,
db: Session = Depends(get_db),
current_user: models.User = Depends(checker_view)
):
"""
Returns daily stats for the last N days (date, present, late, absent) for ECharts.
"""
end_date = date.today()
start_date = end_date - timedelta(days=days - 1)
# Generate list of dates
date_list = [start_date + timedelta(days=i) for i in range(days)]
# Get total active employees
total_active = db.query(func.count(models.Employee.id)).filter(models.Employee.status == "Active").scalar() or 0
trends = []
for d in date_list:
present = db.query(func.count(models.Attendance.id)).filter(
and_(
models.Attendance.date == d,
models.Attendance.status.in_(["Present", "Late", "Half Day"])
)
).scalar() or 0
late = db.query(func.count(models.Attendance.id)).filter(
and_(
models.Attendance.date == d,
models.Attendance.status == "Late"
)
).scalar() or 0
absent = max(0, total_active - present)
trends.append({
"date": d.isoformat(),
"present": present,
"late": late,
"absent": absent
})
return trends
@router.get("/department-distribution")
def get_department_distribution(
db: Session = Depends(get_db),
current_user: models.User = Depends(checker_view)
):
"""
Returns employee and attendance counts by department for ECharts.
"""
today = date.today()
departments = db.query(models.Department).all()
dist = []
for dept in departments:
# Total active in dept
total_in_dept = db.query(func.count(models.Employee.id)).filter(
and_(
models.Employee.department_id == dept.id,
models.Employee.status == "Active"
)
).scalar() or 0
# Present today in dept
present_in_dept = db.query(func.count(models.Attendance.id)).join(models.Employee).filter(
and_(
models.Employee.department_id == dept.id,
models.Attendance.date == today,
models.Attendance.status.in_(["Present", "Late", "Half Day"])
)
).scalar() or 0
dist.append({
"id": dept.id,
"department": dept.name,
"code": dept.code,
"total_employees": total_in_dept,
"present_today": present_in_dept
})
return dist
@router.get("/recent-activity", response_model=List[schemas.AttendanceLogOut])
def get_recent_activity(
limit: int = 10,
db: Session = Depends(get_db),
current_user: models.User = Depends(checker_view)
):
"""
Returns latest kiosk scan logs for the dashboard tickfeed.
"""
cutoff = datetime.utcnow() - timedelta(hours=24)
return db.query(models.AttendanceLog).filter(models.AttendanceLog.timestamp >= cutoff).order_by(desc(models.AttendanceLog.timestamp)).limit(limit).all()
@router.get("/live-stream")
async def live_stream(
current_user: models.User = Depends(RoleCheckerSSE(["Super Admin", "Admin", "HR"]))
):
"""
Real-time Server-Sent Events (SSE) feed of kiosk scans.
"""
async def event_generator():
queue = event_bus.subscribe()
try:
while True:
# Wait for next event published to the bus
event_data = await queue.get()
# Yield standard SSE formatted message
yield f"data: {json.dumps(event_data)}\n\n"
except asyncio.CancelledError:
# Client disconnected
pass
finally:
event_bus.unsubscribe(queue)
return StreamingResponse(event_generator(), media_type="text/event-stream")