Boost Azure Demo

This commit is contained in:
MangoPig
2026-05-25 17:05:06 +01:00
parent 675285e99d
commit 4f79137d89
230 changed files with 43275 additions and 2644 deletions

View File

@@ -603,6 +603,156 @@ def gen_answer(student: dict, assignment: dict, aq: dict, q: dict, week_idx: int
}
def derive_working_steps(q: dict, ans: dict):
solve_mode = ans["solve_mode"]
correct = bool(ans["is_correct"])
topic = q["topic"]
sub_topic = q.get("sub_topic") or topic
expected = q["correct_answer"]
given = ans["answer_text"]
misconception_tag = ans.get("misconception_tag")
if solve_mode == "just_answer":
return "I solved it mentally and wrote the final answer only."
if solve_mode == "solve_together":
if correct:
return f"I followed guided steps for {topic.lower()} and reached {given}."
return f"I needed guided help on {sub_topic.lower()}, but I still ended with {given} instead of {expected}."
if solve_mode == "handwritten":
if correct:
return f"I worked it out on paper using {sub_topic.lower()} and checked that the final answer was {given}."
return f"I worked it out on paper for {sub_topic.lower()}, but my final answer was {given} instead of {expected}."
if correct:
return f"I used a step-by-step method for {sub_topic.lower()} and got the correct answer {given}."
misconception_explanations = {
"add_tops_add_bottoms": "I added the numerators and denominators directly instead of finding a common denominator.",
"fraction_op_confusion": "I mixed up the fraction rule and used the wrong operation method.",
"fraction_general_uncertainty": "I was unsure which fraction method to use, so my working was inconsistent.",
"place_value_misalignment": "I lined the digits up incorrectly and misread the place value.",
"arithmetic_slip": "My method was close, but I made a careless arithmetic slip in the calculation.",
"scaffolding_dependence": "I could start the method, but I was not secure enough to finish it independently.",
"word_problem_interpretation": "I misunderstood what the word problem was asking me to calculate.",
}
explanation = misconception_explanations.get(
misconception_tag,
f"My method did not lead to the expected answer {expected}.",
)
return f"I tried a step-by-step method for {sub_topic.lower()}, but I got {given}. {explanation}"
def derive_review_fields(q: dict, ans: dict):
correct = bool(ans["is_correct"])
solve_mode = ans["solve_mode"]
misconception_tag = ans["misconception_tag"]
expected = q["correct_answer"]
if correct:
understanding_score = {
"step_by_step": 0.95,
"handwritten": 0.85,
"just_answer": 0.75,
"solve_together": 0.65,
}.get(solve_mode, 0.75)
confidence = {
"step_by_step": 0.82,
"handwritten": 0.78,
"just_answer": 0.9,
"solve_together": 0.62,
}.get(solve_mode, 0.78)
needs_attention = understanding_score < 0.72
issue_reason = (
"Correct answer, but there is limited evidence that the method is secure."
if needs_attention else None
)
ai_feedback_review = (
"Correct answer with clear method evidence."
if solve_mode == "step_by_step"
else "Correct answer, but understanding evidence is lighter because the response is brief."
if needs_attention
else "Correct answer with secure understanding shown."
)
else:
understanding_score = {
"step_by_step": 0.4,
"handwritten": 0.32,
"just_answer": 0.2,
"solve_together": 0.28,
}.get(solve_mode, 0.25)
confidence = {
"step_by_step": 0.55,
"handwritten": 0.6,
"just_answer": 0.72,
"solve_together": 0.5,
}.get(solve_mode, 0.6)
needs_attention = True
issue_reason = {
"add_tops_add_bottoms": "The student added the numerator and denominator directly instead of finding a common denominator.",
"fraction_op_confusion": "The student confused the fraction operation and did not apply the correct method.",
"fraction_general_uncertainty": "The student shows insecure understanding of equivalent or comparable fractions.",
"place_value_misalignment": "The student misread place value, causing digits to be aligned incorrectly.",
"arithmetic_slip": "The final answer is wrong, suggesting a careless arithmetic slip rather than a secure method.",
"scaffolding_dependence": "The student appears dependent on scaffolding and does not show secure independent understanding.",
"word_problem_interpretation": "The student did not translate the word problem into the correct calculation.",
}.get(
misconception_tag,
f"The answer does not match the correct answer ({expected}), showing an incomplete understanding of the method.",
)
ai_feedback_review = f"Incorrect answer compared with the expected answer {expected}. {issue_reason}"
return {
"review_correctness_score": 1.0,
"review_question_score": 1.0,
"review_understanding_score": round(understanding_score, 3),
"review_confidence": round(confidence, 3),
"review_needs_attention": needs_attention,
"review_issue_reason": issue_reason,
"review_tags": [misconception_tag] if misconception_tag else [],
"ai_feedback_review": ai_feedback_review,
}
def build_assignment_review_summary(student_name: str, assignment_name: str, reviews: list[dict]):
if not reviews:
return None, "No submitted work is available for this assignment yet.", None
per_question_scores = []
weak_topics: dict[str, list[float]] = {}
attention_count = 0
correct_count = 0
for review in reviews:
correctness_value = 1.0 if review["is_correct"] else 0.0
understanding_value = review["review_understanding_score"]
question_score = (correctness_value + understanding_value) / 2
per_question_scores.append(question_score)
weak_topics.setdefault(review["topic"], []).append(question_score)
if review["review_needs_attention"]:
attention_count += 1
if review["is_correct"]:
correct_count += 1
overall_score = round(sum(per_question_scores) / len(per_question_scores) * 10, 2)
weakest_topics = sorted(
((topic, sum(values) / len(values)) for topic, values in weak_topics.items()),
key=lambda item: (item[1], item[0]),
)[:2]
weakest_topic_text = ", ".join(topic for topic, _ in weakest_topics) if weakest_topics else "general fluency"
next_step_outcome = "accept" if overall_score >= 6.0 else "support" if overall_score >= 4.5 else "redo"
ai_feedback = (
f"{student_name} completed {assignment_name} with {correct_count}/{len(reviews)} correct responses. "
f"Overall score is {overall_score}/10. "
f"The weakest areas were {weakest_topic_text}. "
f"{attention_count} question(s) need extra attention."
)
return overall_score, ai_feedback, next_step_outcome
# Order assignments oldest -> newest for week_idx threading.
ASSIGNMENT_DEFS_SORTED = sorted(ASSIGNMENT_DEFS, key=lambda a: a[3]) # by due_offset
weeks_total = len(ASSIGNMENT_DEFS_SORTED)
@@ -710,9 +860,12 @@ for week_idx, assignment_def in enumerate(ASSIGNMENT_DEFS_SORTED):
questions_to_answer = a_questions[:n_done]
running_time_offset = 0
answer_reviews = []
for aq in questions_to_answer:
q = QB_BY_ID[aq["question_bank_id"]]
ans = gen_answer(student, assignment_def, aq, q, week_idx, weeks_total)
working_steps = derive_working_steps(q, ans)
review = derive_review_fields(q, ans)
answered_at = started_at + timedelta(seconds=running_time_offset + ans["time_on_task_seconds"])
running_time_offset += ans["time_on_task_seconds"] + RNG.randint(5, 30)
@@ -723,12 +876,23 @@ for week_idx, assignment_def in enumerate(ASSIGNMENT_DEFS_SORTED):
"answer_type": "LATEX",
"answer_latex": ans["answer_text"],
"extracted_answer": ans["answer_text"],
"solve_mode": ans["solve_mode"],
"working_steps": working_steps,
"graded_marks": 1 if ans["is_correct"] else 0,
"marks_awarded": 1 if ans["is_correct"] else 0,
"ai_reasoning": (
"Answer matches expected solution." if ans["is_correct"]
else f"Incorrect; expected {q['correct_answer']}."
),
"is_correct": ans["is_correct"],
"ai_feedback": review["ai_feedback_review"],
"review_needs_attention": review["review_needs_attention"],
"review_issue_reason": review["review_issue_reason"],
"review_correctness_score": review["review_correctness_score"],
"review_understanding_score": review["review_understanding_score"],
"review_question_score": review["review_question_score"],
"review_confidence": review["review_confidence"],
"review_tags": review["review_tags"],
"grading_status": "GRADED",
"grading_attempts": 1,
"is_active": True,
@@ -743,6 +907,12 @@ for week_idx, assignment_def in enumerate(ASSIGNMENT_DEFS_SORTED):
"_question_difficulty": q["difficulty"],
"_answered_at": ms(answered_at),
})
answer_reviews.append({
"topic": q["topic"],
"is_correct": ans["is_correct"],
"review_understanding_score": review["review_understanding_score"],
"review_needs_attention": review["review_needs_attention"],
})
answer_id_seq += 1
total_score += (1 if ans["is_correct"] else 0)
@@ -761,6 +931,11 @@ for week_idx, assignment_def in enumerate(ASSIGNMENT_DEFS_SORTED):
log_id_seq += 1
submitted_at = started_at + timedelta(seconds=running_time_offset) if submitted else None
overall_score, assignee_ai_feedback, next_step_outcome = build_assignment_review_summary(
student["fullname"],
name,
answer_reviews,
)
assignee = {
"id": assignee_id,
@@ -771,6 +946,9 @@ for week_idx, assignment_def in enumerate(ASSIGNMENT_DEFS_SORTED):
"started_at": ms(started_at),
"submitted_at": ms(submitted_at) if submitted_at else None,
"total_marks": total_score if submitted else None,
"overall_score": overall_score if submitted else None,
"ai_feedback": assignee_ai_feedback if submitted else None,
"next_step_outcome": next_step_outcome if submitted else None,
"is_active": True,
"deactivated_at": None,
"created_at": ms(days_ago(-(offset - 7))),