Repository navigation
Expand file tree
/
Copy pathhistory.js
More file actions
100 lines (90 loc) · 2.56 KB
/
Copy pathhistory.js
File metadata and controls
100 lines (90 loc) · 2.56 KB
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// Score history — persisted to localStorage so progress survives sessions.
// Each entry: { taskId, taskName, section, overall, timestamp }
import { getTaskMeta } from "./content.js";
const KEY = "pte_history_v1";
const MAX_ENTRIES = 1000; // safety bound
function load() {
try {
return JSON.parse(localStorage.getItem(KEY) || "[]");
} catch (e) {
return [];
}
}
function save(arr) {
if (arr.length > MAX_ENTRIES) arr = arr.slice(-MAX_ENTRIES);
localStorage.setItem(KEY, JSON.stringify(arr));
}
export function addScore(taskId, score) {
const overall = score?.overall_0_90;
if (overall == null || isNaN(overall)) return;
const meta = getTaskMeta(taskId);
const entry = {
taskId,
taskName: meta?.name || taskId,
section: meta?.section || "Other",
overall: Math.round(overall),
timestamp: Date.now(),
};
const arr = load();
arr.push(entry);
save(arr);
}
export function getAll() {
return load();
}
export function clearAll() {
localStorage.removeItem(KEY);
}
// Per-task aggregates: count, mean, best, last score.
export function summary() {
const arr = load();
const groups = {};
for (const e of arr) {
if (!groups[e.taskId]) {
groups[e.taskId] = {
taskId: e.taskId,
taskName: e.taskName,
section: e.section,
scores: [],
};
}
groups[e.taskId].scores.push({ overall: e.overall, timestamp: e.timestamp });
}
return Object.values(groups).map((g) => {
const scores = g.scores.map((s) => s.overall);
const last = g.scores[g.scores.length - 1];
return {
taskId: g.taskId,
taskName: g.taskName,
section: g.section,
count: scores.length,
mean: Math.round(scores.reduce((a, b) => a + b, 0) / scores.length),
best: Math.max(...scores),
worst: Math.min(...scores),
last: last.overall,
lastTimestamp: last.timestamp,
recent: g.scores.slice(-10), // last 10 for sparkline
};
});
}
// Section-level aggregates.
export function sectionSummary() {
const arr = load();
const groups = {};
for (const e of arr) {
if (!groups[e.section]) groups[e.section] = [];
groups[e.section].push(e.overall);
}
return Object.entries(groups).map(([section, scores]) => ({
section,
count: scores.length,
mean: Math.round(scores.reduce((a, b) => a + b, 0) / scores.length),
}));
}
// Identify weakest tasks: lowest mean among those practised at least twice.
export function weakSpots(limit = 3) {
return summary()
.filter((s) => s.count >= 2)
.sort((a, b) => a.mean - b.mean)
.slice(0, limit);
}