This is a challenging question. I approached Question 2 by going through each grade separately. The first step selects all results from a grade level. Then I insert another Query function on the selected list of associations ("students"). This query function acts like yours above by inserting a grade based on a grade function defined below. Finally, I apply Join to merge the results from each grade level.
Join@@Table[
Query[Select[#["grade"]==specGrade&],
<|#,"students"->Query[All,<|#,"score"->grade2[specGrade,#["age"]]|>&][#["students"]]|>&
][sampleStructure]
,
{specGrade,{11,12}}
]
My grading function was defined as this:
grade2[11,15]:="C+"
grade2[11,16]:="B-"
grade2[12,16]:="B+"
grade2[12,17]:="A-"
grade2[_,_]:="F"