DEV Community

Akbo Ichou
Akbo Ichou

Posted on

Points to Grades: Building a German Grading Scale (Notenschlüssel) Calculator With Half-Point Precision

German schools grade on a 1–6 scale (1 = sehr gut, 6 = ungenügend), but tests are scored in points. The mapping between the two is called a Notenschlüssel (grading key), and teachers build one for almost every exam — often by hand in a spreadsheet.

Notenschlüsselrechner is a free, no-login tool that does this: enter the maximum points, choose a linear key for school or the IHK scale for vocational training, and get the full table, a grade distribution (Notenspiegel) and export. There's a teacher mode and a student mode. The logic is small, but the details matter — here's how it works.

Keys are just thresholds

Every grading key is a list of minimum percentages per grade. Model it as data:

type Grade = 1 | 2 | 3 | 4 | 5 | 6;
type Key = { grade: Grade; minPct: number }[]; // sorted from best to worst

// IHK 100-point scale, common in German vocational training
export const IHK: Key = [
  { grade: 1, minPct: 92 },
  { grade: 2, minPct: 81 },
  { grade: 3, minPct: 67 },
  { grade: 4, minPct: 50 },
  { grade: 5, minPct: 30 },
  { grade: 6, minPct: 0 },
];
Enter fullscreen mode Exit fullscreen mode

A linear key from one decision

For a linear school key, the teacher usually decides one thing: the pass mark (the percentage needed for a 4). The remaining grades are spaced evenly between that and 100%:

export function linearKey(passPct = 50): Key {
  const step = (100 - passPct) / 4;           // grades 1–4 share the top band
  const top: Key = [1, 2, 3, 4].map((g, i) => ({
    grade: g as Grade,
    minPct: +(100 - step * (i + 1)).toFixed(2),
  }));
  return [...top, { grade: 5, minPct: passPct / 2 }, { grade: 6, minPct: 0 }];
}
Enter fullscreen mode Exit fullscreen mode

With a 50% pass mark that gives 87.5 / 75 / 62.5 / 50 / 25 / 0. Schools differ, which is exactly why the thresholds are data rather than hardcoded logic.

Half points and rounding

Tests are often scored in half points, and the boundary rule matters: a teacher expects "you need 34.5 of 37 points for a 1", not "34.04". Convert percentages to point thresholds and round up to the next half point, so nobody gets a better grade than the key allows:

const ceilHalf = (x: number) => Math.ceil(x * 2) / 2;

export function pointTable(maxPoints: number, key: Key) {
  return key.map(({ grade, minPct }, i) => {
    const from = ceilHalf((minPct / 100) * maxPoints);
    const to = i === 0 ? maxPoints : ceilHalf((key[i - 1].minPct / 100) * maxPoints) - 0.5;
    return { grade, from, to };
  });
}
Enter fullscreen mode Exit fullscreen mode

For 50 points on the IHK scale, a 1 starts at 46 points (92% of 50).

Looking up a grade

export function gradeFor(points: number, maxPoints: number, key: Key): Grade {
  const pct = (points / maxPoints) * 100;
  return key.find((k) => pct >= k.minPct)!.grade;
}
Enter fullscreen mode Exit fullscreen mode

Test the boundaries explicitly — exactly on the threshold, half a point below, zero, and full marks.

The Notenspiegel

Once you have grades, the class distribution and average are one reduce away:

export function notenspiegel(grades: Grade[]) {
  const counts = [1, 2, 3, 4, 5, 6].map((g) => grades.filter((x) => x === g).length);
  const avg = grades.reduce((a, b) => a + b, 0) / grades.length;
  return { counts, average: +avg.toFixed(2) };
}
Enter fullscreen mode Exit fullscreen mode

Formatting for Germany

Decimal commas matter: a class average is "2,43", not "2.43". Use Intl.NumberFormat("de-DE") everywhere numbers are displayed or exported.

Takeaways

  • Represent grading keys as threshold data.
  • Derive linear keys from the one decision teachers actually make.
  • Round point thresholds up to the next half point.
  • Test boundaries, and format numbers for the locale.

Try it at notenschluesselrechner.de (in German). Have you built grading tools for other school systems?

Top comments (0)