Mar 05 2026: implement reinspect comparisio fix

This commit is contained in:
2026-03-05 10:01:51 -05:00
parent 120134a769
commit b234e5c4be
+42 -29
View File
@@ -496,54 +496,67 @@ def view(inspection_id):
issues = inspection.issues.order_by(Issue.reported_at.desc()).all()
# ── Score comparison against parent inspection ────────────────────────
# Produces a list of per-item dicts so the template can render a
# side-by-side delta table without any additional queries at render time.
# Scores live in form_data (field_id -> value) inside inspection.notes,
# NOT in InspectionResult checklist rows. We walk the shared form_fields
# schema and compare current vs. parent responses per scoreable field.
comparison = None
if inspection.parent and inspection.parent.status == 'completed':
parent = inspection.parent
# Index parent results by checklist_item_id
parent_results = {
r.checklist_item_id: r
for r in parent.results.all()
}
# Decode parent form_data from its notes field
parent_form_data = {}
if parent.notes:
try:
parsed_p = json.loads(parent.notes)
if isinstance(parsed_p, dict):
parent_form_data = parsed_p.get('_form_data', {})
except (json.JSONDecodeError, TypeError):
pass
scoreable_types = ('rating', 'checkbox', 'radio')
rows = []
for result in inspection.results.order_by(
InspectionResult.checklist_item_id
).all():
item = result.checklist_item
p_res = parent_results.get(result.checklist_item_id)
# Normalise scores to a 0100 percentage for fair comparison
def _pct(res, ci):
if res is None:
for field in form_fields:
ftype = field.get('type')
if ftype not in scoreable_types:
continue
fid = field.get('id', '')
label = field.get('label') or field.get('placeholder') or fid
cur_val = form_data.get(fid, '')
par_val = parent_form_data.get(fid, '')
def _field_pct(val, ft):
if ft == 'rating':
try:
v = int(val)
return None if v == 0 else round(v / 5 * 100, 1)
except (ValueError, TypeError):
return None
if ci.scoring_type == 'pass_fail':
if res.passed is None:
if ft == 'checkbox':
if val == '' or val is None:
return None
return 100.0 if res.passed else 0.0
if ci.scoring_type == 'rating_5':
return (float(res.score) / 5 * 100) if res.score is not None else None
if ci.scoring_type == 'rating_10':
return (float(res.score) / 10 * 100) if res.score is not None else None
return 100.0 if val == 'true' else 0.0
if ft == 'radio':
return None if not val else 100.0
return None
cur_pct = _pct(result, item)
par_pct = _pct(p_res, item) if p_res else None
cur_pct = _field_pct(cur_val, ftype)
par_pct = _field_pct(par_val, ftype)
if cur_pct is None and par_pct is None:
continue # skip unanswered items on both sides
continue
delta = None
if cur_pct is not None and par_pct is not None:
delta = round(cur_pct - par_pct, 1)
rows.append({
'category': item.category or 'Uncategorised',
'description': item.item_description,
'parent_pct': round(par_pct, 1) if par_pct is not None else None,
'current_pct': round(cur_pct, 1) if cur_pct is not None else None,
'category': field.get('section') or field.get('category') or 'General',
'description': label,
'parent_pct': par_pct,
'current_pct': cur_pct,
'delta': delta,
})