From 253291d5a41048a367ecbdef2ea1a344fa136dbb Mon Sep 17 00:00:00 2001 From: NguyenND Date: Thu, 16 Jul 2026 17:28:10 -0400 Subject: [PATCH] Jul 16 - Fill the gaps between Single-tenant mode and Multi-tenant mode - MT6 --- app/routes/reports.py | 28 +++++++++++++-- app/templates/reports/index.html | 62 +++++++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 11 deletions(-) diff --git a/app/routes/reports.py b/app/routes/reports.py index a06752b..362da50 100644 --- a/app/routes/reports.py +++ b/app/routes/reports.py @@ -142,9 +142,11 @@ def index(): ) avg_score = _scope_insp(avg_score).scalar() - # Scores by facility (for bar chart) + # Scores by facility (for bar chart) — includes project_id so the report + # can group/filter facilities by Contract client-side. fac_score_q = db.session.query( Facility.name, + Facility.project_id, func.avg(Inspection.overall_score).label('avg_score'), func.count(Inspection.id).label('count'), ).join(Inspection, Facility.id == Inspection.facility_id)\ @@ -160,9 +162,16 @@ def index(): fac_score_q = fac_score_q.filter( Facility.id.in_(customer_facility_ids) if customer_facility_ids else False ) - facility_scores = fac_score_q.group_by(Facility.id, Facility.name)\ + facility_scores = fac_score_q.group_by(Facility.id, Facility.name, Facility.project_id)\ .order_by(func.avg(Inspection.overall_score).desc()).all() + # Resolve contract names for the facilities present. + _proj_ids = {r.project_id for r in facility_scores if r.project_id} + _proj_names = ( + {p.id: p.name for p in Project.query.filter(Project.id.in_(_proj_ids)).all()} + if _proj_ids else {} + ) + # Prior-period facility scores for period-over-period delta badges period_len = end - start prior_end = start @@ -256,12 +265,24 @@ def index(): inspectors = User.query.filter_by(role='inspector', active=True)\ .order_by(User.full_name, User.username).all() - facility_scores_list = [{'name': r.name, 'avg_score': round(float(r.avg_score), 2), 'count': r.count} for r in facility_scores] + facility_scores_list = [{ + 'name': r.name, + 'avg_score': round(float(r.avg_score), 2), + 'count': r.count, + 'project_id': r.project_id or 0, + 'contract': _proj_names.get(r.project_id, 'No Contract'), + } for r in facility_scores] # Attach prior avg and delta to each facility score dict for the template table for row in facility_scores_list: row['prior_avg'] = prior_scores_map.get(row['name']) row['delta'] = facility_deltas.get(row['name']) + # Distinct contracts present, for the "Avg Score by Facility" contract filter. + score_contracts = sorted( + {(r['project_id'], r['contract']) for r in facility_scores_list}, + key=lambda t: (t[1] or '').lower(), + ) + return render_template('reports/index.html', start=start, end=end, total_inspections=total_inspections, @@ -269,6 +290,7 @@ def index(): flagged=flagged, avg_score=round(float(avg_score), 2) if avg_score else None, facility_scores=facility_scores_list, + score_contracts=score_contracts, daily_scores=[{'day': str(r.day), 'avg': round(float(r.avg), 2), 'count': r.count} for r in daily_scores], issue_severity=[{'severity': r.severity, 'count': r.count} for r in issue_severity], issue_status=[{'status': r.status, 'count': r.count} for r in issue_status], diff --git a/app/templates/reports/index.html b/app/templates/reports/index.html index 83154f6..ee0435c 100644 --- a/app/templates/reports/index.html +++ b/app/templates/reports/index.html @@ -105,7 +105,17 @@
-
Avg Score by Facility
+
+
Avg Score by Facility
+ {% if score_contracts %} + + {% endif %} +
@@ -129,6 +139,7 @@ Facility + Contract Current Period Prior Period Change @@ -137,8 +148,9 @@ {% for row in facility_scores %} - + {{ row.name }} + {{ row.contract }} {{ '%.1f'|format(row.avg_score|float) }}% @@ -266,16 +278,31 @@ new Chart(document.getElementById('trendChart'), { } }); -// ── Facility bar chart ──────────────────────────────────────────────────────── -new Chart(document.getElementById('facilityChart'), { +// ── Facility bar chart (filterable by contract) ─────────────────────────────── +const FACILITY_SCORES = {{ facility_scores | tojson }}; +let facilityChartObj = null; +function _facColors(data) { return data.map(s => s >= 90 ? GREEN : s >= 70 ? AMBER : RED); } +function renderFacilityChart(pid) { + const rows = (!pid) + ? FACILITY_SCORES + : FACILITY_SCORES.filter(r => String(r.project_id) === String(pid)); + const labels = rows.map(r => r.name); + const data = rows.map(r => r.avg_score); + if (facilityChartObj) { + facilityChartObj.data.labels = labels; + facilityChartObj.data.datasets[0].data = data; + facilityChartObj.data.datasets[0].backgroundColor = _facColors(data); + facilityChartObj.update(); + return; + } + facilityChartObj = new Chart(document.getElementById('facilityChart'), { type: 'bar', data: { - labels: {{ facility_scores | map(attribute='name') | list | tojson }}, + labels: labels, datasets: [{ label: 'Avg Score (%)', - data: {{ facility_scores | map(attribute='avg_score') | list | tojson }}, - backgroundColor: {{ facility_scores | map(attribute='avg_score') | list | tojson }} - .map(s => s >= 90 ? GREEN : s >= 70 ? AMBER : RED), + data: data, + backgroundColor: _facColors(data), borderRadius: 4, }] }, @@ -285,6 +312,25 @@ new Chart(document.getElementById('facilityChart'), { plugins: { legend: { display: false } } } }); +} + +function filterFacilityScoreTable(pid) { + document.querySelectorAll('.facility-score-row').forEach(function (tr) { + const rp = tr.getAttribute('data-project-id'); + tr.style.display = (!pid || rp === String(pid)) ? '' : 'none'; + }); +} + +(function () { + renderFacilityChart(''); + const sel = document.getElementById('scoreContractFilter'); + if (sel) { + sel.addEventListener('change', function () { + renderFacilityChart(this.value); + filterFacilityScoreTable(this.value); + }); + } +}()); // ── Severity doughnut ───────────────────────────────────────────────────────── const sevData = {{ issue_severity | tojson }};