06/08 Add inspectors' KPI, performance

This commit is contained in:
2026-06-08 14:16:28 -04:00
parent d4c326483c
commit 00266eb32a
4 changed files with 525 additions and 0 deletions
+134
View File
@@ -561,4 +561,138 @@ def export_issues():
stream_with_context(generate()),
mimetype='text/csv',
headers={'Content-Disposition': f'attachment; filename="{filename}"'}
)
# ── Inspector Performance ─────────────────────────────────────────────────────
@bp.route('/inspector-performance')
@login_required
@supervisor_required
def inspector_performance():
"""KPI & performance dashboard for all inspectors — admin/director only."""
start, end = _date_range()
selected_id = request.args.get('inspector_id', type=int)
# ── Per-inspector aggregate stats ─────────────────────────────────────
total_rows = db.session.query(
Inspection.inspector_id,
func.count(Inspection.id).label('total'),
func.count(func.distinct(Inspection.facility_id)).label('facilities'),
).filter(
Inspection.inspection_date >= start,
Inspection.inspection_date <= end,
).group_by(Inspection.inspector_id).all()
completed_rows = db.session.query(
Inspection.inspector_id,
func.count(Inspection.id).label('completed'),
func.avg(Inspection.overall_score).label('avg_score'),
).filter(
Inspection.inspection_date >= start,
Inspection.inspection_date <= end,
Inspection.status == 'completed',
).group_by(Inspection.inspector_id).all()
issue_rows = db.session.query(
Inspection.inspector_id,
func.count(Issue.id).label('count'),
).join(Issue, Issue.inspection_id == Inspection.id)\
.filter(
Inspection.inspection_date >= start,
Inspection.inspection_date <= end,
).group_by(Inspection.inspector_id).all()
followup_rows = db.session.query(
Inspection.inspector_id,
func.count(Inspection.id).label('count'),
).filter(
Inspection.inspection_date >= start,
Inspection.inspection_date <= end,
Inspection.follow_up_required == True,
).group_by(Inspection.inspector_id).all()
total_map = {r.inspector_id: {'total': r.total, 'facilities': r.facilities} for r in total_rows}
completed_map = {r.inspector_id: {'completed': r.completed, 'avg_score': r.avg_score} for r in completed_rows}
issues_map = {r.inspector_id: r.count for r in issue_rows}
followups_map = {r.inspector_id: r.count for r in followup_rows}
all_ids = set(total_map.keys())
active_inspectors = (
User.query
.filter(User.id.in_(all_ids), User.active == True, User.role == 'inspector')
.order_by(User.full_name, User.username)
.all()
) if all_ids else []
inspector_stats = []
for u in active_inspectors:
t = total_map.get(u.id, {})
c = completed_map.get(u.id, {})
tot = t.get('total', 0)
comp = c.get('completed', 0)
avg = round(float(c['avg_score']), 1) if c.get('avg_score') else None
inspector_stats.append({
'id': u.id,
'display_name': u.display_name,
'total': tot,
'completed': comp,
'completion_rate': round(comp / tot * 100) if tot else 0,
'avg_score': avg,
'issues_flagged': issues_map.get(u.id, 0),
'follow_ups': followups_map.get(u.id, 0),
'facilities': t.get('facilities', 0),
})
inspector_stats.sort(key=lambda x: (x['avg_score'] is None, -(x['avg_score'] or 0)))
# ── Individual drill-down ─────────────────────────────────────────────
selected_inspector = None
selected_kpis = None
trend_data = []
recent_inspections = []
if selected_id:
selected_inspector = db.session.get(User, selected_id)
if selected_inspector and selected_inspector.role == 'inspector':
selected_kpis = next((s for s in inspector_stats if s['id'] == selected_id), None)
trend_rows = db.session.query(
func.date(Inspection.inspection_date).label('day'),
func.avg(Inspection.overall_score).label('avg'),
func.count(Inspection.id).label('count'),
).filter(
Inspection.inspector_id == selected_id,
Inspection.inspection_date >= start,
Inspection.inspection_date <= end,
Inspection.status == 'completed',
Inspection.overall_score.isnot(None),
).group_by(func.date(Inspection.inspection_date))\
.order_by(func.date(Inspection.inspection_date)).all()
trend_data = [
{'day': str(r.day), 'avg': round(float(r.avg), 2), 'count': r.count}
for r in trend_rows
]
recent_inspections = (
Inspection.query
.filter(
Inspection.inspector_id == selected_id,
Inspection.inspection_date >= start,
Inspection.inspection_date <= end,
)
.order_by(Inspection.inspection_date.desc())
.limit(20)
.all()
)
return render_template('reports/inspector_performance.html',
start=start, end=end,
inspector_stats=inspector_stats,
selected_inspector=selected_inspector,
selected_kpis=selected_kpis,
trend_data=trend_data,
recent_inspections=recent_inspections,
selected_id=selected_id,
)