diff --git a/app/routes/public.py b/app/routes/public.py index bbf5aaa..bb91f8e 100644 --- a/app/routes/public.py +++ b/app/routes/public.py @@ -62,12 +62,19 @@ def _rating_label(score): def _build_summary(facility: Facility) -> dict: - """Assemble the occupant-facing summary for a facility.""" + """Assemble the occupant-facing summary for a facility. + + Occupant-safe (rule 74): aggregate rating, counts, a score trend, and + recent inspection DATES only — no checklist/template names, no + per-inspection scores, no issue descriptions, and no severity/SLA detail. + """ fid = facility.id now = now_eastern() - cutoff = now - timedelta(days=90) + cutoff_90 = now - timedelta(days=90) + cutoff_30 = now - timedelta(days=30) + cutoff_60 = now - timedelta(days=60) - # Most recent completed, scored inspection + # Most recent completed, scored inspection (for last-inspected date) last_insp = ( Inspection.query .filter(Inspection.facility_id == fid, @@ -77,15 +84,18 @@ def _build_summary(facility: Facility) -> dict: .first() ) + def _avg_between(start, end=None): + q = (db.session.query(func.avg(Inspection.overall_score)) + .filter(Inspection.facility_id == fid, + Inspection.status == 'completed', + Inspection.overall_score.isnot(None), + Inspection.inspection_date >= start)) + if end is not None: + q = q.filter(Inspection.inspection_date < end) + return q.scalar() + # Average score over the last 90 days (fallback: all-time) for the rating - avg_90 = ( - db.session.query(func.avg(Inspection.overall_score)) - .filter(Inspection.facility_id == fid, - Inspection.status == 'completed', - Inspection.overall_score.isnot(None), - Inspection.inspection_date >= cutoff) - .scalar() - ) + avg_90 = _avg_between(cutoff_90) if avg_90 is None: avg_90 = ( db.session.query(func.avg(Inspection.overall_score)) @@ -96,6 +106,34 @@ def _build_summary(facility: Facility) -> dict: ) avg_score = round(float(avg_90), 1) if avg_90 is not None else None + # Completed-inspection count over the last 90 days + inspections_90 = ( + Inspection.query + .filter(Inspection.facility_id == fid, + Inspection.status == 'completed', + Inspection.inspection_date >= cutoff_90) + .count() + ) + + # Score trend: last 30 days vs the prior 30 days (aggregate only) + avg_cur = _avg_between(cutoff_30) + avg_prior = _avg_between(cutoff_60, cutoff_30) + if avg_cur is not None and avg_prior is not None: + trend_delta = round(float(avg_cur) - float(avg_prior), 1) + else: + trend_delta = None + + # Recent inspection DATES only (no checklist names, no scores) + recent = ( + Inspection.query + .filter(Inspection.facility_id == fid, + Inspection.status == 'completed') + .order_by(Inspection.inspection_date.desc()) + .limit(5) + .all() + ) + recent_dates = [i.inspection_date for i in recent] + # Open-issue COUNT (linked directly or via an area) — no details exposed open_issue_count = ( Issue.query @@ -105,6 +143,17 @@ def _build_summary(facility: Facility) -> dict: .count() ) + # Resolved-issue COUNT over the last 90 days — no details exposed + resolved_90 = ( + Issue.query + .outerjoin(Area, Issue.area_id == Area.id) + .filter(Issue.status == 'resolved', + Issue.resolved_at.isnot(None), + Issue.resolved_at >= cutoff_90, + db.or_(Issue.facility_id == fid, Area.facility_id == fid)) + .count() + ) + label, colour = _rating_label(avg_score) return { @@ -112,10 +161,12 @@ def _build_summary(facility: Facility) -> dict: 'avg_score': avg_score, 'rating_label': label, 'rating_colour': colour, - 'last_inspected': last_insp.inspection_date if last_insp else None, - 'last_score': (round(float(last_insp.overall_score), 1) - if last_insp and last_insp.overall_score is not None else None), + 'inspections_90': inspections_90, 'open_issue_count': open_issue_count, + 'resolved_90': resolved_90, + 'trend_delta': trend_delta, + 'last_inspected': last_insp.inspection_date if last_insp else None, + 'recent_dates': recent_dates, } diff --git a/app/templates/public/facility.html b/app/templates/public/facility.html index e912a1f..efc2769 100644 --- a/app/templates/public/facility.html +++ b/app/templates/public/facility.html @@ -8,11 +8,18 @@ @@ -27,49 +34,91 @@ {% endwith %} {# ── Header ── #} -
-
Facility Status
-

{{ facility.name }}

- {% if facility.address %} -
{{ facility.address }}
- {% endif %} -
- - {# ── Quality rating ── #} -
-
Cleaning Quality
- {{ rating_label }} -
- {{ ('%.0f' % avg_score) ~ '%' if avg_score is not none else '—' }} +
+
+
+

{{ facility.name }}

+
+ {% if facility.project %}{{ facility.project.name }}{% endif %} + {% if facility.project and facility.address %} · {% endif %} + {% if facility.address %}{{ facility.address }}{% endif %} +
-
Average score, last 90 days
- {# ── Facts ── #} + {# ── KPI row ── #}
-
-
-
Last Inspected
-
- {{ last_inspected.strftime('%b %d, %Y') if last_inspected else 'Not yet' }} +
+
+
+ {{ ('%.1f' % avg_score) ~ '%' if avg_score is not none else '—' }}
- {% if last_score is not none %} -
Scored {{ '%.0f' % last_score }}%
- {% endif %} +
Avg Score
+
90 Days
-
-
-
Open Issues
-
{{ open_issue_count }}
-
Currently being tracked
+
+
+
{{ inspections_90 }}
+
Inspections
+
90 Days
+
+
+
+
+
{{ open_issue_count }}
+
Open
+
Issues
+
+
+
+
+
{{ resolved_90 }}
+
Resolved
+
90 Days
+ {# ── Score trend (aggregate only) ── #} +
+
Score Trend — 30 Days vs Prior 30
+
+ {% if trend_delta is none %} + Not enough data yet + {% elif trend_delta > 0 %} + +{{ '%.1f' % trend_delta }} pts + {% elif trend_delta < 0 %} + {{ '%.1f' % trend_delta }} pts + {% else %} + No change + {% endif %} +
+
+ + {# ── Cleaning quality rating ── #} +
+
Cleaning Quality
+ {{ rating_label }} +
+ + {# ── Recent inspections (DATES ONLY — occupant-safe) ── #} + {% if recent_dates %} +
+
Recent Inspections
+ {% for d in recent_dates %} +
+ {{ d.strftime('%b %d, %Y') }} + Completed +
+ {% endfor %} +
This facility is inspected regularly by our quality team.
+
+ {% endif %} + {# ── Report a problem ── #} -
-

Report a Problem

+
+

Report a Problem

Notice something that needs attention? Let the cleaning team know.