06/08 Optimize app

This commit is contained in:
2026-06-08 15:07:05 -04:00
parent 948ab111f6
commit 1f0bf2e586
2 changed files with 64 additions and 4 deletions
+41 -3
View File
@@ -6,7 +6,7 @@ from app.utils.time_utils import now_eastern
from flask import (Blueprint, render_template, request, from flask import (Blueprint, render_template, request,
Response, stream_with_context, abort) Response, stream_with_context, abort)
from flask_login import login_required, current_user from flask_login import login_required, current_user
from sqlalchemy import func from sqlalchemy import func, literal_column
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from app import db from app import db
from app.models.inspection import Inspection, InspectionTemplate from app.models.inspection import Inspection, InspectionTemplate
@@ -22,6 +22,16 @@ bp = Blueprint('reports', __name__, url_prefix='/reports')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def _fmt_minutes(mins):
"""Format an integer minute value as '45m' or '1h 30m'. Returns None if input is None."""
if mins is None:
return None
mins = int(round(float(mins)))
if mins >= 60:
return f'{mins // 60}h {mins % 60}m'
return f'{mins}m'
def _date_range(): def _date_range():
"""Parse ?start= and ?end= query params; default to last 30 days.""" """Parse ?start= and ?end= query params; default to last 30 days."""
end_default = now_eastern() end_default = now_eastern()
@@ -589,10 +599,18 @@ def inspector_performance():
Inspection.inspector_id, Inspection.inspector_id,
func.count(Inspection.id).label('completed'), func.count(Inspection.id).label('completed'),
func.avg(Inspection.overall_score).label('avg_score'), func.avg(Inspection.overall_score).label('avg_score'),
func.avg(
func.timestampdiff(
literal_column('MINUTE'),
Inspection.inspection_date,
Inspection.completed_at,
)
).label('avg_minutes'),
).filter( ).filter(
Inspection.inspection_date >= start, Inspection.inspection_date >= start,
Inspection.inspection_date <= end, Inspection.inspection_date <= end,
Inspection.status == 'completed', Inspection.status == 'completed',
Inspection.completed_at.isnot(None),
).group_by(Inspection.inspector_id).all() ).group_by(Inspection.inspector_id).all()
issue_rows = db.session.query( issue_rows = db.session.query(
@@ -614,7 +632,14 @@ def inspector_performance():
).group_by(Inspection.inspector_id).all() ).group_by(Inspection.inspector_id).all()
total_map = {r.inspector_id: {'total': r.total, 'facilities': r.facilities} for r in total_rows} 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} completed_map = {
r.inspector_id: {
'completed': r.completed,
'avg_score': r.avg_score,
'avg_minutes': r.avg_minutes,
}
for r in completed_rows
}
issues_map = {r.inspector_id: r.count for r in issue_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} followups_map = {r.inspector_id: r.count for r in followup_rows}
@@ -630,9 +655,10 @@ def inspector_performance():
for u in active_inspectors: for u in active_inspectors:
t = total_map.get(u.id, {}) t = total_map.get(u.id, {})
c = completed_map.get(u.id, {}) c = completed_map.get(u.id, {})
tot = t.get('total', 0) tot = t.get('total', 0)
comp = c.get('completed', 0) comp = c.get('completed', 0)
avg = round(float(c['avg_score']), 1) if c.get('avg_score') else None avg = round(float(c['avg_score']), 1) if c.get('avg_score') else None
avg_mins = round(float(c['avg_minutes'])) if c.get('avg_minutes') else None
inspector_stats.append({ inspector_stats.append({
'id': u.id, 'id': u.id,
'display_name': u.display_name, 'display_name': u.display_name,
@@ -640,6 +666,8 @@ def inspector_performance():
'completed': comp, 'completed': comp,
'completion_rate': round(comp / tot * 100) if tot else 0, 'completion_rate': round(comp / tot * 100) if tot else 0,
'avg_score': avg, 'avg_score': avg,
'avg_minutes': avg_mins,
'avg_time': _fmt_minutes(avg_mins),
'issues_flagged': issues_map.get(u.id, 0), 'issues_flagged': issues_map.get(u.id, 0),
'follow_ups': followups_map.get(u.id, 0), 'follow_ups': followups_map.get(u.id, 0),
'facilities': t.get('facilities', 0), 'facilities': t.get('facilities', 0),
@@ -647,6 +675,15 @@ def inspector_performance():
inspector_stats.sort(key=lambda x: (x['avg_score'] is None, -(x['avg_score'] or 0))) inspector_stats.sort(key=lambda x: (x['avg_score'] is None, -(x['avg_score'] or 0)))
# Team average score for benchmarking (vs. avg column)
_scores = [s['avg_score'] for s in inspector_stats if s['avg_score'] is not None]
team_avg_score = round(sum(_scores) / len(_scores), 1) if _scores else None
for s in inspector_stats:
if s['avg_score'] is not None and team_avg_score is not None:
s['vs_avg'] = round(s['avg_score'] - team_avg_score, 1)
else:
s['vs_avg'] = None
# ── Individual drill-down ───────────────────────────────────────────── # ── Individual drill-down ─────────────────────────────────────────────
selected_inspector = None selected_inspector = None
selected_kpis = None selected_kpis = None
@@ -696,6 +733,7 @@ def inspector_performance():
return render_template('reports/inspector_performance.html', return render_template('reports/inspector_performance.html',
start=start, end=end, start=start, end=end,
inspector_stats=inspector_stats, inspector_stats=inspector_stats,
team_avg_score=team_avg_score,
selected_inspector=selected_inspector, selected_inspector=selected_inspector,
selected_kpis=selected_kpis, selected_kpis=selected_kpis,
trend_data=trend_data, trend_data=trend_data,
@@ -100,6 +100,11 @@
<th class="text-center">Completed</th> <th class="text-center">Completed</th>
<th class="text-center">Completion Rate</th> <th class="text-center">Completion Rate</th>
<th class="text-center">Avg Score</th> <th class="text-center">Avg Score</th>
<th class="text-center">
vs. Avg
{% if team_avg_score %}<span class="text-muted fw-normal small">({{ '%.1f'|format(team_avg_score) }}%)</span>{% endif %}
</th>
<th class="text-center">Avg Time</th>
<th class="text-center">Issues Flagged</th> <th class="text-center">Issues Flagged</th>
<th class="text-center">Follow-ups</th> <th class="text-center">Follow-ups</th>
<th class="text-center">Facilities</th> <th class="text-center">Facilities</th>
@@ -133,6 +138,22 @@
<span class="text-muted"></span> <span class="text-muted"></span>
{% endif %} {% endif %}
</td> </td>
<td class="text-center">
{% if s.vs_avg is not none %}
{% if s.vs_avg > 0 %}
<span class="badge bg-success">+{{ '%.1f'|format(s.vs_avg) }}%</span>
{% elif s.vs_avg < 0 %}
<span class="badge bg-danger">{{ '%.1f'|format(s.vs_avg) }}%</span>
{% else %}
<span class="badge bg-secondary">0.0%</span>
{% endif %}
{% else %}
<span class="text-muted"></span>
{% endif %}
</td>
<td class="text-center">
<span class="small text-muted">{{ s.avg_time or '—' }}</span>
</td>
<td class="text-center"> <td class="text-center">
{% if s.issues_flagged > 0 %} {% if s.issues_flagged > 0 %}
<span class="badge bg-danger">{{ s.issues_flagged }}</span> <span class="badge bg-danger">{{ s.issues_flagged }}</span>
@@ -184,11 +205,12 @@
('Total Inspections', selected_kpis.total, 'primary', 'bi-clipboard-data'), ('Total Inspections', selected_kpis.total, 'primary', 'bi-clipboard-data'),
('Completed', selected_kpis.completed, 'success', 'bi-check-circle'), ('Completed', selected_kpis.completed, 'success', 'bi-check-circle'),
('Avg Score', (('%.1f'|format(selected_kpis.avg_score)) + '%') if selected_kpis.avg_score else '—', 'info', 'bi-graph-up'), ('Avg Score', (('%.1f'|format(selected_kpis.avg_score)) + '%') if selected_kpis.avg_score else '—', 'info', 'bi-graph-up'),
('Avg Completion Time',selected_kpis.avg_time or '—', 'info', 'bi-stopwatch'),
('Issues Flagged', selected_kpis.issues_flagged, 'danger', 'bi-flag'), ('Issues Flagged', selected_kpis.issues_flagged, 'danger', 'bi-flag'),
('Follow-ups Req.', selected_kpis.follow_ups, 'warning', 'bi-arrow-repeat'), ('Follow-ups Req.', selected_kpis.follow_ups, 'warning', 'bi-arrow-repeat'),
('Facilities Covered', selected_kpis.facilities, 'primary', 'bi-building'), ('Facilities Covered', selected_kpis.facilities, 'primary', 'bi-building'),
] %} ] %}
<div class="col-6 col-md-4 col-lg-2"> <div class="col-6 col-md-4 col-lg-3 col-xl-auto" style="min-width:130px;">
<div class="card shadow-sm stat-card {{ color }} h-100"> <div class="card shadow-sm stat-card {{ color }} h-100">
<div class="card-body py-2 px-3"> <div class="card-body py-2 px-3">
<p class="text-muted small mb-1">{{ label }}</p> <p class="text-muted small mb-1">{{ label }}</p>