Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 2x 24x 34x 24x 120x 120x 120x 120x 120x 120x 24x | /**
* EI-2966 — Score Distribution helper.
*
* Pure helper that normalizes the `/v1/student/dashboard/grade-
* distribution/fetch` response for the chart UI:
* - Guarantees the 5 canonical buckets are present in order
* (<60 / 60-69 / 70-79 / 80-89 / 90-100), even if the backend
* omits an empty bucket.
* - Adds a `percent` field per bucket (0-100, rounded to 2 dp).
* - Returns `{ buckets: [...], total_graded: number, max_count: number }`.
* `max_count` is useful for the bar-chart UI (height scaling).
*/
const CANONICAL = [
{ label: "<60", min: 0, max: 59 },
{ label: "60-69", min: 60, max: 69 },
{ label: "70-79", min: 70, max: 79 },
{ label: "80-89", min: 80, max: 89 },
{ label: "90-100", min: 90, max: 100 },
];
export function normalizeDistribution(response) {
const raw = Array.isArray(response?.buckets) ? response.buckets : [];
const byLabel = new Map(raw.map((b) => [b.label, b]));
const buckets = CANONICAL.map(({ label, min, max }) => {
const found = byLabel.get(label);
const count = typeof found?.count === "number" && Number.isFinite(found.count) ? found.count : 0;
return { label, min, max, count };
});
const total_graded = buckets.reduce((sum, b) => sum + b.count, 0);
const max_count = buckets.reduce((m, b) => Math.max(m, b.count), 0);
const withPercent = buckets.map((b) => ({
...b,
percent: total_graded === 0 ? 0 : Math.round((b.count / total_graded) * 10000) / 100,
}));
return { buckets: withPercent, total_graded, max_count };
}
|