vibe coded frontend
This commit is contained in:
148
Mock-Data/README.md
Normal file
148
Mock-Data/README.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# Mock Pupil-History Dataset — Learning Path Agent Hackathon
|
||||
|
||||
Hand-crafted, internally-consistent JSON dataset for the BoostAI **Learning
|
||||
Path Agent** hackathon challenge. Mirrors the production SQLAlchemy schema
|
||||
in `elevenplus-backend/src/app/models/` so an agent built on this data
|
||||
transfers cleanly to the real database after the hackathon.
|
||||
|
||||
**Reference date:** `2026-05-01` (timestamps in the data are relative to
|
||||
this — adjust `TODAY` in `generate.py` to shift them).
|
||||
|
||||
## What's in here
|
||||
|
||||
```
|
||||
classroom.json One Year-6 Maths class + tutor + student-classroom links
|
||||
students.json 12 students (with _persona annotation per student)
|
||||
question_bank.json 50 unique 11+ Maths questions
|
||||
assignments.json 8 assignments (5 CLOSED, 2 PUBLISHED, 1 DRAFT)
|
||||
assignment_questions.json 64 assignment-question rows (links to question_bank)
|
||||
assignment_assignees.json 85 (student × assignment) rows with status/scores
|
||||
student_answers.json 593 per-question answer records — THE primary file
|
||||
activity_logs.json 593 activity log rows (timestamps, durations, solve_mode)
|
||||
dataset.json All of the above bundled into one object
|
||||
generate.py Deterministic generator (re-run anytime)
|
||||
```
|
||||
|
||||
The agent's main input is `dataset.json` (or `student_answers.json` plus
|
||||
`question_bank.json` if you'd rather load files separately).
|
||||
|
||||
## Schema fidelity
|
||||
|
||||
Field names and enum values match the production models exactly:
|
||||
|
||||
| Production model | This dataset |
|
||||
|---|---|
|
||||
| `users.py` (role=student) | `students.json` |
|
||||
| `classrooms.py` + `classroom_student_rs.py` | `classroom.json` |
|
||||
| `assignments.py` (status: DRAFT/PUBLISHED/CLOSED) | `assignments.json` |
|
||||
| `assignment_questions.py` | `assignment_questions.json` |
|
||||
| `assignment_assignees.py` (status: NOT_STARTED/IN_PROGRESS/SUBMITTED) | `assignment_assignees.json` |
|
||||
| `question_bank.py` (difficulty: EASY/MEDIUM/HARD; source: BOOST) | `question_bank.json` |
|
||||
| `student_assignment_answers.py` (graded_marks, marks_awarded, grading_status=GRADED) | `student_answers.json` |
|
||||
| `assignment_activity_logs.py` (activity_type, duration_seconds, extra_data) | `activity_logs.json` |
|
||||
| `question_metrics.py` (explanation_type) | embedded as `_solve_mode` on each answer |
|
||||
|
||||
Timestamps are **Unix milliseconds** (BigInteger) per the project convention.
|
||||
|
||||
### Hackathon annotations (not in production schema)
|
||||
|
||||
Fields prefixed with **`_`** are hackathon-only annotations. Strip them if
|
||||
you ever seed this data into the real DB.
|
||||
|
||||
| Annotation | Where | Meaning |
|
||||
|---|---|---|
|
||||
| `_persona` | `students.json` | Engineered behaviour persona (see below) |
|
||||
| `_solve_mode` | `student_answers.json` | One of `just_answer`, `step_by_step`, `solve_together`, `handwritten` |
|
||||
| `_time_on_task_seconds` | `student_answers.json` | Seconds spent on this question |
|
||||
| `_is_correct` | `student_answers.json` | Boolean correctness (already implied by `graded_marks`) |
|
||||
| `_misconception_tag` | `student_answers.json` | Set when wrong answer matches a known misconception (e.g. `add_tops_add_bottoms`) |
|
||||
| `_question_topic` / `_sub_topic` / `_difficulty` | `student_answers.json` | Denormalised from `question_bank` for convenience |
|
||||
| `_answered_at` | `student_answers.json` | Same as `created_at`, just clearer name |
|
||||
|
||||
## The 12 students
|
||||
|
||||
Five students have engineered misconception personas; the other seven are
|
||||
realistic noise. The agent should ideally identify the personas from the
|
||||
data itself — `_persona` is included only so you can grade the agent.
|
||||
|
||||
| ID | Name | Persona | What you'll see in the data |
|
||||
|---|---|---|---|
|
||||
| 201 | Aisha Khan | `fraction_inversion` | ~12% on Fractions Add/Subtract/Multiply, ~78% elsewhere. Wrong answers show **add-tops-add-bottoms** pattern (e.g. ½+⅓ → ⅖). |
|
||||
| 202 | Ben Carter | `place_value_gaps` | Fails multi-digit subtraction with borrowing & decimal alignment. Strong on single-digit ops. |
|
||||
| 203 | Chen Wei | `rushed_careless` | Right method when in `step_by_step`; wrong final answer in `just_answer`. Time-on-task drops week-over-week. **No activity in the last 8 days** — also drives Early Warning. |
|
||||
| 204 | Daniela Rossi | `solve_together_dependent` | Solve-Together share rises **21% → 88%** across the period. Independent accuracy degrading. |
|
||||
| 205 | Elif Demir | `word_problem_weak` | 0% on word problems, ~90% on bare computation of the same operations. |
|
||||
| 206 | Felix Brown | `stable_strong` | ~84% overall — baseline noise. |
|
||||
| 207 | Grace Park | `stable_strong` | ~85% overall — baseline noise. |
|
||||
| 208–210 | Singh / Nakamura / Williams | `stable_mid` | ~65% overall — baseline noise. |
|
||||
| 211–212 | Patel / O'Connor | `stable_weak` | ~50% overall — baseline noise. |
|
||||
|
||||
## The 8 assignments
|
||||
|
||||
| ID | Name | Topic | Due | Status | Why it matters |
|
||||
|---|---|---|---|---|---|
|
||||
| 3001 | HW1 — Place Value Warmup | Place Value | -28d | CLOSED | Baseline data |
|
||||
| 3002 | HW2 — Arithmetic Practice | Arithmetic | -22d | CLOSED | |
|
||||
| 3003 | HW3 — Fractions Foundations | Fractions | -16d | CLOSED | First fraction signal |
|
||||
| 3004 | HW4 — Negatives & BIDMAS | BIDMAS | -10d | CLOSED | |
|
||||
| 3005 | HW5 — Geometry Basics | Geometry | -6d | CLOSED | |
|
||||
| 3006 | HW6 — Algebra & Sequences | Algebra | +2d | PUBLISHED | In flight |
|
||||
| **3007** | **HW7 — Adding Fractions (test prep)** | **Fractions** | **+5d** | **PUBLISHED** | **Curriculum deadline anchor for the bonus EWS** |
|
||||
| 3008 | HW8 — Mixed Revision | Mixed | +12d | DRAFT | No activity yet |
|
||||
|
||||
## Bonus / Early Warning signals embedded in the data
|
||||
|
||||
| Signal | Where it lives | Who exhibits it |
|
||||
|---|---|---|
|
||||
| Drop in attempt rate (last 7 days) | `student_answers.json` timestamps | Student 203 (no recent activity); 211 reduced volume |
|
||||
| Increasing Solve-Together reliance | `_solve_mode` distribution over time | Student 204 (21% → 88%) |
|
||||
| Declining score trend on deadline topic | Fractions accuracy across HW3 → HW7 | Student 201 (very weak on Fractions; HW7 is fractions, due in 5 days) |
|
||||
| Time since last session | `activity_logs.json` last timestamp | Student 203 (≥ 8 days) |
|
||||
|
||||
**Expected agent output for the bonus monitor on this dataset:**
|
||||
|
||||
| Rank | Student | Topic driving risk | Suggested action |
|
||||
|---|---|---|---|
|
||||
| 1 | 203 Chen Wei | Engagement collapse | Reach out + light re-entry assignment; check for blockers |
|
||||
| 2 | 201 Aisha Khan | Fractions / Add — HW7 in 5 days | 3 targeted fraction-add questions on the add-tops-add-bottoms misconception |
|
||||
| 3 | 204 Daniela Rossi | Increasing scaffold dependence (any topic) | Pair with `step_by_step` mode + scheduled `just_answer` checkpoint |
|
||||
|
||||
## Running / re-running the generator
|
||||
|
||||
```bash
|
||||
cd boost-ai-eval/mock-data
|
||||
python3 generate.py
|
||||
```
|
||||
|
||||
The RNG is seeded (`20260501`), so output is byte-stable across runs unless
|
||||
you change the source. To shift dates, edit `TODAY` at the top of
|
||||
`generate.py`.
|
||||
|
||||
## Quick agent-side recipes
|
||||
|
||||
**Load everything:**
|
||||
```python
|
||||
import json
|
||||
data = json.load(open('boost-ai-eval/mock-data/dataset.json'))
|
||||
students = data['students']
|
||||
answers = data['student_answers']
|
||||
qbank = {q['id']: q for q in data['question_bank']}
|
||||
```
|
||||
|
||||
**Pull all answers for a single student:**
|
||||
```python
|
||||
def answers_for(student_id):
|
||||
aa_ids = {aa['id'] for aa in data['assignment_assignees']
|
||||
if aa['student_id'] == student_id}
|
||||
return [a for a in data['student_answers'] if a['assignee_id'] in aa_ids]
|
||||
```
|
||||
|
||||
**Compute topic-level mastery snapshot:**
|
||||
```python
|
||||
from collections import defaultdict
|
||||
topic_acc = defaultdict(lambda: [0, 0]) # [correct, total]
|
||||
for a in answers_for(201):
|
||||
topic_acc[a['_question_topic']][1] += 1
|
||||
topic_acc[a['_question_topic']][0] += int(a['_is_correct'])
|
||||
# {'Fractions': [2, 16], 'Place Value': [3, 4], ...}
|
||||
```
|
||||
7646
Mock-Data/activity_logs.json
Normal file
7646
Mock-Data/activity_logs.json
Normal file
File diff suppressed because it is too large
Load Diff
1107
Mock-Data/assignment_assignees.json
Normal file
1107
Mock-Data/assignment_assignees.json
Normal file
File diff suppressed because it is too large
Load Diff
578
Mock-Data/assignment_questions.json
Normal file
578
Mock-Data/assignment_questions.json
Normal file
@@ -0,0 +1,578 @@
|
||||
[
|
||||
{
|
||||
"id": 300101,
|
||||
"assignment_id": 3001,
|
||||
"question_bank_id": 1001,
|
||||
"question_order": 1,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1774605600000
|
||||
},
|
||||
{
|
||||
"id": 300102,
|
||||
"assignment_id": 3001,
|
||||
"question_bank_id": 1002,
|
||||
"question_order": 2,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1774605600000
|
||||
},
|
||||
{
|
||||
"id": 300103,
|
||||
"assignment_id": 3001,
|
||||
"question_bank_id": 1003,
|
||||
"question_order": 3,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1774605600000
|
||||
},
|
||||
{
|
||||
"id": 300104,
|
||||
"assignment_id": 3001,
|
||||
"question_bank_id": 1101,
|
||||
"question_order": 4,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1774605600000
|
||||
},
|
||||
{
|
||||
"id": 300105,
|
||||
"assignment_id": 3001,
|
||||
"question_bank_id": 1102,
|
||||
"question_order": 5,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1774605600000
|
||||
},
|
||||
{
|
||||
"id": 300106,
|
||||
"assignment_id": 3001,
|
||||
"question_bank_id": 1301,
|
||||
"question_order": 6,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1774605600000
|
||||
},
|
||||
{
|
||||
"id": 300107,
|
||||
"assignment_id": 3001,
|
||||
"question_bank_id": 1401,
|
||||
"question_order": 7,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1774605600000
|
||||
},
|
||||
{
|
||||
"id": 300108,
|
||||
"assignment_id": 3001,
|
||||
"question_bank_id": 1701,
|
||||
"question_order": 8,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1774605600000
|
||||
},
|
||||
{
|
||||
"id": 300201,
|
||||
"assignment_id": 3002,
|
||||
"question_bank_id": 1101,
|
||||
"question_order": 1,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775124000000
|
||||
},
|
||||
{
|
||||
"id": 300202,
|
||||
"assignment_id": 3002,
|
||||
"question_bank_id": 1102,
|
||||
"question_order": 2,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775124000000
|
||||
},
|
||||
{
|
||||
"id": 300203,
|
||||
"assignment_id": 3002,
|
||||
"question_bank_id": 1103,
|
||||
"question_order": 3,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775124000000
|
||||
},
|
||||
{
|
||||
"id": 300204,
|
||||
"assignment_id": 3002,
|
||||
"question_bank_id": 1104,
|
||||
"question_order": 4,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775124000000
|
||||
},
|
||||
{
|
||||
"id": 300205,
|
||||
"assignment_id": 3002,
|
||||
"question_bank_id": 1105,
|
||||
"question_order": 5,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775124000000
|
||||
},
|
||||
{
|
||||
"id": 300206,
|
||||
"assignment_id": 3002,
|
||||
"question_bank_id": 1106,
|
||||
"question_order": 6,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775124000000
|
||||
},
|
||||
{
|
||||
"id": 300207,
|
||||
"assignment_id": 3002,
|
||||
"question_bank_id": 1801,
|
||||
"question_order": 7,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775124000000
|
||||
},
|
||||
{
|
||||
"id": 300208,
|
||||
"assignment_id": 3002,
|
||||
"question_bank_id": 1803,
|
||||
"question_order": 8,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775124000000
|
||||
},
|
||||
{
|
||||
"id": 300301,
|
||||
"assignment_id": 3003,
|
||||
"question_bank_id": 1401,
|
||||
"question_order": 1,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775642400000
|
||||
},
|
||||
{
|
||||
"id": 300302,
|
||||
"assignment_id": 3003,
|
||||
"question_bank_id": 1402,
|
||||
"question_order": 2,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775642400000
|
||||
},
|
||||
{
|
||||
"id": 300303,
|
||||
"assignment_id": 3003,
|
||||
"question_bank_id": 1411,
|
||||
"question_order": 3,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775642400000
|
||||
},
|
||||
{
|
||||
"id": 300304,
|
||||
"assignment_id": 3003,
|
||||
"question_bank_id": 1412,
|
||||
"question_order": 4,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775642400000
|
||||
},
|
||||
{
|
||||
"id": 300305,
|
||||
"assignment_id": 3003,
|
||||
"question_bank_id": 1413,
|
||||
"question_order": 5,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775642400000
|
||||
},
|
||||
{
|
||||
"id": 300306,
|
||||
"assignment_id": 3003,
|
||||
"question_bank_id": 1421,
|
||||
"question_order": 6,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775642400000
|
||||
},
|
||||
{
|
||||
"id": 300307,
|
||||
"assignment_id": 3003,
|
||||
"question_bank_id": 1422,
|
||||
"question_order": 7,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775642400000
|
||||
},
|
||||
{
|
||||
"id": 300308,
|
||||
"assignment_id": 3003,
|
||||
"question_bank_id": 1802,
|
||||
"question_order": 8,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1775642400000
|
||||
},
|
||||
{
|
||||
"id": 300401,
|
||||
"assignment_id": 3004,
|
||||
"question_bank_id": 1201,
|
||||
"question_order": 1,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776160800000
|
||||
},
|
||||
{
|
||||
"id": 300402,
|
||||
"assignment_id": 3004,
|
||||
"question_bank_id": 1202,
|
||||
"question_order": 2,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776160800000
|
||||
},
|
||||
{
|
||||
"id": 300403,
|
||||
"assignment_id": 3004,
|
||||
"question_bank_id": 1203,
|
||||
"question_order": 3,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776160800000
|
||||
},
|
||||
{
|
||||
"id": 300404,
|
||||
"assignment_id": 3004,
|
||||
"question_bank_id": 1301,
|
||||
"question_order": 4,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776160800000
|
||||
},
|
||||
{
|
||||
"id": 300405,
|
||||
"assignment_id": 3004,
|
||||
"question_bank_id": 1302,
|
||||
"question_order": 5,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776160800000
|
||||
},
|
||||
{
|
||||
"id": 300406,
|
||||
"assignment_id": 3004,
|
||||
"question_bank_id": 1303,
|
||||
"question_order": 6,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776160800000
|
||||
},
|
||||
{
|
||||
"id": 300407,
|
||||
"assignment_id": 3004,
|
||||
"question_bank_id": 1502,
|
||||
"question_order": 7,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776160800000
|
||||
},
|
||||
{
|
||||
"id": 300408,
|
||||
"assignment_id": 3004,
|
||||
"question_bank_id": 1701,
|
||||
"question_order": 8,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776160800000
|
||||
},
|
||||
{
|
||||
"id": 300501,
|
||||
"assignment_id": 3005,
|
||||
"question_bank_id": 1601,
|
||||
"question_order": 1,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776506400000
|
||||
},
|
||||
{
|
||||
"id": 300502,
|
||||
"assignment_id": 3005,
|
||||
"question_bank_id": 1602,
|
||||
"question_order": 2,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776506400000
|
||||
},
|
||||
{
|
||||
"id": 300503,
|
||||
"assignment_id": 3005,
|
||||
"question_bank_id": 1603,
|
||||
"question_order": 3,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776506400000
|
||||
},
|
||||
{
|
||||
"id": 300504,
|
||||
"assignment_id": 3005,
|
||||
"question_bank_id": 1611,
|
||||
"question_order": 4,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776506400000
|
||||
},
|
||||
{
|
||||
"id": 300505,
|
||||
"assignment_id": 3005,
|
||||
"question_bank_id": 1612,
|
||||
"question_order": 5,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776506400000
|
||||
},
|
||||
{
|
||||
"id": 300506,
|
||||
"assignment_id": 3005,
|
||||
"question_bank_id": 1613,
|
||||
"question_order": 6,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776506400000
|
||||
},
|
||||
{
|
||||
"id": 300507,
|
||||
"assignment_id": 3005,
|
||||
"question_bank_id": 1804,
|
||||
"question_order": 7,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776506400000
|
||||
},
|
||||
{
|
||||
"id": 300508,
|
||||
"assignment_id": 3005,
|
||||
"question_bank_id": 1805,
|
||||
"question_order": 8,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1776506400000
|
||||
},
|
||||
{
|
||||
"id": 300601,
|
||||
"assignment_id": 3006,
|
||||
"question_bank_id": 1501,
|
||||
"question_order": 1,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777197600000
|
||||
},
|
||||
{
|
||||
"id": 300602,
|
||||
"assignment_id": 3006,
|
||||
"question_bank_id": 1502,
|
||||
"question_order": 2,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777197600000
|
||||
},
|
||||
{
|
||||
"id": 300603,
|
||||
"assignment_id": 3006,
|
||||
"question_bank_id": 1503,
|
||||
"question_order": 3,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777197600000
|
||||
},
|
||||
{
|
||||
"id": 300604,
|
||||
"assignment_id": 3006,
|
||||
"question_bank_id": 1511,
|
||||
"question_order": 4,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777197600000
|
||||
},
|
||||
{
|
||||
"id": 300605,
|
||||
"assignment_id": 3006,
|
||||
"question_bank_id": 1512,
|
||||
"question_order": 5,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777197600000
|
||||
},
|
||||
{
|
||||
"id": 300606,
|
||||
"assignment_id": 3006,
|
||||
"question_bank_id": 1513,
|
||||
"question_order": 6,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777197600000
|
||||
},
|
||||
{
|
||||
"id": 300607,
|
||||
"assignment_id": 3006,
|
||||
"question_bank_id": 1804,
|
||||
"question_order": 7,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777197600000
|
||||
},
|
||||
{
|
||||
"id": 300608,
|
||||
"assignment_id": 3006,
|
||||
"question_bank_id": 1702,
|
||||
"question_order": 8,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777197600000
|
||||
},
|
||||
{
|
||||
"id": 300701,
|
||||
"assignment_id": 3007,
|
||||
"question_bank_id": 1411,
|
||||
"question_order": 1,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777456800000
|
||||
},
|
||||
{
|
||||
"id": 300702,
|
||||
"assignment_id": 3007,
|
||||
"question_bank_id": 1412,
|
||||
"question_order": 2,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777456800000
|
||||
},
|
||||
{
|
||||
"id": 300703,
|
||||
"assignment_id": 3007,
|
||||
"question_bank_id": 1413,
|
||||
"question_order": 3,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777456800000
|
||||
},
|
||||
{
|
||||
"id": 300704,
|
||||
"assignment_id": 3007,
|
||||
"question_bank_id": 1414,
|
||||
"question_order": 4,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777456800000
|
||||
},
|
||||
{
|
||||
"id": 300705,
|
||||
"assignment_id": 3007,
|
||||
"question_bank_id": 1415,
|
||||
"question_order": 5,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777456800000
|
||||
},
|
||||
{
|
||||
"id": 300706,
|
||||
"assignment_id": 3007,
|
||||
"question_bank_id": 1416,
|
||||
"question_order": 6,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777456800000
|
||||
},
|
||||
{
|
||||
"id": 300707,
|
||||
"assignment_id": 3007,
|
||||
"question_bank_id": 1802,
|
||||
"question_order": 7,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777456800000
|
||||
},
|
||||
{
|
||||
"id": 300708,
|
||||
"assignment_id": 3007,
|
||||
"question_bank_id": 1422,
|
||||
"question_order": 8,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1777456800000
|
||||
},
|
||||
{
|
||||
"id": 300801,
|
||||
"assignment_id": 3008,
|
||||
"question_bank_id": 1004,
|
||||
"question_order": 1,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1778061600000
|
||||
},
|
||||
{
|
||||
"id": 300802,
|
||||
"assignment_id": 3008,
|
||||
"question_bank_id": 1106,
|
||||
"question_order": 2,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1778061600000
|
||||
},
|
||||
{
|
||||
"id": 300803,
|
||||
"assignment_id": 3008,
|
||||
"question_bank_id": 1303,
|
||||
"question_order": 3,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1778061600000
|
||||
},
|
||||
{
|
||||
"id": 300804,
|
||||
"assignment_id": 3008,
|
||||
"question_bank_id": 1416,
|
||||
"question_order": 4,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1778061600000
|
||||
},
|
||||
{
|
||||
"id": 300805,
|
||||
"assignment_id": 3008,
|
||||
"question_bank_id": 1503,
|
||||
"question_order": 5,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1778061600000
|
||||
},
|
||||
{
|
||||
"id": 300806,
|
||||
"assignment_id": 3008,
|
||||
"question_bank_id": 1613,
|
||||
"question_order": 6,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1778061600000
|
||||
},
|
||||
{
|
||||
"id": 300807,
|
||||
"assignment_id": 3008,
|
||||
"question_bank_id": 1712,
|
||||
"question_order": 7,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1778061600000
|
||||
},
|
||||
{
|
||||
"id": 300808,
|
||||
"assignment_id": 3008,
|
||||
"question_bank_id": 1805,
|
||||
"question_order": 8,
|
||||
"maximum_marks": 1,
|
||||
"rubric": null,
|
||||
"created_at": 1778061600000
|
||||
}
|
||||
]
|
||||
98
Mock-Data/assignments.json
Normal file
98
Mock-Data/assignments.json
Normal file
@@ -0,0 +1,98 @@
|
||||
[
|
||||
{
|
||||
"id": 3001,
|
||||
"name": "HW1 \u2014 Place Value Warmup",
|
||||
"teacher_id": 100,
|
||||
"topic": "Place Value",
|
||||
"due_date": 1775260740000,
|
||||
"status": "CLOSED",
|
||||
"maximum_marks": 8,
|
||||
"is_deleted": false,
|
||||
"created_at": 1774605600000,
|
||||
"updated_at": 1774605600000
|
||||
},
|
||||
{
|
||||
"id": 3002,
|
||||
"name": "HW2 \u2014 Arithmetic Practice",
|
||||
"teacher_id": 100,
|
||||
"topic": "Arithmetic",
|
||||
"due_date": 1775779140000,
|
||||
"status": "CLOSED",
|
||||
"maximum_marks": 8,
|
||||
"is_deleted": false,
|
||||
"created_at": 1775124000000,
|
||||
"updated_at": 1775124000000
|
||||
},
|
||||
{
|
||||
"id": 3003,
|
||||
"name": "HW3 \u2014 Fractions Foundations",
|
||||
"teacher_id": 100,
|
||||
"topic": "Fractions",
|
||||
"due_date": 1776297540000,
|
||||
"status": "CLOSED",
|
||||
"maximum_marks": 8,
|
||||
"is_deleted": false,
|
||||
"created_at": 1775642400000,
|
||||
"updated_at": 1775642400000
|
||||
},
|
||||
{
|
||||
"id": 3004,
|
||||
"name": "HW4 \u2014 Negatives & BIDMAS",
|
||||
"teacher_id": 100,
|
||||
"topic": "BIDMAS",
|
||||
"due_date": 1776815940000,
|
||||
"status": "CLOSED",
|
||||
"maximum_marks": 8,
|
||||
"is_deleted": false,
|
||||
"created_at": 1776160800000,
|
||||
"updated_at": 1776160800000
|
||||
},
|
||||
{
|
||||
"id": 3005,
|
||||
"name": "HW5 \u2014 Geometry Basics",
|
||||
"teacher_id": 100,
|
||||
"topic": "Geometry",
|
||||
"due_date": 1777161540000,
|
||||
"status": "CLOSED",
|
||||
"maximum_marks": 8,
|
||||
"is_deleted": false,
|
||||
"created_at": 1776506400000,
|
||||
"updated_at": 1776506400000
|
||||
},
|
||||
{
|
||||
"id": 3006,
|
||||
"name": "HW6 \u2014 Algebra & Sequences",
|
||||
"teacher_id": 100,
|
||||
"topic": "Algebra",
|
||||
"due_date": 1777852740000,
|
||||
"status": "PUBLISHED",
|
||||
"maximum_marks": 8,
|
||||
"is_deleted": false,
|
||||
"created_at": 1777197600000,
|
||||
"updated_at": 1777197600000
|
||||
},
|
||||
{
|
||||
"id": 3007,
|
||||
"name": "HW7 \u2014 Adding Fractions (test prep)",
|
||||
"teacher_id": 100,
|
||||
"topic": "Fractions",
|
||||
"due_date": 1778111940000,
|
||||
"status": "PUBLISHED",
|
||||
"maximum_marks": 8,
|
||||
"is_deleted": false,
|
||||
"created_at": 1777456800000,
|
||||
"updated_at": 1777456800000
|
||||
},
|
||||
{
|
||||
"id": 3008,
|
||||
"name": "HW8 \u2014 Mixed Revision",
|
||||
"teacher_id": 100,
|
||||
"topic": "Mixed",
|
||||
"due_date": 1778716740000,
|
||||
"status": "DRAFT",
|
||||
"maximum_marks": 8,
|
||||
"is_deleted": false,
|
||||
"created_at": 1778061600000,
|
||||
"updated_at": 1778061600000
|
||||
}
|
||||
]
|
||||
101
Mock-Data/classroom.json
Normal file
101
Mock-Data/classroom.json
Normal file
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"classroom": {
|
||||
"id": 500,
|
||||
"name": "Year 6 \u2014 Maths Set 1",
|
||||
"organization_id": 1,
|
||||
"tutor_id": 100,
|
||||
"invite_code": "Y6MATHS1",
|
||||
"target_level": 6,
|
||||
"archived": false,
|
||||
"hide_just_answer": false,
|
||||
"is_deleted": false,
|
||||
"created_at": 1772445600000,
|
||||
"updated_at": 1772445600000
|
||||
},
|
||||
"tutor": {
|
||||
"id": 100,
|
||||
"fullname": "Sarah Johnson",
|
||||
"email": "sarah.johnson@boostai.example",
|
||||
"username": "sjohnson",
|
||||
"role": "tutor",
|
||||
"active": true,
|
||||
"is_test": false,
|
||||
"is_deleted": false,
|
||||
"created_at": 1762077600000,
|
||||
"updated_at": 1762077600000
|
||||
},
|
||||
"classroom_student_rs": [
|
||||
{
|
||||
"id": 801,
|
||||
"classroom_id": 500,
|
||||
"student_id": 201,
|
||||
"created_at": 1772877600000
|
||||
},
|
||||
{
|
||||
"id": 802,
|
||||
"classroom_id": 500,
|
||||
"student_id": 202,
|
||||
"created_at": 1772877600000
|
||||
},
|
||||
{
|
||||
"id": 803,
|
||||
"classroom_id": 500,
|
||||
"student_id": 203,
|
||||
"created_at": 1772877600000
|
||||
},
|
||||
{
|
||||
"id": 804,
|
||||
"classroom_id": 500,
|
||||
"student_id": 204,
|
||||
"created_at": 1772877600000
|
||||
},
|
||||
{
|
||||
"id": 805,
|
||||
"classroom_id": 500,
|
||||
"student_id": 205,
|
||||
"created_at": 1772877600000
|
||||
},
|
||||
{
|
||||
"id": 806,
|
||||
"classroom_id": 500,
|
||||
"student_id": 206,
|
||||
"created_at": 1772877600000
|
||||
},
|
||||
{
|
||||
"id": 807,
|
||||
"classroom_id": 500,
|
||||
"student_id": 207,
|
||||
"created_at": 1772877600000
|
||||
},
|
||||
{
|
||||
"id": 808,
|
||||
"classroom_id": 500,
|
||||
"student_id": 208,
|
||||
"created_at": 1772877600000
|
||||
},
|
||||
{
|
||||
"id": 809,
|
||||
"classroom_id": 500,
|
||||
"student_id": 209,
|
||||
"created_at": 1772877600000
|
||||
},
|
||||
{
|
||||
"id": 810,
|
||||
"classroom_id": 500,
|
||||
"student_id": 210,
|
||||
"created_at": 1772877600000
|
||||
},
|
||||
{
|
||||
"id": 811,
|
||||
"classroom_id": 500,
|
||||
"student_id": 211,
|
||||
"created_at": 1772877600000
|
||||
},
|
||||
{
|
||||
"id": 812,
|
||||
"classroom_id": 500,
|
||||
"student_id": 212,
|
||||
"created_at": 1772877600000
|
||||
}
|
||||
]
|
||||
}
|
||||
24386
Mock-Data/dataset.json
Normal file
24386
Mock-Data/dataset.json
Normal file
File diff suppressed because it is too large
Load Diff
830
Mock-Data/generate.py
Normal file
830
Mock-Data/generate.py
Normal file
@@ -0,0 +1,830 @@
|
||||
"""
|
||||
Mock pupil-history dataset generator for the BoostAI "Learning Path Agent"
|
||||
hackathon challenge.
|
||||
|
||||
Outputs JSON files under the same directory, mirroring the production
|
||||
SQLAlchemy schema in elevenplus-backend/src/app/models/. The generator is
|
||||
deterministic (seeded RNG) so re-runs produce identical output.
|
||||
|
||||
Run:
|
||||
python3 generate.py
|
||||
|
||||
Outputs:
|
||||
classroom.json, students.json, question_bank.json, assignments.json,
|
||||
assignment_questions.json, assignment_assignees.json,
|
||||
student_answers.json, activity_logs.json, dataset.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
# Reference date — keep in sync with the hackathon brief.
|
||||
TODAY = datetime(2026, 5, 1, 9, 0, 0, tzinfo=timezone.utc)
|
||||
|
||||
OUT_DIR = Path(__file__).parent
|
||||
RNG = random.Random(20260501)
|
||||
|
||||
|
||||
def ms(dt: datetime) -> int:
|
||||
return int(dt.timestamp() * 1000)
|
||||
|
||||
|
||||
def days_ago(n: float, hour: int = 10, minute: int = 0) -> datetime:
|
||||
return (TODAY - timedelta(days=n)).replace(hour=hour, minute=minute, second=0, microsecond=0)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Classroom + Tutor + Students
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
TUTOR = {
|
||||
"id": 100,
|
||||
"fullname": "Sarah Johnson",
|
||||
"email": "sarah.johnson@boostai.example",
|
||||
"username": "sjohnson",
|
||||
"role": "tutor",
|
||||
"active": True,
|
||||
"is_test": False,
|
||||
"is_deleted": False,
|
||||
"created_at": ms(days_ago(180)),
|
||||
"updated_at": ms(days_ago(180)),
|
||||
}
|
||||
|
||||
CLASSROOM = {
|
||||
"id": 500,
|
||||
"name": "Year 6 — Maths Set 1",
|
||||
"organization_id": 1,
|
||||
"tutor_id": TUTOR["id"],
|
||||
"invite_code": "Y6MATHS1",
|
||||
"target_level": 6,
|
||||
"archived": False,
|
||||
"hide_just_answer": False,
|
||||
"is_deleted": False,
|
||||
"created_at": ms(days_ago(60)),
|
||||
"updated_at": ms(days_ago(60)),
|
||||
}
|
||||
|
||||
# 12 students. _persona is a hackathon annotation (not in the production
|
||||
# schema) — used to drive answer generation below and to document expected
|
||||
# agent output. Strip the underscore-prefixed fields if seeding the real DB.
|
||||
STUDENTS_RAW = [
|
||||
(201, "Aisha Khan", "fraction_inversion"),
|
||||
(202, "Ben Carter", "place_value_gaps"),
|
||||
(203, "Chen Wei", "rushed_careless"),
|
||||
(204, "Daniela Rossi", "solve_together_dependent"),
|
||||
(205, "Elif Demir", "word_problem_weak"),
|
||||
(206, "Felix Brown", "stable_strong"),
|
||||
(207, "Grace Park", "stable_strong"),
|
||||
(208, "Harry Singh", "stable_mid"),
|
||||
(209, "Isla Nakamura", "stable_mid"),
|
||||
(210, "Jaden Williams", "stable_mid"),
|
||||
(211, "Kira Patel", "stable_weak"),
|
||||
(212, "Liam O'Connor", "stable_weak"),
|
||||
]
|
||||
|
||||
STUDENTS = []
|
||||
CLASSROOM_STUDENT_RS = []
|
||||
for sid, fullname, persona in STUDENTS_RAW:
|
||||
first = fullname.split()[0].lower().replace("'", "")
|
||||
STUDENTS.append({
|
||||
"id": sid,
|
||||
"fullname": fullname,
|
||||
"email": f"{first}.{sid}@boostai.example",
|
||||
"username": f"{first}{sid}",
|
||||
"role": "student",
|
||||
"active": True,
|
||||
"is_test": False,
|
||||
"is_deleted": False,
|
||||
"created_at": ms(days_ago(55)),
|
||||
"updated_at": ms(days_ago(55)),
|
||||
"_persona": persona,
|
||||
})
|
||||
CLASSROOM_STUDENT_RS.append({
|
||||
"id": 600 + sid,
|
||||
"classroom_id": CLASSROOM["id"],
|
||||
"student_id": sid,
|
||||
"created_at": ms(days_ago(55)),
|
||||
})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Question Bank — Maths, 11+
|
||||
# Fields mirror question_bank.py.
|
||||
# _wrong_answer_map: hackathon helper — for misconception personas, the
|
||||
# answer they typically produce. Not in production schema.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
QUESTION_BANK = [
|
||||
# ---- Place Value (tens, hundreds, thousands, decimals) ----
|
||||
{"id": 1001, "topic": "Place Value", "sub_topic": "Multi-digit numbers", "tag": None,
|
||||
"difficulty": "EASY",
|
||||
"question_text": "What is the value of the digit 7 in the number 4,732?",
|
||||
"correct_answer": "700",
|
||||
"_wrong_answers": {"place_value_gaps": "70"}},
|
||||
{"id": 1002, "topic": "Place Value", "sub_topic": "Multi-digit numbers", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "Round 24,587 to the nearest thousand.",
|
||||
"correct_answer": "25000",
|
||||
"_wrong_answers": {"place_value_gaps": "24000"}},
|
||||
{"id": 1003, "topic": "Place Value", "sub_topic": "Decimals", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "Write 0.07 as a fraction in its simplest form.",
|
||||
"correct_answer": "7/100",
|
||||
"_wrong_answers": {"place_value_gaps": "7/10"}},
|
||||
{"id": 1004, "topic": "Place Value", "sub_topic": "Decimals", "tag": None,
|
||||
"difficulty": "HARD",
|
||||
"question_text": "What is 0.3 + 0.07?",
|
||||
"correct_answer": "0.37",
|
||||
"_wrong_answers": {"place_value_gaps": "0.10"}},
|
||||
|
||||
# ---- Arithmetic ----
|
||||
{"id": 1101, "topic": "Arithmetic", "sub_topic": "Addition", "tag": None,
|
||||
"difficulty": "EASY",
|
||||
"question_text": "Calculate 246 + 137.",
|
||||
"correct_answer": "383",
|
||||
"_wrong_answers": {"rushed_careless": "373"}},
|
||||
{"id": 1102, "topic": "Arithmetic", "sub_topic": "Subtraction", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "Calculate 503 - 47.",
|
||||
"correct_answer": "456",
|
||||
"_wrong_answers": {"place_value_gaps": "544", "rushed_careless": "466"}},
|
||||
{"id": 1103, "topic": "Arithmetic", "sub_topic": "Subtraction", "tag": None,
|
||||
"difficulty": "HARD",
|
||||
"question_text": "Calculate 4,002 - 1,375.",
|
||||
"correct_answer": "2627",
|
||||
"_wrong_answers": {"place_value_gaps": "3737", "rushed_careless": "2617"}},
|
||||
{"id": 1104, "topic": "Arithmetic", "sub_topic": "Multiplication", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "Calculate 28 x 7.",
|
||||
"correct_answer": "196",
|
||||
"_wrong_answers": {"rushed_careless": "186"}},
|
||||
{"id": 1105, "topic": "Arithmetic", "sub_topic": "Division", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "Calculate 144 / 6.",
|
||||
"correct_answer": "24",
|
||||
"_wrong_answers": {"rushed_careless": "26"}},
|
||||
{"id": 1106, "topic": "Arithmetic", "sub_topic": "Multiplication", "tag": None,
|
||||
"difficulty": "HARD",
|
||||
"question_text": "Calculate 156 x 24.",
|
||||
"correct_answer": "3744",
|
||||
"_wrong_answers": {"rushed_careless": "3724", "place_value_gaps": "374"}},
|
||||
|
||||
# ---- Negative Numbers ----
|
||||
{"id": 1201, "topic": "Negative Numbers", "sub_topic": "Addition", "tag": None,
|
||||
"difficulty": "EASY",
|
||||
"question_text": "What is -5 + 8?",
|
||||
"correct_answer": "3"},
|
||||
{"id": 1202, "topic": "Negative Numbers", "sub_topic": "Subtraction", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "What is 4 - 9?",
|
||||
"correct_answer": "-5"},
|
||||
{"id": 1203, "topic": "Negative Numbers", "sub_topic": "Mixed", "tag": None,
|
||||
"difficulty": "HARD",
|
||||
"question_text": "What is -7 - (-3)?",
|
||||
"correct_answer": "-4"},
|
||||
|
||||
# ---- BIDMAS ----
|
||||
{"id": 1301, "topic": "BIDMAS", "sub_topic": None, "tag": None,
|
||||
"difficulty": "EASY",
|
||||
"question_text": "Calculate 5 + 3 x 2.",
|
||||
"correct_answer": "11"},
|
||||
{"id": 1302, "topic": "BIDMAS", "sub_topic": None, "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "Calculate (8 - 3) x 4 + 2.",
|
||||
"correct_answer": "22"},
|
||||
{"id": 1303, "topic": "BIDMAS", "sub_topic": None, "tag": None,
|
||||
"difficulty": "HARD",
|
||||
"question_text": "Calculate 24 / (2 + 4) + 3 x 5.",
|
||||
"correct_answer": "19",
|
||||
"_wrong_answers": {"rushed_careless": "17"}},
|
||||
|
||||
# ---- Fractions: Equivalent ----
|
||||
{"id": 1401, "topic": "Fractions", "sub_topic": "Equivalent", "tag": None,
|
||||
"difficulty": "EASY",
|
||||
"question_text": "Which fraction is equivalent to 2/4?",
|
||||
"correct_answer": "1/2"},
|
||||
{"id": 1402, "topic": "Fractions", "sub_topic": "Equivalent", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "Simplify 12/18 to its lowest terms.",
|
||||
"correct_answer": "2/3"},
|
||||
{"id": 1403, "topic": "Fractions", "sub_topic": "Equivalent", "tag": None,
|
||||
"difficulty": "HARD",
|
||||
"question_text": "Which is larger: 3/5 or 5/8? Give your answer.",
|
||||
"correct_answer": "5/8"},
|
||||
|
||||
# ---- Fractions: Add/Subtract (CORE for fraction_inversion persona) ----
|
||||
{"id": 1411, "topic": "Fractions", "sub_topic": "Add", "tag": None,
|
||||
"difficulty": "EASY",
|
||||
"question_text": "What is 1/2 + 1/3?",
|
||||
"correct_answer": "5/6",
|
||||
"_wrong_answers": {"fraction_inversion": "2/5"}},
|
||||
{"id": 1412, "topic": "Fractions", "sub_topic": "Add", "tag": None,
|
||||
"difficulty": "EASY",
|
||||
"question_text": "What is 1/4 + 1/2?",
|
||||
"correct_answer": "3/4",
|
||||
"_wrong_answers": {"fraction_inversion": "2/6"}},
|
||||
{"id": 1413, "topic": "Fractions", "sub_topic": "Add", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "What is 2/5 + 1/3?",
|
||||
"correct_answer": "11/15",
|
||||
"_wrong_answers": {"fraction_inversion": "3/8"}},
|
||||
{"id": 1414, "topic": "Fractions", "sub_topic": "Add", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "What is 3/4 + 1/6?",
|
||||
"correct_answer": "11/12",
|
||||
"_wrong_answers": {"fraction_inversion": "4/10"}},
|
||||
{"id": 1415, "topic": "Fractions", "sub_topic": "Subtract", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "What is 5/6 - 1/3?",
|
||||
"correct_answer": "1/2",
|
||||
"_wrong_answers": {"fraction_inversion": "4/3"}},
|
||||
{"id": 1416, "topic": "Fractions", "sub_topic": "Add", "tag": None,
|
||||
"difficulty": "HARD",
|
||||
"question_text": "What is 7/12 + 5/8?",
|
||||
"correct_answer": "29/24",
|
||||
"_wrong_answers": {"fraction_inversion": "12/20"}},
|
||||
|
||||
# ---- Fractions: Multiply ----
|
||||
{"id": 1421, "topic": "Fractions", "sub_topic": "Multiply", "tag": None,
|
||||
"difficulty": "EASY",
|
||||
"question_text": "What is 1/2 x 1/3?",
|
||||
"correct_answer": "1/6",
|
||||
"_wrong_answers": {"fraction_inversion": "2/3"}},
|
||||
{"id": 1422, "topic": "Fractions", "sub_topic": "Multiply", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "What is 2/3 x 3/4?",
|
||||
"correct_answer": "1/2",
|
||||
"_wrong_answers": {"fraction_inversion": "5/7"}},
|
||||
{"id": 1423, "topic": "Fractions", "sub_topic": "Multiply", "tag": None,
|
||||
"difficulty": "HARD",
|
||||
"question_text": "What is 4/5 of 35?",
|
||||
"correct_answer": "28",
|
||||
"_wrong_answers": {"fraction_inversion": "20"}},
|
||||
|
||||
# ---- Algebra: Simple Equations ----
|
||||
{"id": 1501, "topic": "Algebra", "sub_topic": "Simple Equations", "tag": None,
|
||||
"difficulty": "EASY",
|
||||
"question_text": "Solve x + 7 = 12.",
|
||||
"correct_answer": "5"},
|
||||
{"id": 1502, "topic": "Algebra", "sub_topic": "Simple Equations", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "Solve 3x - 4 = 17.",
|
||||
"correct_answer": "7",
|
||||
"_wrong_answers": {"rushed_careless": "8"}},
|
||||
{"id": 1503, "topic": "Algebra", "sub_topic": "Simple Equations", "tag": None,
|
||||
"difficulty": "HARD",
|
||||
"question_text": "Solve 2(x + 3) = 18.",
|
||||
"correct_answer": "6"},
|
||||
|
||||
# ---- Algebra: Sequences ----
|
||||
{"id": 1511, "topic": "Algebra", "sub_topic": "Sequences", "tag": None,
|
||||
"difficulty": "EASY",
|
||||
"question_text": "What is the next term: 2, 5, 8, 11, ___ ?",
|
||||
"correct_answer": "14"},
|
||||
{"id": 1512, "topic": "Algebra", "sub_topic": "Sequences", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "Find the 10th term of the sequence 4, 7, 10, 13, ...",
|
||||
"correct_answer": "31"},
|
||||
{"id": 1513, "topic": "Algebra", "sub_topic": "Sequences", "tag": None,
|
||||
"difficulty": "HARD",
|
||||
"question_text": "What is the nth term of the sequence 5, 8, 11, 14, ...?",
|
||||
"correct_answer": "3n+2"},
|
||||
|
||||
# ---- Geometry: Area & Perimeter ----
|
||||
{"id": 1601, "topic": "Geometry", "sub_topic": "Perimeter", "tag": None,
|
||||
"difficulty": "EASY",
|
||||
"question_text": "What is the perimeter of a square with side length 7 cm?",
|
||||
"correct_answer": "28"},
|
||||
{"id": 1602, "topic": "Geometry", "sub_topic": "Area", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "What is the area of a rectangle 8 cm by 5 cm?",
|
||||
"correct_answer": "40"},
|
||||
{"id": 1603, "topic": "Geometry", "sub_topic": "Area", "tag": None,
|
||||
"difficulty": "HARD",
|
||||
"question_text": "A right-angled triangle has base 6 cm and height 9 cm. What is its area?",
|
||||
"correct_answer": "27"},
|
||||
|
||||
# ---- Geometry: Angles ----
|
||||
{"id": 1611, "topic": "Geometry", "sub_topic": "Angles", "tag": None,
|
||||
"difficulty": "EASY",
|
||||
"question_text": "Two angles on a straight line are 110° and x. What is x?",
|
||||
"correct_answer": "70"},
|
||||
{"id": 1612, "topic": "Geometry", "sub_topic": "Angles", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "The angles of a triangle are 45°, 60° and x. What is x?",
|
||||
"correct_answer": "75"},
|
||||
{"id": 1613, "topic": "Geometry", "sub_topic": "Angles", "tag": None,
|
||||
"difficulty": "HARD",
|
||||
"question_text": "What is the sum of interior angles of a hexagon?",
|
||||
"correct_answer": "720"},
|
||||
|
||||
# ---- Data: Mean / Median / Mode ----
|
||||
{"id": 1701, "topic": "Data", "sub_topic": "Mean", "tag": None,
|
||||
"difficulty": "EASY",
|
||||
"question_text": "Find the mean of 4, 6, 8, 10, 12.",
|
||||
"correct_answer": "8"},
|
||||
{"id": 1702, "topic": "Data", "sub_topic": "Median", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "Find the median of 3, 7, 2, 9, 5.",
|
||||
"correct_answer": "5"},
|
||||
{"id": 1703, "topic": "Data", "sub_topic": "Mode", "tag": None,
|
||||
"difficulty": "EASY",
|
||||
"question_text": "Find the mode of 2, 3, 3, 5, 7, 7, 7, 8.",
|
||||
"correct_answer": "7"},
|
||||
|
||||
# ---- Data: Probability ----
|
||||
{"id": 1711, "topic": "Data", "sub_topic": "Probability", "tag": None,
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "A bag has 3 red and 5 blue marbles. What is the probability of red?",
|
||||
"correct_answer": "3/8"},
|
||||
{"id": 1712, "topic": "Data", "sub_topic": "Probability", "tag": None,
|
||||
"difficulty": "HARD",
|
||||
"question_text": "A fair die is rolled. What is the probability of an even number greater than 2?",
|
||||
"correct_answer": "1/3"},
|
||||
|
||||
# ---- Word problems (cross-topic) ----
|
||||
{"id": 1801, "topic": "Arithmetic", "sub_topic": "Word problem", "tag": "word_problem",
|
||||
"difficulty": "EASY",
|
||||
"question_text": "Tom has 24 apples. He gives 9 to his friend. How many does he have left?",
|
||||
"correct_answer": "15"},
|
||||
{"id": 1802, "topic": "Fractions", "sub_topic": "Word problem", "tag": "word_problem",
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "A pizza is cut into 8 slices. Sara eats 1/4 and Tom eats 3/8. What fraction is left?",
|
||||
"correct_answer": "3/8",
|
||||
"_wrong_answers": {"word_problem_weak": "1/2", "fraction_inversion": "4/12"}},
|
||||
{"id": 1803, "topic": "Arithmetic", "sub_topic": "Word problem", "tag": "word_problem",
|
||||
"difficulty": "MEDIUM",
|
||||
"question_text": "A train ticket costs £8.50. How much do 6 tickets cost?",
|
||||
"correct_answer": "51",
|
||||
"_wrong_answers": {"word_problem_weak": "48", "rushed_careless": "50"}},
|
||||
{"id": 1804, "topic": "Algebra", "sub_topic": "Word problem", "tag": "word_problem",
|
||||
"difficulty": "HARD",
|
||||
"question_text": "Three consecutive numbers add up to 72. What is the smallest number?",
|
||||
"correct_answer": "23",
|
||||
"_wrong_answers": {"word_problem_weak": "24"}},
|
||||
{"id": 1805, "topic": "Geometry", "sub_topic": "Word problem", "tag": "word_problem",
|
||||
"difficulty": "HARD",
|
||||
"question_text": "A rectangular garden is 12 m long and 4 m shorter than it is long. What is its area?",
|
||||
"correct_answer": "96",
|
||||
"_wrong_answers": {"word_problem_weak": "48"}},
|
||||
]
|
||||
|
||||
# Fill in remaining standard fields for every bank entry.
|
||||
for q in QUESTION_BANK:
|
||||
q.setdefault("category", "Math")
|
||||
q.setdefault("year_level", "Year 6")
|
||||
q.setdefault("source", "BOOST")
|
||||
q.setdefault("source_description", None)
|
||||
q.setdefault("teacher_id", TUTOR["id"])
|
||||
q.setdefault("maximum_marks", 1)
|
||||
q.setdefault("rubric", None)
|
||||
q.setdefault("step_by_step_solution", None)
|
||||
q.setdefault("image_url", None)
|
||||
q.setdefault("is_deleted", False)
|
||||
q.setdefault("created_at", ms(days_ago(40)))
|
||||
q.setdefault("updated_at", ms(days_ago(40)))
|
||||
q.setdefault("_wrong_answers", {})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Assignments + Assignment Questions
|
||||
#
|
||||
# 8 assignments. Distribution: 5 CLOSED (past), 2 PUBLISHED (in-flight),
|
||||
# 1 DRAFT (future). The deadline-pressure assignment is a Fractions/Add
|
||||
# assignment due in 5 days — drives the bonus Early Warning topic correlation.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
ASSIGNMENT_DEFS = [
|
||||
# (id, name, focus_topic, due_offset_days, status, question_bank_ids)
|
||||
(3001, "HW1 — Place Value Warmup", "Place Value", -28, "CLOSED",
|
||||
[1001, 1002, 1003, 1101, 1102, 1301, 1401, 1701]),
|
||||
(3002, "HW2 — Arithmetic Practice", "Arithmetic", -22, "CLOSED",
|
||||
[1101, 1102, 1103, 1104, 1105, 1106, 1801, 1803]),
|
||||
(3003, "HW3 — Fractions Foundations", "Fractions", -16, "CLOSED",
|
||||
[1401, 1402, 1411, 1412, 1413, 1421, 1422, 1802]),
|
||||
(3004, "HW4 — Negatives & BIDMAS", "BIDMAS", -10, "CLOSED",
|
||||
[1201, 1202, 1203, 1301, 1302, 1303, 1502, 1701]),
|
||||
(3005, "HW5 — Geometry Basics", "Geometry", -6, "CLOSED",
|
||||
[1601, 1602, 1603, 1611, 1612, 1613, 1804, 1805]),
|
||||
(3006, "HW6 — Algebra & Sequences", "Algebra", 2, "PUBLISHED",
|
||||
[1501, 1502, 1503, 1511, 1512, 1513, 1804, 1702]),
|
||||
# The deadline-pressure assignment — bonus Early Warning anchors here.
|
||||
(3007, "HW7 — Adding Fractions (test prep)", "Fractions", 5, "PUBLISHED",
|
||||
[1411, 1412, 1413, 1414, 1415, 1416, 1802, 1422]),
|
||||
(3008, "HW8 — Mixed Revision", "Mixed", 12, "DRAFT",
|
||||
[1004, 1106, 1303, 1416, 1503, 1613, 1712, 1805]),
|
||||
]
|
||||
|
||||
ASSIGNMENTS = []
|
||||
ASSIGNMENT_QUESTIONS = []
|
||||
for aid, name, topic, offset, status, qb_ids in ASSIGNMENT_DEFS:
|
||||
created_offset = max(offset - 7, -45) # created ~1 week before due
|
||||
ASSIGNMENTS.append({
|
||||
"id": aid,
|
||||
"name": name,
|
||||
"teacher_id": TUTOR["id"],
|
||||
"topic": topic,
|
||||
"due_date": ms(days_ago(-offset, hour=23, minute=59)),
|
||||
"status": status,
|
||||
"maximum_marks": len(qb_ids),
|
||||
"is_deleted": False,
|
||||
"created_at": ms(days_ago(-created_offset)),
|
||||
"updated_at": ms(days_ago(-created_offset)),
|
||||
})
|
||||
for order, qb_id in enumerate(qb_ids, start=1):
|
||||
ASSIGNMENT_QUESTIONS.append({
|
||||
"id": aid * 100 + order,
|
||||
"assignment_id": aid,
|
||||
"question_bank_id": qb_id,
|
||||
"question_order": order,
|
||||
"maximum_marks": 1,
|
||||
"rubric": None,
|
||||
"created_at": ms(days_ago(-created_offset)),
|
||||
})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Assignment Assignees (per student × per assignment) + Student Answers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
QB_BY_ID = {q["id"]: q for q in QUESTION_BANK}
|
||||
AQ_BY_ID = {aq["id"]: aq for aq in ASSIGNMENT_QUESTIONS}
|
||||
|
||||
|
||||
def assignee_status_for(assignment_status: str, persona: str, aid: int) -> str:
|
||||
if assignment_status == "DRAFT":
|
||||
return "NOT_STARTED"
|
||||
if assignment_status == "PUBLISHED":
|
||||
# Some students have started it, some not.
|
||||
return "IN_PROGRESS"
|
||||
return "SUBMITTED"
|
||||
|
||||
|
||||
# --- Persona-driven correctness/solve-mode generation -----------------------
|
||||
#
|
||||
# Each persona is a function that, given the assignment, the question, and
|
||||
# the assignment's "week index" (0 = oldest, higher = more recent), returns:
|
||||
# (is_correct, solve_mode, time_seconds, answer_text)
|
||||
# All randomness flows through RNG (seeded), so output is deterministic.
|
||||
|
||||
SOLVE_MODES = ["just_answer", "step_by_step", "solve_together", "handwritten"]
|
||||
|
||||
|
||||
def base_time_for_difficulty(d: str) -> int:
|
||||
return {"EASY": 60, "MEDIUM": 100, "HARD": 160}[d]
|
||||
|
||||
|
||||
def jitter_time(base: int) -> int:
|
||||
return max(15, int(base + RNG.randint(-25, 35)))
|
||||
|
||||
|
||||
def pick_mode_default(persona: str, week_idx: int) -> str:
|
||||
# Most students mostly use just_answer; occasionally step_by_step;
|
||||
# rarely solve_together.
|
||||
r = RNG.random()
|
||||
if r < 0.70:
|
||||
return "just_answer"
|
||||
if r < 0.90:
|
||||
return "step_by_step"
|
||||
if r < 0.97:
|
||||
return "solve_together"
|
||||
return "handwritten"
|
||||
|
||||
|
||||
def answer_for_persona(q: dict, persona: str, force_correct: bool) -> tuple[bool, str]:
|
||||
"""Return (is_correct, answer_text)."""
|
||||
if force_correct:
|
||||
return True, q["correct_answer"]
|
||||
wrong_map = q.get("_wrong_answers", {}) or {}
|
||||
if persona in wrong_map:
|
||||
return False, wrong_map[persona]
|
||||
# Generic wrong answer.
|
||||
return False, q["correct_answer"] + "?"
|
||||
|
||||
|
||||
def gen_answer(student: dict, assignment: dict, aq: dict, q: dict, week_idx: int, total_weeks: int):
|
||||
"""Return a student_answers row dict (or None if assignee hasn't attempted it)."""
|
||||
persona = student["_persona"]
|
||||
difficulty = q["difficulty"]
|
||||
base_time = base_time_for_difficulty(difficulty)
|
||||
is_word = q.get("tag") == "word_problem"
|
||||
is_fraction_op = q["topic"] == "Fractions" and q["sub_topic"] in ("Add", "Subtract", "Multiply")
|
||||
is_place_value = q["topic"] == "Place Value" or (q["topic"] == "Arithmetic" and q["sub_topic"] in ("Subtraction", "Multiplication") and difficulty == "HARD")
|
||||
|
||||
# Default: stable_mid baseline.
|
||||
p_correct = 0.65
|
||||
solve_mode = pick_mode_default(persona, week_idx)
|
||||
answered_at_offset_days = 0 # set below
|
||||
misconception_tag = None
|
||||
|
||||
if persona == "fraction_inversion":
|
||||
if is_fraction_op:
|
||||
# Sharp misconception: very low on fraction ops, declining.
|
||||
p_correct = max(0.03, 0.20 - 0.03 * week_idx)
|
||||
misconception_candidate = "add_tops_add_bottoms" if q["sub_topic"] in ("Add", "Subtract") else "fraction_op_confusion"
|
||||
elif q["topic"] == "Fractions":
|
||||
# Equivalent fractions etc: still shaky.
|
||||
p_correct = 0.25
|
||||
misconception_candidate = "fraction_general_uncertainty"
|
||||
else:
|
||||
p_correct = 0.78
|
||||
misconception_candidate = None
|
||||
elif persona == "place_value_gaps":
|
||||
if is_place_value or (q["topic"] == "Place Value"):
|
||||
p_correct = 0.25
|
||||
misconception_candidate = "place_value_misalignment"
|
||||
else:
|
||||
p_correct = 0.65
|
||||
misconception_candidate = None
|
||||
elif persona == "rushed_careless":
|
||||
# Right method when forced to slow down (step_by_step), wrong when rushed.
|
||||
# In just_answer: 40% correct. In step_by_step: 90% correct.
|
||||
# Time-on-task drops over time (rushing more).
|
||||
# Solve mode mostly just_answer.
|
||||
r = RNG.random()
|
||||
solve_mode = "just_answer" if r < 0.85 else "step_by_step"
|
||||
if solve_mode == "step_by_step":
|
||||
p_correct = 0.90
|
||||
else:
|
||||
p_correct = 0.40
|
||||
misconception_candidate = "arithmetic_slip"
|
||||
# Time decays: week 0 = 0.9 * base, latest = 0.4 * base
|
||||
t_factor = max(0.4, 0.9 - 0.12 * week_idx)
|
||||
base_time = int(base_time * t_factor)
|
||||
elif persona == "solve_together_dependent":
|
||||
# solve_together usage rises sharply over time. Independent
|
||||
# accuracy is low and degrading — student is leaning on scaffolding
|
||||
# more and more.
|
||||
st_prob = 0.08 + 0.18 * week_idx # week 0 ~8%, week 5 ~98%
|
||||
st_prob = min(0.92, st_prob)
|
||||
r = RNG.random()
|
||||
if r < st_prob:
|
||||
solve_mode = "solve_together"
|
||||
p_correct = 0.85
|
||||
else:
|
||||
solve_mode = "just_answer" if RNG.random() < 0.7 else "step_by_step"
|
||||
p_correct = max(0.20, 0.55 - 0.06 * week_idx)
|
||||
misconception_candidate = "scaffolding_dependence"
|
||||
elif persona == "word_problem_weak":
|
||||
if is_word:
|
||||
p_correct = 0.20
|
||||
misconception_candidate = "word_problem_interpretation"
|
||||
else:
|
||||
p_correct = 0.78
|
||||
misconception_candidate = None
|
||||
elif persona == "stable_strong":
|
||||
p_correct = 0.88 if difficulty != "HARD" else 0.78
|
||||
misconception_candidate = None
|
||||
elif persona == "stable_mid":
|
||||
p_correct = 0.65 if difficulty != "HARD" else 0.50
|
||||
misconception_candidate = None
|
||||
elif persona == "stable_weak":
|
||||
p_correct = 0.55 if difficulty != "HARD" else 0.40
|
||||
misconception_candidate = None
|
||||
else:
|
||||
misconception_candidate = None
|
||||
|
||||
# Decide attempted-or-not for IN_PROGRESS assignments.
|
||||
is_correct = RNG.random() < p_correct
|
||||
is_correct, answer_text = answer_for_persona(q, persona, force_correct=is_correct)
|
||||
if not is_correct:
|
||||
misconception_tag = misconception_candidate
|
||||
|
||||
return {
|
||||
"is_correct": is_correct,
|
||||
"answer_text": answer_text,
|
||||
"solve_mode": solve_mode,
|
||||
"time_on_task_seconds": jitter_time(base_time),
|
||||
"misconception_tag": misconception_tag,
|
||||
}
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
ASSIGNMENT_ASSIGNEES = []
|
||||
STUDENT_ANSWERS = []
|
||||
ACTIVITY_LOGS = []
|
||||
|
||||
assignee_id_seq = 4000
|
||||
answer_id_seq = 50000
|
||||
log_id_seq = 70000
|
||||
|
||||
# Define which students "fall behind" on attempts (bonus signal).
|
||||
LOW_ATTEMPT_RATE_STUDENTS = {203} # Chen Wei: persona=rushed_careless + 8 days no activity
|
||||
SKIP_RECENT_ASSIGNMENTS_STUDENTS = {203} # student 3 hasn't attempted recent assignments
|
||||
|
||||
|
||||
for week_idx, assignment_def in enumerate(ASSIGNMENT_DEFS_SORTED):
|
||||
aid, name, topic, offset, status, qb_ids = assignment_def
|
||||
a_questions = [aq for aq in ASSIGNMENT_QUESTIONS if aq["assignment_id"] == aid]
|
||||
|
||||
for student in STUDENTS:
|
||||
sid = student["id"]
|
||||
persona = student["_persona"]
|
||||
# Skip recent assignments for students with low attempt rate
|
||||
if status != "CLOSED" and sid in SKIP_RECENT_ASSIGNMENTS_STUDENTS:
|
||||
assignee = {
|
||||
"id": assignee_id_seq,
|
||||
"assignment_id": aid,
|
||||
"student_id": sid,
|
||||
"classroom_id": CLASSROOM["id"],
|
||||
"status": "NOT_STARTED",
|
||||
"started_at": None,
|
||||
"submitted_at": None,
|
||||
"total_marks": None,
|
||||
"is_active": True,
|
||||
"deactivated_at": None,
|
||||
"created_at": ms(days_ago(-(offset - 7))),
|
||||
}
|
||||
ASSIGNMENT_ASSIGNEES.append(assignee)
|
||||
assignee_id_seq += 1
|
||||
continue
|
||||
|
||||
# Skip last published assignment for some low-attempt students
|
||||
if sid in LOW_ATTEMPT_RATE_STUDENTS and status != "CLOSED":
|
||||
assignee = {
|
||||
"id": assignee_id_seq,
|
||||
"assignment_id": aid,
|
||||
"student_id": sid,
|
||||
"classroom_id": CLASSROOM["id"],
|
||||
"status": "NOT_STARTED",
|
||||
"started_at": None,
|
||||
"submitted_at": None,
|
||||
"total_marks": None,
|
||||
"is_active": True,
|
||||
"deactivated_at": None,
|
||||
"created_at": ms(days_ago(-(offset - 7))),
|
||||
}
|
||||
ASSIGNMENT_ASSIGNEES.append(assignee)
|
||||
assignee_id_seq += 1
|
||||
continue
|
||||
|
||||
# In-progress: ~70% have started and answered ~half the questions
|
||||
if status == "IN_PROGRESS" or status == "PUBLISHED":
|
||||
started = RNG.random() < 0.85
|
||||
submitted = False
|
||||
if not started:
|
||||
assignee = {
|
||||
"id": assignee_id_seq,
|
||||
"assignment_id": aid,
|
||||
"student_id": sid,
|
||||
"classroom_id": CLASSROOM["id"],
|
||||
"status": "NOT_STARTED",
|
||||
"started_at": None,
|
||||
"submitted_at": None,
|
||||
"total_marks": None,
|
||||
"is_active": True,
|
||||
"deactivated_at": None,
|
||||
"created_at": ms(days_ago(-(offset - 7))),
|
||||
}
|
||||
ASSIGNMENT_ASSIGNEES.append(assignee)
|
||||
assignee_id_seq += 1
|
||||
continue
|
||||
else:
|
||||
started = True
|
||||
submitted = True
|
||||
|
||||
# Date the attempts: roughly the day after the assignment was set
|
||||
attempt_day_offset = max(offset - 5, -42)
|
||||
started_at = days_ago(-attempt_day_offset, hour=16 + RNG.randint(0, 3), minute=RNG.randint(0, 59))
|
||||
|
||||
# For DRAFT, no assignees needed (skip)
|
||||
if status == "DRAFT":
|
||||
continue
|
||||
|
||||
assignee_id = assignee_id_seq
|
||||
assignee_id_seq += 1
|
||||
|
||||
# Generate answers
|
||||
total_score = 0
|
||||
questions_to_answer = a_questions
|
||||
if status in ("IN_PROGRESS", "PUBLISHED") and not submitted:
|
||||
# Partial completion
|
||||
n_done = RNG.randint(max(1, len(a_questions) // 2), len(a_questions))
|
||||
questions_to_answer = a_questions[:n_done]
|
||||
|
||||
running_time_offset = 0
|
||||
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)
|
||||
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)
|
||||
|
||||
STUDENT_ANSWERS.append({
|
||||
"id": answer_id_seq,
|
||||
"assignee_id": assignee_id,
|
||||
"assignment_question_id": aq["id"],
|
||||
"answer_type": "LATEX",
|
||||
"answer_latex": ans["answer_text"],
|
||||
"extracted_answer": ans["answer_text"],
|
||||
"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']}."
|
||||
),
|
||||
"grading_status": "GRADED",
|
||||
"grading_attempts": 1,
|
||||
"is_active": True,
|
||||
"created_at": ms(answered_at),
|
||||
# ---- Hackathon annotations (not in production schema) ----
|
||||
"_solve_mode": ans["solve_mode"],
|
||||
"_time_on_task_seconds": ans["time_on_task_seconds"],
|
||||
"_is_correct": ans["is_correct"],
|
||||
"_misconception_tag": ans["misconception_tag"],
|
||||
"_question_topic": q["topic"],
|
||||
"_question_sub_topic": q["sub_topic"],
|
||||
"_question_difficulty": q["difficulty"],
|
||||
"_answered_at": ms(answered_at),
|
||||
})
|
||||
answer_id_seq += 1
|
||||
total_score += (1 if ans["is_correct"] else 0)
|
||||
|
||||
# Activity log
|
||||
ACTIVITY_LOGS.append({
|
||||
"id": log_id_seq,
|
||||
"assignee_id": assignee_id,
|
||||
"assignment_question_id": aq["id"],
|
||||
"activity_type": "ANSWERED",
|
||||
"timestamp": ms(answered_at),
|
||||
"duration_seconds": ans["time_on_task_seconds"],
|
||||
"extra_data": {"solve_mode": ans["solve_mode"]},
|
||||
"created_at": ms(answered_at),
|
||||
"_student_id": sid,
|
||||
})
|
||||
log_id_seq += 1
|
||||
|
||||
submitted_at = started_at + timedelta(seconds=running_time_offset) if submitted else None
|
||||
|
||||
assignee = {
|
||||
"id": assignee_id,
|
||||
"assignment_id": aid,
|
||||
"student_id": sid,
|
||||
"classroom_id": CLASSROOM["id"],
|
||||
"status": "SUBMITTED" if submitted else "IN_PROGRESS",
|
||||
"started_at": ms(started_at),
|
||||
"submitted_at": ms(submitted_at) if submitted_at else None,
|
||||
"total_marks": total_score if submitted else None,
|
||||
"is_active": True,
|
||||
"deactivated_at": None,
|
||||
"created_at": ms(days_ago(-(offset - 7))),
|
||||
}
|
||||
ASSIGNMENT_ASSIGNEES.append(assignee)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Write outputs
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def write_json(name: str, data) -> None:
|
||||
path = OUT_DIR / name
|
||||
with path.open("w") as f:
|
||||
json.dump(data, f, indent=2, default=str)
|
||||
print(f" wrote {name} ({len(data) if isinstance(data, list) else 'object'} records)")
|
||||
|
||||
|
||||
print("Generating mock dataset...")
|
||||
write_json("classroom.json", {
|
||||
"classroom": CLASSROOM,
|
||||
"tutor": TUTOR,
|
||||
"classroom_student_rs": CLASSROOM_STUDENT_RS,
|
||||
})
|
||||
write_json("students.json", STUDENTS)
|
||||
write_json("question_bank.json", QUESTION_BANK)
|
||||
write_json("assignments.json", ASSIGNMENTS)
|
||||
write_json("assignment_questions.json", ASSIGNMENT_QUESTIONS)
|
||||
write_json("assignment_assignees.json", ASSIGNMENT_ASSIGNEES)
|
||||
write_json("student_answers.json", STUDENT_ANSWERS)
|
||||
write_json("activity_logs.json", ACTIVITY_LOGS)
|
||||
|
||||
dataset = {
|
||||
"_meta": {
|
||||
"generated_at_utc": TODAY.isoformat(),
|
||||
"reference_today": TODAY.date().isoformat(),
|
||||
"schema_source": "elevenplus-backend/src/app/models/",
|
||||
"subject": "Maths (UK 11+)",
|
||||
"students": len(STUDENTS),
|
||||
"assignments": len(ASSIGNMENTS),
|
||||
"questions_in_bank": len(QUESTION_BANK),
|
||||
"student_answers": len(STUDENT_ANSWERS),
|
||||
"expected_top_3_at_risk_student_ids": [201, 203, 204],
|
||||
},
|
||||
"classroom": CLASSROOM,
|
||||
"tutor": TUTOR,
|
||||
"classroom_student_rs": CLASSROOM_STUDENT_RS,
|
||||
"students": STUDENTS,
|
||||
"question_bank": QUESTION_BANK,
|
||||
"assignments": ASSIGNMENTS,
|
||||
"assignment_questions": ASSIGNMENT_QUESTIONS,
|
||||
"assignment_assignees": ASSIGNMENT_ASSIGNEES,
|
||||
"student_answers": STUDENT_ANSWERS,
|
||||
"activity_logs": ACTIVITY_LOGS,
|
||||
}
|
||||
write_json("dataset.json", dataset)
|
||||
print("Done.")
|
||||
1157
Mock-Data/question_bank.json
Normal file
1157
Mock-Data/question_bank.json
Normal file
File diff suppressed because it is too large
Load Diff
13526
Mock-Data/student_answers.json
Normal file
13526
Mock-Data/student_answers.json
Normal file
File diff suppressed because it is too large
Load Diff
158
Mock-Data/students.json
Normal file
158
Mock-Data/students.json
Normal file
@@ -0,0 +1,158 @@
|
||||
[
|
||||
{
|
||||
"id": 201,
|
||||
"fullname": "Aisha Khan",
|
||||
"email": "aisha.201@boostai.example",
|
||||
"username": "aisha201",
|
||||
"role": "student",
|
||||
"active": true,
|
||||
"is_test": false,
|
||||
"is_deleted": false,
|
||||
"created_at": 1772877600000,
|
||||
"updated_at": 1772877600000,
|
||||
"_persona": "fraction_inversion"
|
||||
},
|
||||
{
|
||||
"id": 202,
|
||||
"fullname": "Ben Carter",
|
||||
"email": "ben.202@boostai.example",
|
||||
"username": "ben202",
|
||||
"role": "student",
|
||||
"active": true,
|
||||
"is_test": false,
|
||||
"is_deleted": false,
|
||||
"created_at": 1772877600000,
|
||||
"updated_at": 1772877600000,
|
||||
"_persona": "place_value_gaps"
|
||||
},
|
||||
{
|
||||
"id": 203,
|
||||
"fullname": "Chen Wei",
|
||||
"email": "chen.203@boostai.example",
|
||||
"username": "chen203",
|
||||
"role": "student",
|
||||
"active": true,
|
||||
"is_test": false,
|
||||
"is_deleted": false,
|
||||
"created_at": 1772877600000,
|
||||
"updated_at": 1772877600000,
|
||||
"_persona": "rushed_careless"
|
||||
},
|
||||
{
|
||||
"id": 204,
|
||||
"fullname": "Daniela Rossi",
|
||||
"email": "daniela.204@boostai.example",
|
||||
"username": "daniela204",
|
||||
"role": "student",
|
||||
"active": true,
|
||||
"is_test": false,
|
||||
"is_deleted": false,
|
||||
"created_at": 1772877600000,
|
||||
"updated_at": 1772877600000,
|
||||
"_persona": "solve_together_dependent"
|
||||
},
|
||||
{
|
||||
"id": 205,
|
||||
"fullname": "Elif Demir",
|
||||
"email": "elif.205@boostai.example",
|
||||
"username": "elif205",
|
||||
"role": "student",
|
||||
"active": true,
|
||||
"is_test": false,
|
||||
"is_deleted": false,
|
||||
"created_at": 1772877600000,
|
||||
"updated_at": 1772877600000,
|
||||
"_persona": "word_problem_weak"
|
||||
},
|
||||
{
|
||||
"id": 206,
|
||||
"fullname": "Felix Brown",
|
||||
"email": "felix.206@boostai.example",
|
||||
"username": "felix206",
|
||||
"role": "student",
|
||||
"active": true,
|
||||
"is_test": false,
|
||||
"is_deleted": false,
|
||||
"created_at": 1772877600000,
|
||||
"updated_at": 1772877600000,
|
||||
"_persona": "stable_strong"
|
||||
},
|
||||
{
|
||||
"id": 207,
|
||||
"fullname": "Grace Park",
|
||||
"email": "grace.207@boostai.example",
|
||||
"username": "grace207",
|
||||
"role": "student",
|
||||
"active": true,
|
||||
"is_test": false,
|
||||
"is_deleted": false,
|
||||
"created_at": 1772877600000,
|
||||
"updated_at": 1772877600000,
|
||||
"_persona": "stable_strong"
|
||||
},
|
||||
{
|
||||
"id": 208,
|
||||
"fullname": "Harry Singh",
|
||||
"email": "harry.208@boostai.example",
|
||||
"username": "harry208",
|
||||
"role": "student",
|
||||
"active": true,
|
||||
"is_test": false,
|
||||
"is_deleted": false,
|
||||
"created_at": 1772877600000,
|
||||
"updated_at": 1772877600000,
|
||||
"_persona": "stable_mid"
|
||||
},
|
||||
{
|
||||
"id": 209,
|
||||
"fullname": "Isla Nakamura",
|
||||
"email": "isla.209@boostai.example",
|
||||
"username": "isla209",
|
||||
"role": "student",
|
||||
"active": true,
|
||||
"is_test": false,
|
||||
"is_deleted": false,
|
||||
"created_at": 1772877600000,
|
||||
"updated_at": 1772877600000,
|
||||
"_persona": "stable_mid"
|
||||
},
|
||||
{
|
||||
"id": 210,
|
||||
"fullname": "Jaden Williams",
|
||||
"email": "jaden.210@boostai.example",
|
||||
"username": "jaden210",
|
||||
"role": "student",
|
||||
"active": true,
|
||||
"is_test": false,
|
||||
"is_deleted": false,
|
||||
"created_at": 1772877600000,
|
||||
"updated_at": 1772877600000,
|
||||
"_persona": "stable_mid"
|
||||
},
|
||||
{
|
||||
"id": 211,
|
||||
"fullname": "Kira Patel",
|
||||
"email": "kira.211@boostai.example",
|
||||
"username": "kira211",
|
||||
"role": "student",
|
||||
"active": true,
|
||||
"is_test": false,
|
||||
"is_deleted": false,
|
||||
"created_at": 1772877600000,
|
||||
"updated_at": 1772877600000,
|
||||
"_persona": "stable_weak"
|
||||
},
|
||||
{
|
||||
"id": 212,
|
||||
"fullname": "Liam O'Connor",
|
||||
"email": "liam.212@boostai.example",
|
||||
"username": "liam212",
|
||||
"role": "student",
|
||||
"active": true,
|
||||
"is_test": false,
|
||||
"is_deleted": false,
|
||||
"created_at": 1772877600000,
|
||||
"updated_at": 1772877600000,
|
||||
"_persona": "stable_weak"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user