Stop calling a feedback-only grade "no numeric grade recorded"

You were right that the output was wrong, though not quite for the
reason given: the schema has no textual grade. It is
grade: { type: Number, min: 0, max: 100 } with gradeComment: String, so
"OK" is the comment, not the grade.

What the model does allow is exactly your case — teachers grade with the
comment alone and leave grade unset. Rendering that as "graded (no
numeric grade recorded)" reads as missing or broken data when in fact
the written verdict is the whole grade. It now says "graded by feedback,
with no percentage given", and reserves the it-is-absent wording for
when there is genuinely neither a percentage nor a comment.

Also fixes a real misrepresentation next to it: grade is a percentage,
and both the detail and list views printed it bare, so an 85 could be
read as a mark out of 100, 15 or 6. Now rendered as 85%.

formatGradeState is pure and covered by seven cases, including 0% staying
distinct from "no grade" — the bug that an `if (grade)` test would have
introduced.

85 tests, 38/38 smoke.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-13 13:32:08 +02:00
parent a8badc2fd1
commit ab10bbcd14
5 changed files with 93 additions and 11 deletions

View File

@@ -26,7 +26,9 @@ How the content is organised, and the usual path through it:
- **Files** hang off boards, lessons and tasks. Every listing shows file ids; download_file fetches one and
extracts its text (PDF, Word, Excel, PowerPoint, OpenDocument) or returns an image inline.
- **Submissions** ("Abgaben") — what the user handed in. get_task shows that task's submission: the files,
the graded flag, the grade, what the user wrote, and the teacher's written feedback. list_submissions
the graded flag, the grade, what the user wrote, and the teacher's written feedback. A grade is a
percentage (0-100) or absent — there is no textual grade — and teachers often grade with the written
feedback alone, so "graded by feedback" is a complete result, not missing data. list_submissions
surveys them across tasks ("what is still ungraded?"). The written parts are read from the web page
because no API exposes them, so they can be missing even when feedback exists — if none is shown for a
graded submission, say it was not found rather than that none was given. On a teacher account these

View File

@@ -98,12 +98,41 @@ function anyGraded(statuses: SubmissionStatus[]): boolean {
return statuses.some((status) => status.isGraded);
}
/**
* How to describe the grading.
*
* The data model has no textual grade: `grade` is `Number, min 0, max 100` — a
* percentage — and `gradeComment` is free text. Teachers routinely grade with
* the comment alone, leaving `grade` unset, so a written "OK" *is* the verdict
* for that submission rather than a missing value. Saying "no numeric grade
* recorded" in that case reads as broken data, which is why it is only said
* when there is genuinely nothing to show.
*/
export function formatGradeState(
status: Pick<SubmissionStatus, 'isGraded' | 'grade'>,
hasWrittenFeedback: boolean,
percentFromPage?: number,
): string {
if (!status.isGraded) return 'not graded yet';
// `grade` is a percentage in the schema, so render it as one rather than as
// a bare number that could be mistaken for a mark out of 6 or 15.
const percent = status.grade ?? percentFromPage;
if (percent !== null && percent !== undefined) {
return hasWrittenFeedback ? `graded **${percent}%**, with feedback` : `graded **${percent}%**`;
}
if (hasWrittenFeedback) return 'graded by feedback, with no percentage given';
return 'marked graded, but neither a percentage nor feedback was found';
}
function formatRow({ task, status }: { task: TaskContent; status: SubmissionStatus }): string {
const state = status.isSubmitted ? 'submitted' : 'not submitted';
// The list does not fetch pages, so it cannot know whether feedback exists;
// it says only what the API told it.
const graded = status.isGraded
? status.grade !== null && status.grade !== undefined
? `graded ${status.grade}`
: 'graded (no numeric grade)'
? `graded ${status.grade}%`
: 'graded (percentage not set — check get_task for written feedback)'
: 'not graded';
const group = status.submittingCourseGroupName ? ` — group "${status.submittingCourseGroupName}"` : '';
const course = task.courseName ? ` [${task.courseName}]` : '';
@@ -166,14 +195,7 @@ export async function describeSubmission(
const parts: string[] = [];
for (const status of relevant) {
const files = await loadSubmissionFiles(context, status.id);
const percent = detail?.gradePercent;
const graded = status.isGraded
? status.grade !== null && status.grade !== undefined
? `graded **${status.grade}**`
: percent !== undefined
? `graded **${percent}%**`
: 'graded (no numeric grade recorded)'
: 'not graded yet';
const graded = formatGradeState(status, Boolean(detail?.gradeComment), detail?.gradePercent);
parts.push(
[