1- from typing import Optional , Dict , Any
1+ from typing import Optional , Dict , Any , Tuple
22
33
44def parse_json_to_prompt (context : dict , task_progress : dict ) -> str :
@@ -11,7 +11,7 @@ def parse_json_to_prompt(context: dict, task_progress: dict) -> str:
1111 set_data = context .get ("set" , {})
1212 current_part = task_progress .get ("currentPart" , {}) if task_progress else {}
1313 current_part_position = current_part .get ("position" )
14- submissions = current_part . get ( "responseAreas" , [] )
14+ student_work_index = _build_student_work_index ( task_progress )
1515
1616 sections = []
1717
@@ -63,7 +63,7 @@ def parse_json_to_prompt(context: dict, task_progress: dict) -> str:
6363 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
66- sections .append (_format_part (part , part_position , is_current , time_on_part , submissions ))
66+ sections .append (_format_part (part , part_position , is_current , time_on_part , student_work_index ))
6767
6868 # Combine
6969 valid_sections = [s .strip () for s in sections if s and s .strip ()]
@@ -72,11 +72,34 @@ def parse_json_to_prompt(context: dict, task_progress: dict) -> str:
7272 return "\n " .join (line for line in content .split ("\n " ) if line .strip () or not line ).strip ()
7373
7474
75+ def _build_student_work_index (task_progress : dict ) -> Dict [Tuple [int , int ], dict ]:
76+ """Index the reported progress by (part position, response area position)."""
77+ if not task_progress :
78+ return {}
79+
80+ reported_parts = []
81+ current_part = task_progress .get ("currentPart" )
82+ if current_part :
83+ reported_parts .append (current_part )
84+ reported_parts .extend (task_progress .get ("parts" ) or [])
85+
86+ index : Dict [Tuple [int , int ], dict ] = {}
87+ for reported_part in reported_parts :
88+ part_position = reported_part .get ("position" )
89+ for j , ra in enumerate (reported_part .get ("responseAreas" ) or []):
90+ ra_part_position = ra .get ("partPosition" , part_position )
91+ ra_position = ra .get ("position" , j + 1 )
92+ if ra_part_position is None or ra_position is None :
93+ continue
94+ index [(ra_part_position , ra_position )] = ra
95+ return index
96+
97+
7598def _part_letter (position : int ) -> str :
7699 """Map a 1-indexed part position to its letter (1 -> 'a', 2 -> 'b', ...)."""
77100 return chr (96 + position )
78101
79- def _format_part (part : dict , part_position : int , is_current : bool , time_on_part : Optional [str ], submissions : list ) -> str :
102+ def _format_part (part : dict , part_position : int , is_current : bool , time_on_part : Optional [str ], student_work_index : Dict [ Tuple [ int , int ], dict ] ) -> str :
80103 letter = _part_letter (part_position )
81104 status_text = " [CURRENTLY WORKING ON]" if is_current else ""
82105 header = f"## Part ({ letter } ){ status_text } "
@@ -89,7 +112,7 @@ def _format_part(part: dict, part_position: int, is_current: bool, time_on_part:
89112 response_areas = []
90113 for j , ra in enumerate (part .get ("responseAreas" , [])):
91114 ra_position = ra .get ("position" , j + 1 )
92- student_work = _get_student_work (ra_position , submissions )
115+ student_work = _get_student_work (part_position , ra_position , student_work_index )
93116 response_areas .append (_format_response_area (ra_position , ra .get ("preResponseText" ), ra .get ("answer" ), student_work ))
94117 ra_block = f"\n ### Response Areas\n \n { '' .join (response_areas )} " if response_areas else ""
95118
@@ -110,24 +133,29 @@ def _format_part(part: dict, part_position: int, is_current: bool, time_on_part:
110133
111134 return "\n " .join ([header , content , ra_block , answer_block , solutions_block , tutorials_block ]) + "\n ---\n "
112135
113- def _get_student_work (ra_position : int , submissions : list ) -> Dict [str , Any ]:
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 ]
117- latest = s .get ("latestSubmission" ) or {}
118- if latest :
119- return {
120- "has_submissions" : True ,
121- "latest_response" : latest .get ("submission" ),
122- "latest_feedback" : latest .get ("feedback" ),
123- "total_submissions" : s .get ("totalSubmissions" ),
124- "total_wrong" : s .get ("wrongSubmissions" ),
125- }
126- return {"has_submissions" : False }
127-
128- def _format_response_area (position : int , task_description : Optional [str ], expected_answer : Any , student_work : Dict [str , Any ]) -> str :
136+ def _get_student_work (part_position : int , ra_position : int , student_work_index : Dict [Tuple [int , int ], dict ]) -> Optional [Dict [str , Any ]]:
137+ """Look up the student's progress on a 1-indexed part/response area position."""
138+ reported = student_work_index .get ((part_position , ra_position ))
139+ if reported is None :
140+ return None
141+
142+ latest = reported .get ("latestSubmission" ) or {}
143+ if not latest :
144+ return {"has_submissions" : False }
145+
146+ return {
147+ "has_submissions" : True ,
148+ "latest_response" : latest .get ("submission" ),
149+ "latest_feedback" : latest .get ("feedback" ),
150+ "total_submissions" : reported .get ("totalSubmissions" ),
151+ "total_wrong" : reported .get ("wrongSubmissions" ),
152+ }
153+
154+ def _format_response_area (position : int , task_description : Optional [str ], expected_answer : Any , student_work : Optional [Dict [str , Any ]]) -> str :
129155 task_text = f"- Task: { task_description } " if task_description else "- Task: Not specified"
130- if not student_work .get ("has_submissions" ):
156+ if student_work is None :
157+ submission_text = "- Student's work on this response area: Not reported"
158+ elif not student_work .get ("has_submissions" ):
131159 submission_text = "- Student's work on this response area: No response submitted yet"
132160 else :
133161 submission_text = (
0 commit comments