Skip to content

Commit 38e1fcc

Browse files
committed
Merge remote-tracking branch 'template/main'
2 parents 9b7a986 + fc63ccd commit 38e1fcc

6 files changed

Lines changed: 70 additions & 66 deletions

File tree

‎README.md‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ Per the [muEd `ChatRequest` schema](https://mued.org/), only `messages` is requi
222222
"accessStatus": "a good amount of time spent on this question today.",
223223
"markedDone": "This question is still being worked on.",
224224
"currentPart": {
225-
"position": 0,
225+
"position": 1,
226226
"timeSpentOnPart": "10 minutes",
227227
"markedDone": "This part is not marked done.",
228228
"responseAreas": [
@@ -255,18 +255,18 @@ Per the [muEd `ChatRequest` schema](https://mued.org/), only `messages` is requi
255255
"estimatedTime": "15-25 minutes",
256256
"parts": [
257257
{
258-
"position": 0,
258+
"position": 1,
259259
"content": "<part prompt>",
260260
"answerContent": "<part answer>",
261261
"workedSolutionSections": [
262-
{ "position": 0, "title": "Step 1", "content": "..." }
262+
{ "position": 1, "title": "Step 1", "content": "..." }
263263
],
264264
"structuredTutorialSections": [
265-
{ "position": 0, "title": "Hint", "content": "..." }
265+
{ "position": 1, "title": "Hint", "content": "..." }
266266
],
267267
"responseAreas": [
268268
{
269-
"position": 0,
269+
"position": 1,
270270
"responseType": "EXPRESSION",
271271
"answer": "<reference answer>",
272272
"preResponseText": "<label shown before input>"
@@ -278,6 +278,7 @@ Per the [muEd `ChatRequest` schema](https://mued.org/), only `messages` is requi
278278
}
279279
}
280280
```
281+
All `position` and `number` fields are **1-indexed**.
281282

282283
Response:
283284

‎docs/dev.md‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ Body with optional fields:
118118
"accessStatus": "a good amount of time spent on this question today.",
119119
"markedDone": "This question is still being worked on.",
120120
"currentPart": {
121-
"position": 0,
121+
"position": 1,
122122
"timeSpentOnPart": "10 minutes",
123123
"markedDone": "This part is not marked done.",
124124
"responseAreas": [
@@ -151,18 +151,18 @@ Body with optional fields:
151151
"estimatedTime": "15-25 minutes",
152152
"parts": [
153153
{
154-
"position": 0,
154+
"position": 1,
155155
"content": "<part prompt>",
156156
"answerContent": "<part answer>",
157157
"workedSolutionSections": [
158-
{ "position": 0, "title": "Step 1", "content": "..." }
158+
{ "position": 1, "title": "Step 1", "content": "..." }
159159
],
160160
"structuredTutorialSections": [
161-
{ "position": 0, "title": "Hint", "content": "..." }
161+
{ "position": 1, "title": "Hint", "content": "..." }
162162
],
163163
"responseAreas": [
164164
{
165-
"position": 0,
165+
"position": 1,
166166
"responseType": "EXPRESSION",
167167
"answer": "<reference answer>",
168168
"preResponseText": "<label shown before input>"
@@ -174,6 +174,7 @@ Body with optional fields:
174174
}
175175
}
176176
```
177+
All `position` and `number` fields are **1-indexed**.
177178

178179
Response:
179180

‎src/agent/context.py‎

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ def parse_json_to_prompt(context: dict, task_progress: dict) -> str:
1919
title_parts = []
2020
set_number = set_data.get("number")
2121
if set_number is not None and set_data.get("title"):
22-
title_parts.append(f"## Set {set_number + 1}: {set_data['title']}")
22+
title_parts.append(f"## Set {set_number}: {set_data['title']}")
2323

2424
question_num = ""
2525
q_number = question.get("number")
2626
if set_number is not None and q_number is not None:
27-
question_num = f"{set_number + 1}.{q_number + 1}"
27+
question_num = f"{set_number}.{q_number}"
2828
title_parts.append(f"### Question {question_num}: {question.get('title', '')}")
2929

3030
current_part_letter = _part_letter(current_part_position) if current_part_position is not None else ""
@@ -60,7 +60,7 @@ def parse_json_to_prompt(context: dict, task_progress: dict) -> str:
6060

6161
# 3. Parts
6262
for i, part in enumerate(question.get("parts", [])):
63-
part_position = part.get("position", i)
63+
part_position = part.get("position", i + 1)
6464
is_current = current_part_position == part_position
6565
time_on_part = current_part.get("timeSpentOnPart") if is_current else None
6666
sections.append(_format_part(part, part_position, is_current, time_on_part, submissions))
@@ -73,7 +73,8 @@ def parse_json_to_prompt(context: dict, task_progress: dict) -> str:
7373

7474

7575
def _part_letter(position: int) -> str:
76-
return chr(96 + (position + 1))
76+
"""Map a 1-indexed part position to its letter (1 -> 'a', 2 -> 'b', ...)."""
77+
return chr(96 + position)
7778

7879
def _format_part(part: dict, part_position: int, is_current: bool, time_on_part: Optional[str], submissions: list) -> str:
7980
letter = _part_letter(part_position)
@@ -87,31 +88,32 @@ def _format_part(part: dict, part_position: int, is_current: bool, time_on_part:
8788

8889
response_areas = []
8990
for j, ra in enumerate(part.get("responseAreas", [])):
90-
ra_position = ra.get("position", j)
91+
ra_position = ra.get("position", j + 1)
9192
student_work = _get_student_work(ra_position, submissions)
9293
response_areas.append(_format_response_area(ra_position, ra.get("preResponseText"), ra.get("answer"), student_work))
9394
ra_block = f"\n### Response Areas\n\n{''.join(response_areas)}" if response_areas else ""
9495

9596
answer = part.get("answerContent")
9697
answer_block = f"### Final Answer (confidential)\n\n{answer}" if answer else "### Final Answer (confidential)\n\nNo direct answer specified for this part"
9798

98-
solutions = [
99-
f"{ws.get('title', f'#### Solution {i+1}')}\n\n{ws.get('content', '').strip() or 'No content available'}"
100-
for i, ws in enumerate(part.get("workedSolutionSections", []))
101-
]
99+
solutions = []
100+
for i, ws in enumerate(part.get("workedSolutionSections", [])):
101+
title = ws.get("title") or f"#### Solution {ws.get('position', i + 1)}"
102+
solutions.append(f"{title}\n\n{ws.get('content', '').strip() or 'No content available'}")
102103
solutions_block = "### Worked Solutions (confidential)\n\n" + "\n".join(solutions) if solutions else "### Worked Solutions (confidential)\n\nNone available"
103104

104-
tutorials = [
105-
f"{ts.get('title', f'#### Tutorial {i+1}')}\n\n{ts.get('content', '').strip() or 'No content available'}"
106-
for i, ts in enumerate(part.get("structuredTutorialSections", []))
107-
]
105+
tutorials = []
106+
for i, ts in enumerate(part.get("structuredTutorialSections", [])):
107+
title = ts.get("title") or f"#### Tutorial {ts.get('position', i + 1)}"
108+
tutorials.append(f"{title}\n\n{ts.get('content', '').strip() or 'No content available'}")
108109
tutorials_block = "### Structured Tutorials\n\n" + "\n".join(tutorials) if tutorials else "### Structured Tutorials\n\nNone available"
109110

110111
return "\n".join([header, content, ra_block, answer_block, solutions_block, tutorials_block]) + "\n---\n"
111112

112113
def _get_student_work(ra_position: int, submissions: list) -> Dict[str, Any]:
113-
if ra_position < len(submissions):
114-
s = submissions[ra_position]
114+
"""Look up the student's submission for a 1-indexed response area position."""
115+
if 1 <= ra_position <= len(submissions):
116+
s = submissions[ra_position - 1]
115117
latest = s.get("latestSubmission") or {}
116118
if latest:
117119
return {
@@ -135,7 +137,7 @@ def _format_response_area(position: int, task_description: Optional[str], expect
135137
f" - Total attempts: {student_work.get('total_submissions', 0)} out of which {student_work.get('total_wrong', 0)} were incorrect"
136138
)
137139
return (
138-
f"\n#### Response Area {position + 1}\n\n"
140+
f"\n#### Response Area {position}\n\n"
139141
f"{task_text}\n"
140142
f"- Expected Answer (confidential): {expected_answer}\n"
141143
f"{submission_text}\n"

‎tests/example_inputs/example_input_1.json‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"accessStatus": "too much time spent on this question today.",
1212
"markedDone": "This question is still being worked on.",
1313
"currentPart": {
14-
"position": 0,
14+
"position": 1,
1515
"timeSpentOnPart": "37 minutes",
1616
"markedDone": "This part is not marked done.",
1717
"responseAreas": [
@@ -43,74 +43,74 @@
4343
"summary": "",
4444
"set": {
4545
"title": "Computing",
46-
"number": 8,
46+
"number": 9,
4747
"description": "Exercises on number systems and binary arithmetic."
4848
},
4949
"question": {
5050
"title": "Binary Numbers",
51-
"number": 0,
51+
"number": 1,
5252
"guidance": "",
5353
"content": "",
5454
"estimatedTime": "5-10 minutes",
5555
"parts": [
5656
{
57-
"position": 0,
57+
"position": 1,
5858
"content": "Convert the following numbers to decimal: $11010_2$, $110101_2$.",
5959
"answerContent": "$11010_2 = 26_{10}$\\n\\n$110101_2 = 53_{10}$&#x20;\\n\\n&#x20;&#x20;",
6060
"workedSolutionSections": [
6161
{
62-
"position": 0,
62+
"position": 1,
6363
"title": "",
6464
"content": "$11010_2 = (0\\\\times1) + (1\\\\times2) + (0\\\\times4) + (1\\\\times8) + (1\\\\times16) = \\\\boxed{26_{10}}$&#x20;\\n\\n***\\n\\n$110101_2 = (1\\\\times1) + (0\\\\times2) + (1\\\\times4) + (0\\\\times8) + (1\\\\times16) + (1\\\\times32) = \\\\boxed{53_{10}}$&#x20;\\n\\n&#x20;"
6565
}
6666
],
6767
"structuredTutorialSections": [],
6868
"responseAreas": [
6969
{
70-
"position": 0,
70+
"position": 1,
7171
"responseType": "NUMBER",
7272
"answer": 26,
7373
"preResponseText": "$11010_2 =$"
7474
},
7575
{
76-
"position": 1,
76+
"position": 2,
7777
"responseType": "NUMBER",
7878
"answer": 53,
7979
"preResponseText": "$110101_2$"
8080
}
8181
]
8282
},
8383
{
84-
"position": 1,
84+
"position": 2,
8585
"content": "Convert the following numbers to binary: $101_{10}$, $16_{10}$.",
8686
"answerContent": "$101_{10} = 1100101_2$&#x20;\\n\\n$16_{10} = 10000_2$&#x20;\\n\\n&#x20;&#x20;",
8787
"workedSolutionSections": [
8888
{
89-
"position": 0,
89+
"position": 1,
9090
"title": "",
9191
"content": ""
9292
},
9393
{
94-
"position": 0,
94+
"position": 2,
9595
"title": "By repeated division",
9696
"content": "$101_{10}$ can be divided by 2 to give a quotient of 50 and a remainder of 1. The remainder is the first binary digit: $1_2$.\\n\\n***\\n\\n$50_{10}$ divided by 2 gives a quotient of 25 and a remainder of zero, so the second binary digit is 0: $01_2$.\\n\\n***\\n\\n$25_{10}$ divided by 2 gives a quotient of 12 and a remainder of 1, so the third binary digit is 1: $101_2$\\n\\n***\\n\\n$12_{10}$ divided by 2 gives a quotient of 6 and a remainder of 0, so the fourth binary digit is 0: $0101_2$\\n\\n***\\n\\n$6_{10}$ divided by 2 gives a quotient of 3 and a remainder of 0, so the fifth binary digit is 0: $00101_2$\\n\\n***\\n\\n$3_{10}$ divided by 2 gives a quotient of 1 and a remainder of 1, so the sixth binary digit is 1: $100101_2$\\n\\n***\\n\\n$1_{10}$ divided by 2 gives a quotient of zero and a remainder of 1, so the seventh binary digit is 1: $\\\\boxed{1100101_2}$.&#x20;\\n\\n&#x20;&#x20;"
9797
},
9898
{
99-
"position": 1,
99+
"position": 3,
100100
"title": "By inspection",
101101
"content": "$101_{10}$ can be written in terms of powers of 2 as $101 = 64 + 32 + 4 + 1 = 2^6 + 2^5 + 2^2 + 2^0$, so its binary representation is $\\\\boxed{1100101_2}$.\\n\\n***\\n\\n$16_{10}$ is already a power of 2 ($2^4$), so its binary representation will have 1 bit set: $\\\\boxed{10000_2}$.&#x20;\\n\\n&#x20; &#x20;"
102102
}
103103
],
104104
"structuredTutorialSections": [],
105105
"responseAreas": [
106106
{
107-
"position": 0,
107+
"position": 1,
108108
"responseType": "NUMBER",
109109
"answer": 1100101,
110110
"preResponseText": "$101_{10} = $"
111111
},
112112
{
113-
"position": 1,
113+
"position": 2,
114114
"responseType": "NUMBER",
115115
"answer": 10000,
116116
"preResponseText": "$16_{10} = $"

‎tests/example_inputs/example_input_2.json‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"accessStatus": "",
1212
"markedDone": "This question is still being worked on.",
1313
"currentPart": {
14-
"position": 0,
14+
"position": 1,
1515
"timeSpentOnPart": "less than one minute",
1616
"markedDone": "This part is marked done.",
1717
"responseAreas": [
@@ -31,56 +31,56 @@
3131
},
3232
"context": {
3333
"summary": "noting was discussed yet",
34-
"set": { "title": "Physics", "number": 7, "description": "" },
34+
"set": { "title": "Physics", "number": 8, "description": "" },
3535
"question": {
3636
"title": "Work done",
37-
"number": 0,
37+
"number": 1,
3838
"guidance": "See section 1.11.",
3939
"content": "**(L3)** A father pulls a child on a sled with a rope that has a constant tension of $F =100\\\\text{N}$ and makes an angle of $20^\\\\circ$ to the horizontal. Calculate the work he performs after pulling the child for $10\\\\text{m}$.&#x20;\\n\\n![](image)\\n",
4040
"estimatedTime": "10 minutes",
4141
"parts": [
4242
{
43-
"position": 0,
43+
"position": 1,
4444
"content": "Calculate the work he performs after pulling the child for $10\\\\text{m}$.&#x20;\\n",
4545
"answerContent": "$W = 939.7\\\\text{J}$\\n",
4646
"workedSolutionSections": [
4747
{
48-
"position": 0,
48+
"position": 1,
4949
"title": "",
5050
"content": "We require the force component parallel to the direction of travel, and so $W = \\\\vec{F}\\\\cdot\\\\vec{d}$ (the projection of $\\\\vec{F}$ onto $\\\\vec{d}$) (**section 1.11**). Hence applying the dot product definition to evaluate this (**section 1.8**):\\n\\n***\\n\\n$$\\n\\\\begin{aligned}\\nW & = \\\\vec{F}\\\\cdot \\\\vec{d} \\\\\\\\\\n& = 100\\\\cdot10\\\\cos\\\\theta \\\\\\\\\\n& = 939.7\\\\text{J} \\n\\\\end{aligned}\\n$$\\n"
5151
}
5252
],
5353
"structuredTutorialSections": [
5454
{
55-
"position": 0,
55+
"position": 1,
5656
"title": null,
5757
"content": "Can you express the work done as a dot product (**section 1.11**)?\\n\\n\\n***\\n\\nEvaluate the dot product using the dot product definition (**section 1.8**).\\n"
5858
}
5959
],
6060
"responseAreas": [
6161
{
62-
"position": 0,
62+
"position": 1,
6363
"responseType": "NUMERIC_UNITS",
6464
"answer": "939.7 J",
6565
"preResponseText": "$W=$"
6666
}
6767
]
6868
},
6969
{
70-
"position": 1,
70+
"position": 2,
7171
"content": "Why does he not pull at a shallower angle (closer to the horizontal)?\\n",
7272
"answerContent": "",
7373
"workedSolutionSections": [
7474
{
75-
"position": 0,
75+
"position": 1,
7676
"title": "",
7777
"content": "Pulling at a shallower angle (closer to horizontal) would require less work or allow him to pull less strongly, but it reduces the reaction force that he feels too, which reduces his maximum pulling force, and also allow him to gain more leverage as he pulls (he can lean forward and use his weight to pull whilst pivoted at his feet).&#x20;\\n"
7878
}
7979
],
8080
"structuredTutorialSections": [],
8181
"responseAreas": [
8282
{
83-
"position": 0,
83+
"position": 1,
8484
"responseType": "MULTIPLE_CHOICE",
8585
"answer": [
8686
"[true] ... reduces the reaction force acting on him, hence reducing the maximum pulling force.",

0 commit comments

Comments
 (0)