Mar 05 2026: implement reinspect comparisio fix
This commit is contained in:
+48
-35
@@ -496,67 +496,80 @@ def view(inspection_id):
|
|||||||
issues = inspection.issues.order_by(Issue.reported_at.desc()).all()
|
issues = inspection.issues.order_by(Issue.reported_at.desc()).all()
|
||||||
|
|
||||||
# ── Score comparison against parent inspection ────────────────────────
|
# ── Score comparison against parent inspection ────────────────────────
|
||||||
# Produces a list of per-item dicts so the template can render a
|
# Scores live in form_data (field_id -> value) inside inspection.notes,
|
||||||
# side-by-side delta table without any additional queries at render time.
|
# NOT in InspectionResult checklist rows. We walk the shared form_fields
|
||||||
|
# schema and compare current vs. parent responses per scoreable field.
|
||||||
comparison = None
|
comparison = None
|
||||||
if inspection.parent and inspection.parent.status == 'completed':
|
if inspection.parent and inspection.parent.status == 'completed':
|
||||||
parent = inspection.parent
|
parent = inspection.parent
|
||||||
|
|
||||||
# Index parent results by checklist_item_id
|
# Decode parent form_data from its notes field
|
||||||
parent_results = {
|
parent_form_data = {}
|
||||||
r.checklist_item_id: r
|
if parent.notes:
|
||||||
for r in parent.results.all()
|
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 = []
|
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 0–100 percentage for fair comparison
|
for field in form_fields:
|
||||||
def _pct(res, ci):
|
ftype = field.get('type')
|
||||||
if res is None:
|
if ftype not in scoreable_types:
|
||||||
return None
|
continue
|
||||||
if ci.scoring_type == 'pass_fail':
|
|
||||||
if res.passed is None:
|
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
|
return None
|
||||||
return 100.0 if res.passed else 0.0
|
if ft == 'checkbox':
|
||||||
if ci.scoring_type == 'rating_5':
|
if val == '' or val is None:
|
||||||
return (float(res.score) / 5 * 100) if res.score is not None else None
|
return None
|
||||||
if ci.scoring_type == 'rating_10':
|
return 100.0 if val == 'true' else 0.0
|
||||||
return (float(res.score) / 10 * 100) if res.score is not None else None
|
if ft == 'radio':
|
||||||
|
return None if not val else 100.0
|
||||||
return None
|
return None
|
||||||
|
|
||||||
cur_pct = _pct(result, item)
|
cur_pct = _field_pct(cur_val, ftype)
|
||||||
par_pct = _pct(p_res, item) if p_res else None
|
par_pct = _field_pct(par_val, ftype)
|
||||||
|
|
||||||
if cur_pct is None and par_pct is None:
|
if cur_pct is None and par_pct is None:
|
||||||
continue # skip unanswered items on both sides
|
continue
|
||||||
|
|
||||||
delta = None
|
delta = None
|
||||||
if cur_pct is not None and par_pct is not None:
|
if cur_pct is not None and par_pct is not None:
|
||||||
delta = round(cur_pct - par_pct, 1)
|
delta = round(cur_pct - par_pct, 1)
|
||||||
|
|
||||||
rows.append({
|
rows.append({
|
||||||
'category': item.category or 'Uncategorised',
|
'category': field.get('section') or field.get('category') or 'General',
|
||||||
'description': item.item_description,
|
'description': label,
|
||||||
'parent_pct': round(par_pct, 1) if par_pct is not None else None,
|
'parent_pct': par_pct,
|
||||||
'current_pct': round(cur_pct, 1) if cur_pct is not None else None,
|
'current_pct': cur_pct,
|
||||||
'delta': delta,
|
'delta': delta,
|
||||||
})
|
})
|
||||||
|
|
||||||
comparison = {
|
comparison = {
|
||||||
'parent_id': parent.id,
|
'parent_id': parent.id,
|
||||||
'parent_date': parent.inspection_date,
|
'parent_date': parent.inspection_date,
|
||||||
'parent_score': float(parent.overall_score) if parent.overall_score else None,
|
'parent_score': float(parent.overall_score) if parent.overall_score else None,
|
||||||
'current_score': float(inspection.overall_score) if inspection.overall_score else None,
|
'current_score': float(inspection.overall_score) if inspection.overall_score else None,
|
||||||
'score_delta': (
|
'score_delta': (
|
||||||
round(float(inspection.overall_score) - float(parent.overall_score), 2)
|
round(float(inspection.overall_score) - float(parent.overall_score), 2)
|
||||||
if inspection.overall_score and parent.overall_score else None
|
if inspection.overall_score and parent.overall_score else None
|
||||||
),
|
),
|
||||||
'rows': rows,
|
'rows': rows,
|
||||||
'improved': sum(1 for r in rows if r['delta'] is not None and r['delta'] > 0),
|
'improved': sum(1 for r in rows if r['delta'] is not None and r['delta'] > 0),
|
||||||
'regressed': sum(1 for r in rows if r['delta'] is not None and r['delta'] < 0),
|
'regressed': sum(1 for r in rows if r['delta'] is not None and r['delta'] < 0),
|
||||||
'unchanged': sum(1 for r in rows if r['delta'] == 0),
|
'unchanged': sum(1 for r in rows if r['delta'] == 0),
|
||||||
|
|||||||
Reference in New Issue
Block a user