Enhance answer aggregation logic in exam review to handle diverse answer formats and improve robustness
This commit is contained in:
@@ -1206,8 +1206,57 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
total_responses = ua_qs.count()
|
total_responses = ua_qs.count()
|
||||||
|
|
||||||
counts = {}
|
counts = {}
|
||||||
|
# Some UserAnswer models (e.g. physics) do not have a single 'answer'
|
||||||
|
# field. Try the simple values_list first; on failure fall back to
|
||||||
|
# iterating objects and using get_answer() or composing from
|
||||||
|
# per-field parts (a,b,c,d,e, or longs sections).
|
||||||
|
try:
|
||||||
for ans in ua_qs.values_list("answer", flat=True):
|
for ans in ua_qs.values_list("answer", flat=True):
|
||||||
counts[ans] = counts.get(ans, 0) + 1
|
counts[ans] = counts.get(ans, 0) + 1
|
||||||
|
except Exception:
|
||||||
|
for ua in ua_qs:
|
||||||
|
ans_val = None
|
||||||
|
# Prefer model helper if available
|
||||||
|
try:
|
||||||
|
if hasattr(ua, "get_answer"):
|
||||||
|
ans_val = ua.get_answer()
|
||||||
|
except Exception:
|
||||||
|
ans_val = None
|
||||||
|
|
||||||
|
if not ans_val:
|
||||||
|
parts = []
|
||||||
|
# Common single-letter fields used by physics
|
||||||
|
for fld in ("a", "b", "c", "d", "e"):
|
||||||
|
if hasattr(ua, fld):
|
||||||
|
v = getattr(ua, fld)
|
||||||
|
if v is None:
|
||||||
|
continue
|
||||||
|
if isinstance(v, (list, tuple)):
|
||||||
|
parts.extend([str(x) for x in v])
|
||||||
|
else:
|
||||||
|
parts.append(str(v))
|
||||||
|
|
||||||
|
# Long-form answer fields (longs)
|
||||||
|
for fld in (
|
||||||
|
"answer_observations",
|
||||||
|
"answer_interpretation",
|
||||||
|
"answer_principle_diagnosis",
|
||||||
|
"answer_differential_diagnosis",
|
||||||
|
"answer_management",
|
||||||
|
):
|
||||||
|
if hasattr(ua, fld):
|
||||||
|
v = getattr(ua, fld)
|
||||||
|
if v:
|
||||||
|
parts.append(str(v))
|
||||||
|
|
||||||
|
if parts:
|
||||||
|
ans_val = ", ".join(parts)
|
||||||
|
|
||||||
|
# Final fallback
|
||||||
|
if ans_val is None:
|
||||||
|
ans_val = ""
|
||||||
|
|
||||||
|
counts[ans_val] = counts.get(ans_val, 0) + 1
|
||||||
|
|
||||||
if total_responses:
|
if total_responses:
|
||||||
pcts = {k: round(100.0 * v / total_responses, 1) for k, v in counts.items()}
|
pcts = {k: round(100.0 * v / total_responses, 1) for k, v in counts.items()}
|
||||||
@@ -1386,8 +1435,49 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
ua_qs = question.cid_user_answers.filter(exam=exam)
|
ua_qs = question.cid_user_answers.filter(exam=exam)
|
||||||
total_responses = ua_qs.count()
|
total_responses = ua_qs.count()
|
||||||
counts = {}
|
counts = {}
|
||||||
|
try:
|
||||||
for ans in ua_qs.values_list("answer", flat=True):
|
for ans in ua_qs.values_list("answer", flat=True):
|
||||||
counts[ans] = counts.get(ans, 0) + 1
|
counts[ans] = counts.get(ans, 0) + 1
|
||||||
|
except Exception:
|
||||||
|
for ua in ua_qs:
|
||||||
|
ans_val = None
|
||||||
|
try:
|
||||||
|
if hasattr(ua, "get_answer"):
|
||||||
|
ans_val = ua.get_answer()
|
||||||
|
except Exception:
|
||||||
|
ans_val = None
|
||||||
|
|
||||||
|
if not ans_val:
|
||||||
|
parts = []
|
||||||
|
for fld in ("a", "b", "c", "d", "e"):
|
||||||
|
if hasattr(ua, fld):
|
||||||
|
v = getattr(ua, fld)
|
||||||
|
if v is None:
|
||||||
|
continue
|
||||||
|
if isinstance(v, (list, tuple)):
|
||||||
|
parts.extend([str(x) for x in v])
|
||||||
|
else:
|
||||||
|
parts.append(str(v))
|
||||||
|
|
||||||
|
for fld in (
|
||||||
|
"answer_observations",
|
||||||
|
"answer_interpretation",
|
||||||
|
"answer_principle_diagnosis",
|
||||||
|
"answer_differential_diagnosis",
|
||||||
|
"answer_management",
|
||||||
|
):
|
||||||
|
if hasattr(ua, fld):
|
||||||
|
v = getattr(ua, fld)
|
||||||
|
if v:
|
||||||
|
parts.append(str(v))
|
||||||
|
|
||||||
|
if parts:
|
||||||
|
ans_val = ", ".join(parts)
|
||||||
|
|
||||||
|
if ans_val is None:
|
||||||
|
ans_val = ""
|
||||||
|
|
||||||
|
counts[ans_val] = counts.get(ans_val, 0) + 1
|
||||||
|
|
||||||
# Compute percentages per answer key
|
# Compute percentages per answer key
|
||||||
pcts = {}
|
pcts = {}
|
||||||
|
|||||||
+32
-1
@@ -466,16 +466,47 @@ def exam_review_question_summary(request, pk: int, q_index: int):
|
|||||||
# Physics answers may be stored as tuples/lists or named fields; attempt to aggregate
|
# Physics answers may be stored as tuples/lists or named fields; attempt to aggregate
|
||||||
counts = defaultdict(int)
|
counts = defaultdict(int)
|
||||||
other_count = 0
|
other_count = 0
|
||||||
|
# Try simple 'answer' field aggregation first; if not present, fall
|
||||||
|
# back to iterating objects and using get_answer() or composing parts.
|
||||||
|
try:
|
||||||
for ans in ua_qs.values_list("answer", flat=True):
|
for ans in ua_qs.values_list("answer", flat=True):
|
||||||
if not ans:
|
if not ans:
|
||||||
other_count += 1
|
other_count += 1
|
||||||
continue
|
continue
|
||||||
# normalize iterable answers to a string key
|
|
||||||
if isinstance(ans, (list, tuple)):
|
if isinstance(ans, (list, tuple)):
|
||||||
key = ", ".join([str(x) for x in ans])
|
key = ", ".join([str(x) for x in ans])
|
||||||
else:
|
else:
|
||||||
key = str(ans)
|
key = str(ans)
|
||||||
counts[key] += 1
|
counts[key] += 1
|
||||||
|
except Exception:
|
||||||
|
for ua in ua_qs:
|
||||||
|
ans_val = None
|
||||||
|
try:
|
||||||
|
if hasattr(ua, "get_answer"):
|
||||||
|
ans_val = ua.get_answer()
|
||||||
|
except Exception:
|
||||||
|
ans_val = None
|
||||||
|
|
||||||
|
if not ans_val:
|
||||||
|
parts = []
|
||||||
|
for fld in ("a", "b", "c", "d", "e"):
|
||||||
|
if hasattr(ua, fld):
|
||||||
|
v = getattr(ua, fld)
|
||||||
|
if v is None:
|
||||||
|
continue
|
||||||
|
if isinstance(v, (list, tuple)):
|
||||||
|
parts.extend([str(x) for x in v])
|
||||||
|
else:
|
||||||
|
parts.append(str(v))
|
||||||
|
|
||||||
|
if parts:
|
||||||
|
ans_val = ", ".join(parts)
|
||||||
|
|
||||||
|
if not ans_val:
|
||||||
|
other_count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
counts[ans_val] += 1
|
||||||
|
|
||||||
counts["unanswered"] = other_count
|
counts["unanswered"] = other_count
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user