06/08 Optimize app
This commit is contained in:
+40
-2
@@ -6,7 +6,7 @@ from app.utils.time_utils import now_eastern
|
||||
from flask import (Blueprint, render_template, request,
|
||||
Response, stream_with_context, abort)
|
||||
from flask_login import login_required, current_user
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy import func, literal_column
|
||||
from sqlalchemy.orm import joinedload
|
||||
from app import db
|
||||
from app.models.inspection import Inspection, InspectionTemplate
|
||||
@@ -22,6 +22,16 @@ bp = Blueprint('reports', __name__, url_prefix='/reports')
|
||||
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():
|
||||
"""Parse ?start= and ?end= query params; default to last 30 days."""
|
||||
end_default = now_eastern()
|
||||
@@ -589,10 +599,18 @@ def inspector_performance():
|
||||
Inspection.inspector_id,
|
||||
func.count(Inspection.id).label('completed'),
|
||||
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(
|
||||
Inspection.inspection_date >= start,
|
||||
Inspection.inspection_date <= end,
|
||||
Inspection.status == 'completed',
|
||||
Inspection.completed_at.isnot(None),
|
||||
).group_by(Inspection.inspector_id).all()
|
||||
|
||||
issue_rows = db.session.query(
|
||||
@@ -614,7 +632,14 @@ def inspector_performance():
|
||||
).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}
|
||||
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}
|
||||
followups_map = {r.inspector_id: r.count for r in followup_rows}
|
||||
|
||||
@@ -633,6 +658,7 @@ def inspector_performance():
|
||||
tot = t.get('total', 0)
|
||||
comp = c.get('completed', 0)
|
||||
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({
|
||||
'id': u.id,
|
||||
'display_name': u.display_name,
|
||||
@@ -640,6 +666,8 @@ def inspector_performance():
|
||||
'completed': comp,
|
||||
'completion_rate': round(comp / tot * 100) if tot else 0,
|
||||
'avg_score': avg,
|
||||
'avg_minutes': avg_mins,
|
||||
'avg_time': _fmt_minutes(avg_mins),
|
||||
'issues_flagged': issues_map.get(u.id, 0),
|
||||
'follow_ups': followups_map.get(u.id, 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)))
|
||||
|
||||
# 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 ─────────────────────────────────────────────
|
||||
selected_inspector = None
|
||||
selected_kpis = None
|
||||
@@ -696,6 +733,7 @@ def inspector_performance():
|
||||
return render_template('reports/inspector_performance.html',
|
||||
start=start, end=end,
|
||||
inspector_stats=inspector_stats,
|
||||
team_avg_score=team_avg_score,
|
||||
selected_inspector=selected_inspector,
|
||||
selected_kpis=selected_kpis,
|
||||
trend_data=trend_data,
|
||||
|
||||
@@ -100,6 +100,11 @@
|
||||
<th class="text-center">Completed</th>
|
||||
<th class="text-center">Completion Rate</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">Follow-ups</th>
|
||||
<th class="text-center">Facilities</th>
|
||||
@@ -133,6 +138,22 @@
|
||||
<span class="text-muted">—</span>
|
||||
{% endif %}
|
||||
</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">
|
||||
{% if s.issues_flagged > 0 %}
|
||||
<span class="badge bg-danger">{{ s.issues_flagged }}</span>
|
||||
@@ -184,11 +205,12 @@
|
||||
('Total Inspections', selected_kpis.total, 'primary', 'bi-clipboard-data'),
|
||||
('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 Completion Time',selected_kpis.avg_time or '—', 'info', 'bi-stopwatch'),
|
||||
('Issues Flagged', selected_kpis.issues_flagged, 'danger', 'bi-flag'),
|
||||
('Follow-ups Req.', selected_kpis.follow_ups, 'warning', 'bi-arrow-repeat'),
|
||||
('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-body py-2 px-3">
|
||||
<p class="text-muted small mb-1">{{ label }}</p>
|
||||
|
||||
Reference in New Issue
Block a user