@vue-skuilder/common-ui 0.2.27 → 0.2.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/admin/admin.css +2 -0
  2. package/dist/admin/common-ui-admin.es.js +1752 -0
  3. package/dist/admin/common-ui-admin.es.js.map +1 -0
  4. package/dist/admin/components/CardDossierDetail.vue.d.ts +9 -0
  5. package/dist/admin/components/CardDossierDetail.vue.d.ts.map +1 -0
  6. package/dist/admin/components/LearnerCardHistory.vue.d.ts +13 -0
  7. package/dist/admin/components/LearnerCardHistory.vue.d.ts.map +1 -0
  8. package/dist/admin/components/LearnerDatasetSource.vue.d.ts +24 -0
  9. package/dist/admin/components/LearnerDatasetSource.vue.d.ts.map +1 -0
  10. package/dist/admin/components/LearnerDiagnostics.vue.d.ts +14 -0
  11. package/dist/admin/components/LearnerDiagnostics.vue.d.ts.map +1 -0
  12. package/dist/admin/components/LearnerSessionDetail.vue.d.ts +14 -0
  13. package/dist/admin/components/LearnerSessionDetail.vue.d.ts.map +1 -0
  14. package/dist/admin/components/LearnerSessionList.vue.d.ts +13 -0
  15. package/dist/admin/components/LearnerSessionList.vue.d.ts.map +1 -0
  16. package/dist/admin/components/PipelineOverview.vue.d.ts +18 -0
  17. package/dist/admin/components/PipelineOverview.vue.d.ts.map +1 -0
  18. package/dist/admin/components/RunDetail.vue.d.ts +12 -0
  19. package/dist/admin/components/RunDetail.vue.d.ts.map +1 -0
  20. package/dist/admin/composables/useLearnerDataset.d.ts +26 -0
  21. package/dist/admin/composables/useLearnerDataset.d.ts.map +1 -0
  22. package/dist/admin/display.d.ts +20 -0
  23. package/dist/admin/display.d.ts.map +1 -0
  24. package/dist/admin/index.d.ts +23 -0
  25. package/dist/admin/index.d.ts.map +1 -0
  26. package/dist/components/CardBrowser.vue.d.ts +5 -5
  27. package/dist/components/CardHistoryViewer.vue.d.ts +1 -1
  28. package/dist/components/CardSearchResults.vue.d.ts +1 -1
  29. package/dist/components/CourseCardBrowser.vue.d.ts +9 -9
  30. package/dist/components/CourseInformation.vue.d.ts +18 -18
  31. package/dist/components/CourseTagFilterWidget.vue.d.ts +1 -1
  32. package/dist/components/SessionControllerDebug.vue.d.ts +1 -1
  33. package/dist/components/SnackbarService.d.ts +1 -1
  34. package/dist/components/StudySession.types.d.ts +1 -1
  35. package/dist/components/StudySession.vue.d.ts +2 -2
  36. package/dist/components/TagsInput.vue.d.ts +1 -1
  37. package/dist/components/UserTagPreferences.vue.d.ts +1 -1
  38. package/dist/components/auth/UserRegistration.vue.d.ts +74 -74
  39. package/dist/components/auth/UserRegistration.vue.d.ts.map +1 -1
  40. package/dist/components/cardRendering/CardLoader.vue.d.ts +6 -6
  41. package/dist/components/cardRendering/CardViewer.vue.d.ts +2 -2
  42. package/dist/components/cardRendering/MarkdownRenderer.vue.d.ts +6 -6
  43. package/dist/components/cardRendering/MdTokenRenderer.vue.d.ts +6 -6
  44. package/dist/components/studentInputs/BaseUserInput.d.ts +2 -2
  45. package/dist/components/studentInputs/MultipleChoiceOption.vue.d.ts +6 -6
  46. package/dist/components/studentInputs/RadioMultipleChoice.types.d.ts +1 -1
  47. package/dist/components/studentInputs/RadioMultipleChoice.vue.d.ts +9 -9
  48. package/dist/components/studentInputs/TrueFalse.vue.d.ts +9 -9
  49. package/dist/components/studentInputs/UserInputNumber.vue.d.ts +2 -2
  50. package/dist/components/studentInputs/UserInputString.vue.d.ts +3 -3
  51. package/dist/components/studentInputs/fillInInput.vue.d.ts +3 -3
  52. package/dist/composables/CompositionViewable.d.ts +2 -2
  53. package/dist/composables/Displayable.d.ts +1 -1
  54. package/dist/stores/useAuthStore.d.ts +82 -82
  55. package/dist/stores/useAuthStore.d.ts.map +1 -1
  56. package/dist/stores/useConfigStore.d.ts +1 -1
  57. package/package.json +18 -5
  58. package/src/admin/components/CardDossierDetail.vue +99 -0
  59. package/src/admin/components/LearnerCardHistory.vue +196 -0
  60. package/src/admin/components/LearnerDatasetSource.vue +97 -0
  61. package/src/admin/components/LearnerDiagnostics.vue +85 -0
  62. package/src/admin/components/LearnerSessionDetail.vue +439 -0
  63. package/src/admin/components/LearnerSessionList.vue +175 -0
  64. package/src/admin/components/PipelineOverview.vue +390 -0
  65. package/src/admin/components/RunDetail.vue +186 -0
  66. package/src/admin/composables/useLearnerDataset.ts +66 -0
  67. package/src/admin/display.ts +73 -0
  68. package/src/admin/index.ts +28 -0
@@ -0,0 +1,1752 @@
1
+ import { Fragment as e, computed as t, createBlock as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, createTextVNode as o, createVNode as s, defineComponent as c, nextTick as l, normalizeClass as u, normalizeStyle as d, onMounted as f, openBlock as p, ref as m, renderList as h, resolveComponent as g, shallowRef as _, toDisplayString as v, unref as y, watch as b, withCtx as x, withKeys as S } from "vue";
2
+ import { SESSION_FLAGS as C, cardDossiers as w, dumpFileName as T, eloLedger as E, fetchLearnerDump as D, fmtAnswer as O, fmtDelta as k, fmtDuration as A, fromDump as j, isSkillTag as M, parseLearnerDump as N, pipelineRunStats as P, recordPerformance as F, regulatorText as I, runText as L, scoredTagDeltas as R, servedTags as z, sessionDetail as B, sessionText as V, shortTag as H, summarizeSessions as U } from "@vue-skuilder/db/diagnostics";
3
+ import { PIPELINE_STAGES as W, planPipeline as ee } from "@vue-skuilder/db";
4
+ //#region \0rolldown/runtime.js
5
+ var te = Object.defineProperty, __name = (e, t) => te(e, "name", {
6
+ value: t,
7
+ configurable: !0
8
+ });
9
+ //#endregion
10
+ //#region src/admin/composables/useLearnerDataset.ts
11
+ function useLearnerDataset() {
12
+ let e = _(null), t = _(null), n = m(!1), r = m(null);
13
+ async function load(i, a) {
14
+ n.value = !0, r.value = null;
15
+ try {
16
+ let n = await i();
17
+ t.value = j(n, a), e.value = n;
18
+ } catch (e) {
19
+ r.value = e instanceof Error ? e.message : String(e);
20
+ } finally {
21
+ n.value = !1;
22
+ }
23
+ }
24
+ function loadFromCouch(e) {
25
+ return load(() => D({
26
+ ...e,
27
+ auth: { kind: "cookie" }
28
+ }), e.courseId);
29
+ }
30
+ function loadFromFile(e, t) {
31
+ return load(async () => N(await e.text()), t);
32
+ }
33
+ return {
34
+ dump: e,
35
+ dataset: t,
36
+ loading: n,
37
+ error: r,
38
+ loadFromCouch,
39
+ loadFromFile
40
+ };
41
+ }
42
+ function downloadLearnerDump(e) {
43
+ let t = new Blob([JSON.stringify(e, null, 2)], { type: "application/json" }), n = URL.createObjectURL(t), r = document.createElement("a");
44
+ r.href = n, r.download = T(e), r.click(), URL.revokeObjectURL(n);
45
+ }
46
+ //#endregion
47
+ //#region src/admin/components/LearnerDatasetSource.vue?vue&type=script&setup=true&lang.ts
48
+ var ne = { class: "d-flex flex-wrap align-center ga-2" }, re = {
49
+ key: 1,
50
+ class: "d-flex flex-wrap align-center ga-2 mt-3"
51
+ }, ie = /* @__PURE__ */ c({
52
+ __name: "LearnerDatasetSource",
53
+ props: {
54
+ couchUrl: {},
55
+ courseId: {},
56
+ initialUsername: {},
57
+ autoload: { type: Boolean }
58
+ },
59
+ emits: ["loaded"],
60
+ setup(c, { emit: l }) {
61
+ let u = c, d = l, { dump: h, dataset: _, loading: C, error: w, loadFromCouch: T, loadFromFile: E } = useLearnerDataset(), D = m(u.initialUsername ?? ""), O = m(null), k = t(() => {
62
+ let e = _.value?.sessions ?? [];
63
+ return e.length ? `${e[0].startTime.slice(0, 10)} .. ${e[e.length - 1].startTime.slice(0, 10)}` : "";
64
+ });
65
+ function loadLive() {
66
+ !u.couchUrl || !u.courseId || !D.value || T({
67
+ couchUrl: u.couchUrl,
68
+ courseId: u.courseId,
69
+ username: D.value
70
+ });
71
+ }
72
+ function onFile(e) {
73
+ let t = e.target, n = t.files?.[0];
74
+ n && E(n, u.courseId), t.value = "";
75
+ }
76
+ return b(_, (e) => {
77
+ e && h.value && d("loaded", e, h.value);
78
+ }), f(() => {
79
+ u.autoload && loadLive();
80
+ }), (t, c) => {
81
+ let l = g("v-text-field"), u = g("v-btn"), d = g("v-alert"), f = g("v-spacer"), m = g("v-card-text"), b = g("v-card");
82
+ return p(), n(b, { variant: "outlined" }, {
83
+ default: x(() => [s(m, null, {
84
+ default: x(() => [
85
+ a("div", ne, [
86
+ s(l, {
87
+ modelValue: D.value,
88
+ "onUpdate:modelValue": c[0] || (c[0] = (e) => D.value = e),
89
+ label: "Learner",
90
+ density: "compact",
91
+ "hide-details": "",
92
+ style: { "max-width": "16rem" },
93
+ disabled: !t.couchUrl || !t.courseId,
94
+ onKeydown: S(loadLive, ["enter"])
95
+ }, null, 8, ["modelValue", "disabled"]),
96
+ s(u, {
97
+ color: "primary",
98
+ loading: y(C),
99
+ disabled: !t.couchUrl || !t.courseId || !D.value,
100
+ onClick: loadLive
101
+ }, {
102
+ default: x(() => c[3] || (c[3] = [o(" Load ")])),
103
+ _: 1
104
+ }, 8, ["loading", "disabled"]),
105
+ c[5] || (c[5] = a("span", { class: "text-medium-emphasis mx-2" }, "or", -1)),
106
+ s(u, {
107
+ variant: "tonal",
108
+ "prepend-icon": "mdi-file-upload-outline",
109
+ onClick: c[1] || (c[1] = (e) => O.value?.click())
110
+ }, {
111
+ default: x(() => c[4] || (c[4] = [o(" Open dump file ")])),
112
+ _: 1
113
+ }),
114
+ a("input", {
115
+ ref_key: "fileInput",
116
+ ref: O,
117
+ type: "file",
118
+ accept: ".json,application/json",
119
+ hidden: "",
120
+ onChange: onFile
121
+ }, null, 544)
122
+ ]),
123
+ y(w) ? (p(), n(d, {
124
+ key: 0,
125
+ type: "error",
126
+ variant: "tonal",
127
+ density: "compact",
128
+ class: "mt-3"
129
+ }, {
130
+ default: x(() => [o(v(y(w)), 1)]),
131
+ _: 1
132
+ })) : r("", !0),
133
+ y(_) && y(h) ? (p(), i("div", re, [
134
+ a("span", null, [
135
+ a("strong", null, v(y(_).username), 1),
136
+ o(" · " + v(y(_).sessions.length) + " sessions ", 1),
137
+ k.value ? (p(), i(e, { key: 0 }, [o("(" + v(k.value) + ")", 1)], 64)) : r("", !0),
138
+ o(" · " + v(y(_).cardHistories.length) + " cards seen · as of " + v(y(_).asOf.slice(0, 16)) + "Z ", 1),
139
+ y(_).cards ? r("", !0) : (p(), i(e, { key: 1 }, [o("· no course slice")], 64))
140
+ ]),
141
+ s(f),
142
+ s(u, {
143
+ variant: "text",
144
+ size: "small",
145
+ "prepend-icon": "mdi-download",
146
+ onClick: c[2] || (c[2] = (e) => y(downloadLearnerDump)(y(h)))
147
+ }, {
148
+ default: x(() => c[6] || (c[6] = [o(" Save dump ")])),
149
+ _: 1
150
+ })
151
+ ])) : r("", !0)
152
+ ]),
153
+ _: 1
154
+ })]),
155
+ _: 1
156
+ });
157
+ };
158
+ }
159
+ }), G = {
160
+ churned: {
161
+ icon: "mdi-autorenew",
162
+ color: "warning"
163
+ },
164
+ "out-of-content": {
165
+ icon: "mdi-cup-off-outline",
166
+ color: "error"
167
+ },
168
+ walked: {
169
+ icon: "mdi-exit-run",
170
+ color: "error"
171
+ },
172
+ stalled: {
173
+ icon: "mdi-arrow-collapse-horizontal",
174
+ color: "grey"
175
+ },
176
+ grinding: {
177
+ icon: "mdi-alert-octagon-outline",
178
+ color: "warning"
179
+ }
180
+ };
181
+ function statusColor(e) {
182
+ return e === "closed" ? "success" : e === "abandoned" ? "warning" : "error";
183
+ }
184
+ function accuracyClass(e) {
185
+ return e === null ? "text-disabled" : e >= .8 ? "text-success" : e < .5 ? "text-error" : "";
186
+ }
187
+ function deltaClass(e) {
188
+ return e === null || e === 0 ? "text-disabled" : e > 0 ? "text-success" : "text-error";
189
+ }
190
+ function deltaColor(e) {
191
+ return e > 0 ? "success" : e < 0 ? "error" : "grey";
192
+ }
193
+ function fmtTime(e) {
194
+ if (!e) return "—";
195
+ let t = new Date(e);
196
+ return Number.isNaN(t.getTime()) ? e : t.toLocaleString();
197
+ }
198
+ function fmtClock(e) {
199
+ let t = new Date(e);
200
+ return Number.isNaN(t.getTime()) ? e : t.toLocaleTimeString(void 0, { hour12: !1 });
201
+ }
202
+ function shortId(e, t = 28) {
203
+ return e.length > t ? `…${e.slice(-(t - 2))}` : e;
204
+ }
205
+ function useCopy() {
206
+ let e = m(null), t = null;
207
+ async function copy(n, r) {
208
+ try {
209
+ await navigator.clipboard.writeText(n), e.value = r, t && clearTimeout(t), t = setTimeout(() => e.value = null, 1400);
210
+ } catch (e) {
211
+ console.warn("[admin] clipboard write failed", e);
212
+ }
213
+ }
214
+ return {
215
+ copied: e,
216
+ copy
217
+ };
218
+ }
219
+ //#endregion
220
+ //#region src/admin/components/CardDossierDetail.vue?vue&type=script&setup=true&lang.ts
221
+ var ae = {
222
+ key: 0,
223
+ class: "text-caption mb-2"
224
+ }, oe = { class: "text-medium-emphasis" }, se = { class: "d-flex flex-wrap mb-2 ga-1" }, ce = {
225
+ key: 0,
226
+ class: "text-caption text-disabled"
227
+ }, le = { class: "text-caption text-medium-emphasis mb-2" }, ue = {
228
+ key: 0,
229
+ class: "text-error"
230
+ }, de = { class: "text-no-wrap" }, fe = {
231
+ key: 2,
232
+ class: "text-disabled"
233
+ }, pe = {
234
+ class: "text-truncate",
235
+ style: { "max-width": "200px" }
236
+ }, me = /* @__PURE__ */ c({
237
+ __name: "CardDossierDetail",
238
+ props: {
239
+ dossier: {},
240
+ interpreters: {}
241
+ },
242
+ setup(t) {
243
+ function perf(e) {
244
+ let t = F(e);
245
+ return t === null ? "—" : t.toFixed(2);
246
+ }
247
+ function fmtInterval(e) {
248
+ return e ? e < 3600 ? `${Math.round(e / 60)}m` : e < 86400 ? `${Math.round(e / 3600)}h` : `${Math.round(e / 86400)}d` : "none";
249
+ }
250
+ return (t, c) => {
251
+ let l = g("v-chip"), d = g("v-icon"), f = g("v-table");
252
+ return p(), i("div", null, [
253
+ t.dossier.card && Object.keys(t.dossier.card.data).length ? (p(), i("div", ae, [(p(!0), i(e, null, h(t.dossier.card.data, (e, t) => (p(), i("span", {
254
+ key: t,
255
+ class: "mr-3"
256
+ }, [a("span", oe, v(t) + ":", 1), o(" " + v(y(O)(e, 60)), 1)]))), 128))])) : r("", !0),
257
+ a("div", se, [(p(!0), i(e, null, h(t.dossier.card?.tags ?? [], (e) => (p(), n(l, {
258
+ key: e,
259
+ size: "x-small",
260
+ variant: "outlined",
261
+ color: y(M)(t.interpreters, e) ? "primary" : void 0
262
+ }, {
263
+ default: x(() => [o(v(e), 1)]),
264
+ _: 2
265
+ }, 1032, ["color"]))), 128)), t.dossier.card ? r("", !0) : (p(), i("span", ce, "no course data in this dataset"))]),
266
+ a("div", le, [o(" lapses " + v(t.dossier.lapses) + " · streak " + v(t.dossier.streak) + " · best interval " + v(fmtInterval(t.dossier.bestIntervalSeconds)) + " · ", 1), t.dossier.pendingReview ? (p(), i(e, { key: 0 }, [o(" next review " + v(y(fmtTime)(t.dossier.pendingReview.reviewTime)) + " ", 1), t.dossier.pendingReview.overdue ? (p(), i("span", ue, "(overdue)")) : r("", !0)], 64)) : (p(), i(e, { key: 1 }, [o("no review scheduled")], 64))]),
267
+ s(f, {
268
+ density: "compact",
269
+ class: "sk-record-table"
270
+ }, {
271
+ default: x(() => [c[2] || (c[2] = a("thead", null, [a("tr", null, [
272
+ a("th", null, "#"),
273
+ a("th", null, "When"),
274
+ a("th", null, "Result"),
275
+ a("th", null, "Perf"),
276
+ a("th", null, "Prior att."),
277
+ a("th", null, "Time"),
278
+ a("th", null, "Answer")
279
+ ])], -1)), a("tbody", null, [(p(!0), i(e, null, h(t.dossier.records, (e, t) => (p(), i("tr", {
280
+ key: t,
281
+ class: u(e.isCorrect === !1 ? "sk-fail-row" : "")
282
+ }, [
283
+ a("td", null, v(t + 1), 1),
284
+ a("td", de, v(y(fmtTime)(e.timeStamp)), 1),
285
+ a("td", null, [e.isCorrect === !0 ? (p(), n(d, {
286
+ key: 0,
287
+ size: "small",
288
+ color: "success"
289
+ }, {
290
+ default: x(() => c[0] || (c[0] = [o("mdi-check")])),
291
+ _: 1
292
+ })) : e.isCorrect === !1 ? (p(), n(d, {
293
+ key: 1,
294
+ size: "small",
295
+ color: "error"
296
+ }, {
297
+ default: x(() => c[1] || (c[1] = [o("mdi-close")])),
298
+ _: 1
299
+ })) : (p(), i("span", fe, "—"))]),
300
+ a("td", null, v(perf(e)), 1),
301
+ a("td", null, v(e.priorAttemps ?? "—"), 1),
302
+ a("td", null, v(((e.timeSpent ?? 0) / 1e3).toFixed(1)) + "s", 1),
303
+ a("td", pe, v(y(O)(e.userAnswer)), 1)
304
+ ], 2))), 128))])]),
305
+ _: 1
306
+ })
307
+ ]);
308
+ };
309
+ }
310
+ }), K = /* @__PURE__ */ __name((e, t) => {
311
+ let n = e.__vccOpts || e;
312
+ for (let [e, r] of t) n[e] = r;
313
+ return n;
314
+ }, "default"), q = /* @__PURE__ */ K(me, [["__scopeId", "data-v-76b0a622"]]), he = { class: "text-caption text-medium-emphasis mb-4" }, ge = { class: "text-h6" }, _e = { class: "text-caption" }, ve = { class: "d-flex align-center flex-wrap mb-3 ga-3" }, ye = {
315
+ class: "d-flex align-center flex-wrap ga-2",
316
+ style: { width: "100%" }
317
+ }, be = { class: "text-body-2 font-weight-medium" }, xe = { class: "text-caption text-medium-emphasis" }, Se = {
318
+ key: 2,
319
+ class: "text-caption"
320
+ }, Ce = {
321
+ key: 0,
322
+ class: "ml-1 text-disabled"
323
+ }, J = /* @__PURE__ */ K(/* @__PURE__ */ c({
324
+ __name: "LearnerCardHistory",
325
+ props: {
326
+ dataset: {},
327
+ interpreters: {},
328
+ focus: {}
329
+ },
330
+ setup(c) {
331
+ let d = c, f = t(() => w(d.dataset)), _ = m("all"), S = m(""), C = m([]), T = m(null), E = t(() => ({
332
+ stuck: f.value.filter((e) => e.stuck).length,
333
+ failed: f.value.filter((e) => e.fails > 0).length,
334
+ frozen: f.value.filter((e) => e.frozenElo).length
335
+ })), D = t(() => [
336
+ {
337
+ label: "cards seen",
338
+ value: f.value.length,
339
+ color: "primary"
340
+ },
341
+ {
342
+ label: "total attempts",
343
+ value: f.value.reduce((e, t) => e + t.attempts, 0),
344
+ color: "primary"
345
+ },
346
+ {
347
+ label: "total fails",
348
+ value: f.value.reduce((e, t) => e + t.fails, 0),
349
+ color: "error"
350
+ },
351
+ {
352
+ label: "time on cards",
353
+ value: A(f.value.reduce((e, t) => e + t.totalTimeMs, 0) / 1e3),
354
+ color: "grey"
355
+ },
356
+ {
357
+ label: "stuck reviews",
358
+ value: E.value.stuck,
359
+ color: "error"
360
+ },
361
+ {
362
+ label: "frozen ELO",
363
+ value: E.value.frozen,
364
+ color: "warning"
365
+ }
366
+ ]), O = t(() => {
367
+ let e = f.value;
368
+ _.value === "stuck" ? e = e.filter((e) => e.stuck) : _.value === "failed" ? e = e.filter((e) => e.fails > 0) : _.value === "frozen" && (e = e.filter((e) => e.frozenElo));
369
+ let t = S.value?.trim().toLowerCase();
370
+ return t && (e = e.filter((e) => e.cardId.toLowerCase().includes(t) || (e.card?.questionType ?? "").toLowerCase().includes(t) || (e.card?.tags ?? []).some((e) => e.toLowerCase().includes(t)) || Object.values(e.card?.data ?? {}).some((e) => typeof e == "string" && e.toLowerCase().includes(t)))), e;
371
+ });
372
+ function headline(e) {
373
+ let t = Object.values(e.card?.data ?? {}).find((e) => typeof e == "string" && e.length <= 40);
374
+ return typeof t == "string" ? t : "";
375
+ }
376
+ async function applyFocus(e) {
377
+ !e || !f.value.some((t) => t.cardId === e) || (_.value = "all", S.value = e, C.value = [e], await l(), document.querySelector(`[data-card="${CSS.escape(e)}"]`)?.scrollIntoView({
378
+ behavior: "smooth",
379
+ block: "center"
380
+ }), T.value = e, setTimeout(() => {
381
+ T.value === e && (T.value = null);
382
+ }, 2200));
383
+ }
384
+ return b(() => d.focus, (e) => void applyFocus(e), { immediate: !0 }), (t, c) => {
385
+ let l = g("v-icon"), d = g("v-card"), m = g("v-col"), b = g("v-row"), w = g("v-btn"), k = g("v-btn-toggle"), j = g("v-text-field"), M = g("v-alert"), N = g("v-spacer"), P = g("v-chip"), F = g("v-expansion-panel-title"), I = g("v-expansion-panel-text"), L = g("v-expansion-panel"), R = g("v-expansion-panels");
386
+ return p(), i("div", null, [
387
+ a("p", he, [
388
+ c[5] || (c[5] = o(" Every card the learner has met, most painful first (time sunk × failures), with its pending review and current card ELO. ")),
389
+ s(l, {
390
+ size: "x-small",
391
+ color: "error"
392
+ }, {
393
+ default: x(() => c[3] || (c[3] = [o("mdi-sync-alert")])),
394
+ _: 1
395
+ }),
396
+ c[6] || (c[6] = o(" is a failed card holding an overdue review; ")),
397
+ s(l, {
398
+ size: "x-small",
399
+ color: "warning"
400
+ }, {
401
+ default: x(() => c[4] || (c[4] = [o("mdi-snowflake")])),
402
+ _: 1
403
+ }),
404
+ c[7] || (c[7] = o(" is a repeatedly failed card whose ELO has fewer updates than failures. "))
405
+ ]),
406
+ s(b, {
407
+ dense: "",
408
+ class: "mb-2"
409
+ }, {
410
+ default: x(() => [(p(!0), i(e, null, h(D.value, (e) => (p(), n(m, {
411
+ key: e.label,
412
+ cols: "6",
413
+ sm: "4",
414
+ md: "2"
415
+ }, {
416
+ default: x(() => [s(d, {
417
+ variant: "tonal",
418
+ color: e.color,
419
+ class: "pa-2 text-center"
420
+ }, {
421
+ default: x(() => [a("div", ge, v(e.value), 1), a("div", _e, v(e.label), 1)]),
422
+ _: 2
423
+ }, 1032, ["color"])]),
424
+ _: 2
425
+ }, 1024))), 128))]),
426
+ _: 1
427
+ }),
428
+ a("div", ve, [s(k, {
429
+ modelValue: _.value,
430
+ "onUpdate:modelValue": c[0] || (c[0] = (e) => _.value = e),
431
+ density: "compact",
432
+ color: "primary",
433
+ variant: "outlined"
434
+ }, {
435
+ default: x(() => [
436
+ s(w, {
437
+ value: "all",
438
+ size: "small"
439
+ }, {
440
+ default: x(() => [o("All (" + v(f.value.length) + ")", 1)]),
441
+ _: 1
442
+ }),
443
+ s(w, {
444
+ value: "stuck",
445
+ size: "small"
446
+ }, {
447
+ default: x(() => [s(l, {
448
+ start: "",
449
+ size: "small",
450
+ color: "error"
451
+ }, {
452
+ default: x(() => c[8] || (c[8] = [o("mdi-sync-alert")])),
453
+ _: 1
454
+ }), o(" Stuck (" + v(E.value.stuck) + ") ", 1)]),
455
+ _: 1
456
+ }),
457
+ s(w, {
458
+ value: "failed",
459
+ size: "small"
460
+ }, {
461
+ default: x(() => [o("Ever failed (" + v(E.value.failed) + ")", 1)]),
462
+ _: 1
463
+ }),
464
+ s(w, {
465
+ value: "frozen",
466
+ size: "small"
467
+ }, {
468
+ default: x(() => [s(l, {
469
+ start: "",
470
+ size: "small",
471
+ color: "warning"
472
+ }, {
473
+ default: x(() => c[9] || (c[9] = [o("mdi-snowflake")])),
474
+ _: 1
475
+ }), o(" Frozen ELO (" + v(E.value.frozen) + ") ", 1)]),
476
+ _: 1
477
+ })
478
+ ]),
479
+ _: 1
480
+ }, 8, ["modelValue"]), s(j, {
481
+ modelValue: S.value,
482
+ "onUpdate:modelValue": c[1] || (c[1] = (e) => S.value = e),
483
+ density: "compact",
484
+ variant: "outlined",
485
+ "hide-details": "",
486
+ clearable: "",
487
+ placeholder: "filter by card / tag / type / content",
488
+ "prepend-inner-icon": "mdi-magnify",
489
+ style: { "max-width": "320px" }
490
+ }, null, 8, ["modelValue"])]),
491
+ f.value.length === 0 ? (p(), n(M, {
492
+ key: 0,
493
+ type: "info",
494
+ variant: "tonal"
495
+ }, {
496
+ default: x(() => c[10] || (c[10] = [o("No card history for this learner.")])),
497
+ _: 1
498
+ })) : (p(), n(R, {
499
+ key: 1,
500
+ modelValue: C.value,
501
+ "onUpdate:modelValue": c[2] || (c[2] = (e) => C.value = e),
502
+ multiple: "",
503
+ variant: "accordion"
504
+ }, {
505
+ default: x(() => [(p(!0), i(e, null, h(O.value, (e) => (p(), n(L, {
506
+ key: e.cardId,
507
+ value: e.cardId,
508
+ "data-card": e.cardId,
509
+ class: u(e.cardId === T.value ? "sk-card-flash" : "")
510
+ }, {
511
+ default: x(() => [s(F, null, {
512
+ default: x(() => [a("div", ye, [
513
+ e.stuck ? (p(), n(l, {
514
+ key: 0,
515
+ color: "error",
516
+ size: "small",
517
+ title: "failed + overdue review"
518
+ }, {
519
+ default: x(() => c[11] || (c[11] = [o("mdi-sync-alert")])),
520
+ _: 1
521
+ })) : r("", !0),
522
+ e.frozenElo ? (p(), n(l, {
523
+ key: 1,
524
+ color: "warning",
525
+ size: "small",
526
+ title: "repeatedly failed, ELO barely updated"
527
+ }, {
528
+ default: x(() => c[12] || (c[12] = [o(" mdi-snowflake ")])),
529
+ _: 1
530
+ })) : r("", !0),
531
+ a("span", be, v(e.card?.questionType ?? "card"), 1),
532
+ a("code", xe, v(y(shortId)(e.cardId, 24)), 1),
533
+ headline(e) ? (p(), i("span", Se, v(headline(e)), 1)) : r("", !0),
534
+ s(N),
535
+ e.fails > 0 ? (p(), n(P, {
536
+ key: 3,
537
+ size: "x-small",
538
+ color: "error",
539
+ variant: "tonal"
540
+ }, {
541
+ default: x(() => [o(v(e.fails) + " fail" + v(e.fails === 1 ? "" : "s"), 1)]),
542
+ _: 2
543
+ }, 1024)) : r("", !0),
544
+ s(P, {
545
+ size: "x-small",
546
+ variant: "tonal"
547
+ }, {
548
+ default: x(() => [o(v(e.attempts) + " view" + v(e.attempts === 1 ? "" : "s"), 1)]),
549
+ _: 2
550
+ }, 1024),
551
+ s(P, {
552
+ size: "x-small",
553
+ variant: "tonal",
554
+ color: "grey"
555
+ }, {
556
+ default: x(() => [o(v(y(A)(e.totalTimeMs / 1e3)), 1)]),
557
+ _: 2
558
+ }, 1024),
559
+ e.pendingReview ? (p(), n(P, {
560
+ key: 4,
561
+ size: "x-small",
562
+ color: e.pendingReview.overdue ? "error" : "success",
563
+ variant: "flat"
564
+ }, {
565
+ default: x(() => [o(" review " + v(e.pendingReview.overdue ? "overdue" : "scheduled"), 1)]),
566
+ _: 2
567
+ }, 1032, ["color"])) : r("", !0),
568
+ s(P, {
569
+ size: "x-small",
570
+ variant: "tonal",
571
+ color: e.frozenElo ? "warning" : void 0
572
+ }, {
573
+ default: x(() => [o(" ELO " + v(e.cardElo === null ? "?" : Math.round(e.cardElo)) + " ", 1), e.cardEloCount === null ? r("", !0) : (p(), i("span", Ce, "/" + v(e.cardEloCount), 1))]),
574
+ _: 2
575
+ }, 1032, ["color"])
576
+ ])]),
577
+ _: 2
578
+ }, 1024), s(I, null, {
579
+ default: x(() => [s(q, {
580
+ dossier: e,
581
+ interpreters: t.interpreters
582
+ }, null, 8, ["dossier", "interpreters"])]),
583
+ _: 2
584
+ }, 1024)]),
585
+ _: 2
586
+ }, 1032, [
587
+ "value",
588
+ "data-card",
589
+ "class"
590
+ ]))), 128))]),
591
+ _: 1
592
+ }, 8, ["modelValue"]))
593
+ ]);
594
+ };
595
+ }
596
+ }), [["__scopeId", "data-v-bb6b302b"]]), we = { class: "sk-run-detail" }, Te = {
597
+ key: 0,
598
+ class: "mb-2"
599
+ }, Ee = { class: "text-right" }, De = { class: "text-right" }, Oe = { class: "text-right" }, ke = { class: "text-right" }, Ae = {
600
+ key: 1,
601
+ class: "mb-2"
602
+ }, je = { class: "text-right" }, Me = { class: "text-right" }, Ne = { class: "text-right" }, Pe = {
603
+ key: 2,
604
+ class: "mb-2"
605
+ }, Fe = {
606
+ key: 3,
607
+ class: "mb-2"
608
+ }, Ie = {
609
+ key: 1,
610
+ title: "runner-up"
611
+ }, Le = ["onClick"], Re = { class: "text-right" }, ze = { class: "text-caption font-weight-medium" }, Be = ["onClick"], Ve = { class: "text-medium-emphasis" }, He = { class: "text-no-wrap" }, Ue = { class: "text-no-wrap" }, We = { class: "text-right text-no-wrap" }, Ge = { class: "text-caption" }, Ke = {
612
+ key: 4,
613
+ class: "mb-2"
614
+ }, qe = { class: "sk-hint-block" }, Je = {
615
+ key: 5,
616
+ class: "text-caption text-medium-emphasis"
617
+ }, Ye = { key: 2 }, Y = /* @__PURE__ */ K(/* @__PURE__ */ c({
618
+ __name: "RunDetail",
619
+ props: { run: {} },
620
+ emits: ["open-card"],
621
+ setup(c, { emit: l }) {
622
+ let d = c, f = l, m = t(() => {
623
+ let e = d.run.unselectedNew, t = [];
624
+ return e?.nextInLine && t.push({
625
+ label: "next new card in line",
626
+ card: e.nextInLine
627
+ }), e?.topGenerated && t.push({
628
+ label: "top-generated new card (sunk)",
629
+ card: e.topGenerated
630
+ }), t;
631
+ });
632
+ function fmtScore(e) {
633
+ return Math.abs(e) >= .01 || e === 0 ? e.toFixed(2) : e.toExponential(1);
634
+ }
635
+ return (t, c) => {
636
+ let l = g("v-table"), d = g("v-icon");
637
+ return p(), i("div", we, [
638
+ t.run.generators?.length ? (p(), i("div", Te, [c[1] || (c[1] = a("div", { class: "text-caption font-weight-medium" }, "generators", -1)), s(l, {
639
+ density: "compact",
640
+ class: "sk-mini-table"
641
+ }, {
642
+ default: x(() => [c[0] || (c[0] = a("thead", null, [a("tr", null, [
643
+ a("th"),
644
+ a("th", { class: "text-right" }, "cards"),
645
+ a("th", { class: "text-right" }, "new"),
646
+ a("th", { class: "text-right" }, "review"),
647
+ a("th", { class: "text-right" }, "top")
648
+ ])], -1)), a("tbody", null, [(p(!0), i(e, null, h(t.run.generators, (e) => (p(), i("tr", { key: e.name }, [
649
+ a("td", null, v(e.name), 1),
650
+ a("td", Ee, v(e.cardCount), 1),
651
+ a("td", De, v(e.newCount), 1),
652
+ a("td", Oe, v(e.reviewCount), 1),
653
+ a("td", ke, v(e.topScore.toFixed(2)), 1)
654
+ ]))), 128))])]),
655
+ _: 1
656
+ })])) : r("", !0),
657
+ t.run.filters?.length ? (p(), i("div", Ae, [c[3] || (c[3] = a("div", { class: "text-caption font-weight-medium" }, "filters", -1)), s(l, {
658
+ density: "compact",
659
+ class: "sk-mini-table"
660
+ }, {
661
+ default: x(() => [c[2] || (c[2] = a("thead", null, [a("tr", null, [
662
+ a("th"),
663
+ a("th", null, "kind"),
664
+ a("th", { class: "text-right" }, "boosted"),
665
+ a("th", { class: "text-right" }, "penalized"),
666
+ a("th", { class: "text-right" }, "passed"),
667
+ a("th", { class: "text-right" }, "removed")
668
+ ])], -1)), a("tbody", null, [(p(!0), i(e, null, h(t.run.filters, (e) => (p(), i("tr", { key: e.name }, [
669
+ a("td", null, v(e.name), 1),
670
+ a("td", null, v(e.kind ?? "—"), 1),
671
+ a("td", je, v(e.boosted), 1),
672
+ a("td", Me, v(e.penalized), 1),
673
+ a("td", Ne, v(e.passed), 1),
674
+ a("td", { class: u(["text-right", e.removed > 0 ? "text-error" : ""]) }, v(e.removed), 3)
675
+ ]))), 128))])]),
676
+ _: 1
677
+ })])) : r("", !0),
678
+ t.run.regulators?.length ? (p(), i("div", Pe, [c[4] || (c[4] = a("div", { class: "text-caption font-weight-medium" }, "regulators", -1)), (p(!0), i(e, null, h(t.run.regulators, (e) => (p(), i("div", {
679
+ key: e.name,
680
+ class: "sk-mini-line"
681
+ }, [a("span", { class: u(e.multiplier > 1 ? "text-warning" : "text-medium-emphasis") }, " ×" + v(e.multiplier.toFixed(2)), 3), o(" " + v(y(I)(e)), 1)]))), 128))])) : r("", !0),
682
+ t.run.cards?.length ? (p(), i("div", Fe, [c[7] || (c[7] = a("div", { class: "text-caption font-weight-medium" }, "selection", -1)), s(l, {
683
+ density: "compact",
684
+ class: "sk-mini-table"
685
+ }, {
686
+ default: x(() => [c[6] || (c[6] = a("thead", null, [a("tr", null, [
687
+ a("th"),
688
+ a("th", null, "card"),
689
+ a("th", null, "origin"),
690
+ a("th", null, "generator"),
691
+ a("th", { class: "text-right" }, "score")
692
+ ])], -1)), a("tbody", null, [(p(!0), i(e, null, h(t.run.cards, (e) => (p(), i("tr", {
693
+ key: e.cardId,
694
+ class: u(e.selected ? "" : "text-medium-emphasis")
695
+ }, [
696
+ a("td", null, [e.selected ? (p(), n(d, {
697
+ key: 0,
698
+ size: "x-small",
699
+ color: "success"
700
+ }, {
701
+ default: x(() => c[5] || (c[5] = [o("mdi-check")])),
702
+ _: 1
703
+ })) : (p(), i("span", Ie, "·"))]),
704
+ a("td", null, [a("a", {
705
+ class: "sk-card-link",
706
+ onClick: (t) => f("open-card", e.cardId)
707
+ }, v(e.cardId), 9, Le)]),
708
+ a("td", null, v(e.origin), 1),
709
+ a("td", null, v(e.generator ?? "—"), 1),
710
+ a("td", Re, v(fmtScore(e.score)), 1)
711
+ ], 2))), 128))])]),
712
+ _: 1
713
+ })])) : r("", !0),
714
+ (p(!0), i(e, null, h(m.value, (t) => (p(), i("div", {
715
+ key: t.label,
716
+ class: "mb-2"
717
+ }, [a("div", ze, [
718
+ o(v(t.label) + ": ", 1),
719
+ a("a", {
720
+ class: "sk-card-link",
721
+ onClick: (e) => f("open-card", t.card.cardId)
722
+ }, v(t.card.cardId), 9, Be),
723
+ a("span", Ve, " (" + v(t.card.origin) + ", " + v(t.card.generator ?? "?") + ") final " + v(fmtScore(t.card.score)), 1)
724
+ ]), s(l, {
725
+ density: "compact",
726
+ class: "sk-mini-table"
727
+ }, {
728
+ default: x(() => [a("tbody", null, [(p(!0), i(e, null, h(t.card.trail, (e, t) => (p(), i("tr", { key: t }, [
729
+ a("td", He, v(e.action), 1),
730
+ a("td", Ue, v(e.strategyName), 1),
731
+ a("td", We, v(fmtScore(e.score)), 1),
732
+ a("td", Ge, v(e.reason), 1)
733
+ ]))), 128))])]),
734
+ _: 2
735
+ }, 1024)]))), 128)),
736
+ t.run.hints ? (p(), i("div", Ke, [c[8] || (c[8] = a("div", { class: "text-caption font-weight-medium" }, "hints", -1)), a("pre", qe, v(JSON.stringify(t.run.hints, null, 2)), 1)])) : r("", !0),
737
+ t.run.discardedTail ? (p(), i("div", Je, [
738
+ o(" discarded tail: " + v(t.run.discardedTail.count) + " cards ", 1),
739
+ t.run.discardedTail.scoreRange ? (p(), i(e, { key: 0 }, [o(" · score [" + v(t.run.discardedTail.scoreRange[0].toFixed(2)) + ", " + v(t.run.discardedTail.scoreRange[1].toFixed(2)) + "] ", 1)], 64)) : r("", !0),
740
+ t.run.discardedTail.eloRange ? (p(), i(e, { key: 1 }, [o(" · ELO [" + v(Math.round(t.run.discardedTail.eloRange[0])) + ", " + v(Math.round(t.run.discardedTail.eloRange[1])) + "] ", 1)], 64)) : r("", !0),
741
+ t.run.discardedTail.note ? (p(), i("div", Ye, v(t.run.discardedTail.note), 1)) : r("", !0)
742
+ ])) : r("", !0)
743
+ ]);
744
+ };
745
+ }
746
+ }), [["__scopeId", "data-v-70675763"]]), Xe = { class: "d-flex align-center flex-wrap mb-4 ga-2" }, Ze = { class: "text-caption text-medium-emphasis" }, Qe = { class: "text-right" }, $e = { class: "text-right" }, et = {
747
+ key: 1,
748
+ class: "text-caption text-disabled"
749
+ }, tt = { class: "sk-hint-block" }, nt = { class: "sk-hint-block" }, rt = { class: "text-caption text-medium-emphasis mb-4" }, it = { class: "text-right" }, at = { class: "text-right" }, ot = { class: "text-disabled" }, st = { class: "text-right" }, ct = { class: "d-flex flex-wrap ga-5" }, lt = { class: "text-caption text-medium-emphasis" }, ut = { class: "text-h6 text-success" }, dt = { class: "text-caption text-medium-emphasis" }, ft = { class: "text-h6 text-error" }, pt = { class: "text-h6" }, mt = { class: "text-caption text-disabled" }, ht = { class: "d-flex align-center flex-wrap mb-2 ga-3" }, gt = {
750
+ key: 4,
751
+ class: "sk-timeline"
752
+ }, _t = { class: "sk-tl-time text-caption text-disabled" }, vt = { class: "sk-tl-gutter" }, yt = {
753
+ key: 0,
754
+ class: "sk-tl-body"
755
+ }, bt = { class: "d-flex align-center flex-wrap ga-1" }, xt = { class: "font-weight-medium" }, St = { class: "text-caption text-medium-emphasis ml-2" }, Ct = {
756
+ key: 1,
757
+ class: "text-caption text-error ml-2"
758
+ }, wt = {
759
+ key: 1,
760
+ class: "sk-tl-body"
761
+ }, Tt = ["title", "onClick"], Et = { class: "text-caption" }, Dt = { class: "text-caption text-medium-emphasis ml-2" }, Ot = {
762
+ key: 0,
763
+ class: "text-caption text-disabled ml-2"
764
+ }, X = /* @__PURE__ */ K(/* @__PURE__ */ c({
765
+ __name: "LearnerSessionDetail",
766
+ props: {
767
+ dataset: {},
768
+ sessionId: {},
769
+ interpreters: {}
770
+ },
771
+ emits: ["open-card"],
772
+ setup(c, { emit: l }) {
773
+ let d = c, f = l, _ = t(() => B(d.dataset, d.sessionId, d.interpreters)), b = t(() => _.value.summary.doc), S = t(() => z(_.value?.timeline ?? [], d.interpreters)), w = t(() => E(_.value?.timeline ?? [])), T = t(() => _.value?.timeline.filter((e) => e.kind === "response").length ?? 0), D = m("all"), j = t(() => {
774
+ let e = _.value?.timeline ?? [];
775
+ return !D.value || D.value === "all" ? e : e.filter((e) => e.kind === D.value);
776
+ }), N = m(/* @__PURE__ */ new Set());
777
+ function toggle(e) {
778
+ let t = new Set(N.value);
779
+ t.has(e) ? t.delete(e) : t.add(e), N.value = t;
780
+ }
781
+ let { copied: P, copy: F } = useCopy();
782
+ function asText() {
783
+ return _.value ? V(d.dataset.username, _.value, {
784
+ servedTags: S.value,
785
+ eloLedger: w.value
786
+ }, d.interpreters) : "";
787
+ }
788
+ let I = t(() => {
789
+ let e = _.value?.summary;
790
+ if (!e) return "";
791
+ let t = A(e.doc.plannedSeconds);
792
+ return e.durationSeconds === null ? `— of ${t}` : `${A(e.durationSeconds)} of ${t}`;
793
+ });
794
+ function skillTags(e) {
795
+ return e.filter((e) => M(d.interpreters, e)).slice(0, 3);
796
+ }
797
+ function fmtNum(e) {
798
+ return e === null ? "—" : Number.isInteger(e) ? String(e) : e.toFixed(1);
799
+ }
800
+ function fmtHints(e) {
801
+ return e == null ? "none" : JSON.stringify(e, null, 2);
802
+ }
803
+ function entryIcon(e) {
804
+ return e.kind === "run" ? "mdi-cog-refresh-outline" : e.isCorrect === !0 ? "mdi-check-circle-outline" : e.isCorrect === !1 ? "mdi-close-circle-outline" : "mdi-circle-small";
805
+ }
806
+ function entryColor(e) {
807
+ return e.kind === "run" ? "primary" : e.isCorrect === !0 ? "success" : e.isCorrect === !1 ? "error" : "grey";
808
+ }
809
+ function rowClass(e) {
810
+ return e.kind === "run" ? "sk-tl-run" : e.isCorrect === !1 ? "sk-tl-fail" : "";
811
+ }
812
+ return (t, c) => {
813
+ let l = g("v-alert"), d = g("v-chip"), m = g("v-icon"), E = g("v-spacer"), A = g("v-btn"), z = g("v-table"), B = g("v-card"), V = g("v-col"), U = g("v-row"), W = g("v-btn-toggle");
814
+ return p(), i("div", null, [_.value ? (p(), i(e, { key: 1 }, [
815
+ a("div", Xe, [
816
+ s(d, {
817
+ size: "small",
818
+ variant: "flat",
819
+ color: y(statusColor)(b.value.status)
820
+ }, {
821
+ default: x(() => [o(v(b.value.status), 1)]),
822
+ _: 1
823
+ }, 8, ["color"]),
824
+ s(d, {
825
+ size: "small",
826
+ variant: "tonal"
827
+ }, {
828
+ default: x(() => [o(v(y(fmtTime)(b.value.startTime)), 1)]),
829
+ _: 1
830
+ }),
831
+ s(d, {
832
+ size: "small",
833
+ variant: "tonal",
834
+ color: "grey"
835
+ }, {
836
+ default: x(() => [o(v(I.value), 1)]),
837
+ _: 1
838
+ }),
839
+ (p(!0), i(e, null, h(_.value.summary.flags, (e) => (p(), n(d, {
840
+ key: e,
841
+ size: "small",
842
+ variant: "tonal",
843
+ color: y(G)[e].color,
844
+ title: y(C)[e].help
845
+ }, {
846
+ default: x(() => [s(m, {
847
+ start: "",
848
+ size: "small"
849
+ }, {
850
+ default: x(() => [o(v(y(G)[e].icon), 1)]),
851
+ _: 2
852
+ }, 1024), o(" " + v(y(C)[e].label), 1)]),
853
+ _: 2
854
+ }, 1032, ["color", "title"]))), 128)),
855
+ s(E),
856
+ a("code", Ze, v(t.sessionId), 1),
857
+ s(A, {
858
+ variant: "text",
859
+ size: "small",
860
+ "prepend-icon": y(P) === "session" ? "mdi-check" : "mdi-clipboard-text-outline",
861
+ color: y(P) === "session" ? "success" : void 0,
862
+ onClick: c[0] || (c[0] = (e) => y(F)(asText(), "session"))
863
+ }, {
864
+ default: x(() => [o(v(y(P) === "session" ? "Copied" : "Copy as text"), 1)]),
865
+ _: 1
866
+ }, 8, ["prepend-icon", "color"])
867
+ ]),
868
+ _.value.responsesMissing ? (p(), n(l, {
869
+ key: 0,
870
+ type: "warning",
871
+ variant: "tonal",
872
+ density: "compact",
873
+ class: "mb-4"
874
+ }, {
875
+ default: x(() => [o(" The session records " + v(b.value.tally?.responses) + " responses but only " + v(T.value) + " card records carry its id. The timeline is incomplete. ", 1)]),
876
+ _: 1
877
+ })) : r("", !0),
878
+ s(U, { class: "mb-2" }, {
879
+ default: x(() => [s(V, {
880
+ cols: "12",
881
+ md: "6"
882
+ }, {
883
+ default: x(() => [s(B, {
884
+ variant: "outlined",
885
+ class: "pa-3 h-100"
886
+ }, {
887
+ default: x(() => [
888
+ c[4] || (c[4] = a("div", { class: "text-subtitle-2 mb-1" }, "Learner state", -1)),
889
+ c[5] || (c[5] = a("div", { class: "text-caption text-medium-emphasis mb-3" }, " What the navigator was choosing against, and what moved. ", -1)),
890
+ _.value.summary.state.length ? (p(), n(z, {
891
+ key: 0,
892
+ density: "compact",
893
+ class: "sk-mini-table"
894
+ }, {
895
+ default: x(() => [c[3] || (c[3] = a("thead", null, [a("tr", null, [
896
+ a("th"),
897
+ a("th", { class: "text-right" }, "Start"),
898
+ a("th", { class: "text-right" }, "End"),
899
+ a("th", { class: "text-right" }, "Δ")
900
+ ])], -1)), a("tbody", null, [(p(!0), i(e, null, h(_.value.summary.state, (e) => (p(), i("tr", { key: e.label }, [
901
+ a("td", null, v(e.label), 1),
902
+ a("td", Qe, v(fmtNum(e.from)), 1),
903
+ a("td", $e, v(fmtNum(e.to)), 1),
904
+ a("td", { class: u(["text-right", y(deltaClass)(e.delta)]) }, v(y(k)(e.delta)), 3)
905
+ ]))), 128))])]),
906
+ _: 1
907
+ })) : (p(), i("div", et, "No state snapshot recorded."))
908
+ ]),
909
+ _: 1
910
+ })]),
911
+ _: 1
912
+ }), s(V, {
913
+ cols: "12",
914
+ md: "6"
915
+ }, {
916
+ default: x(() => [s(B, {
917
+ variant: "outlined",
918
+ class: "pa-3 h-100"
919
+ }, {
920
+ default: x(() => [
921
+ c[6] || (c[6] = a("div", { class: "text-subtitle-2 mb-1" }, "Session hints", -1)),
922
+ c[7] || (c[7] = a("div", { class: "text-caption text-medium-emphasis mb-3" }, " What outcome observers told the navigator. They act only through these. ", -1)),
923
+ c[8] || (c[8] = a("span", { class: "text-caption font-weight-medium" }, "at open", -1)),
924
+ a("pre", tt, v(fmtHints(b.value.config?.initHints)), 1),
925
+ c[9] || (c[9] = a("span", { class: "text-caption font-weight-medium" }, "at close", -1)),
926
+ a("pre", nt, v(fmtHints(b.value.finalHints)), 1)
927
+ ]),
928
+ _: 1
929
+ })]),
930
+ _: 1
931
+ })]),
932
+ _: 1
933
+ }),
934
+ a("div", rt, [o(" batch limit " + v(b.value.config?.defaultBatchLimit ?? "?") + " · " + v(b.value.config?.sourceCount ?? "?") + " source(s) · opening queues supply " + v(b.value.initialQueues?.supplyQ ?? "?") + " / failed " + v(b.value.initialQueues?.failedQ ?? "?") + " ", 1), b.value.tally ? (p(), i(e, { key: 0 }, [o(" · ended with " + v(b.value.tally.failedQRemaining) + " still in the failed queue", 1)], 64)) : r("", !0)]),
935
+ S.value.length ? (p(), n(B, {
936
+ key: 1,
937
+ variant: "outlined",
938
+ class: "pa-3 mb-4"
939
+ }, {
940
+ default: x(() => [
941
+ c[11] || (c[11] = a("div", { class: "text-subtitle-2 mb-1" }, "Served tags", -1)),
942
+ c[12] || (c[12] = a("div", { class: "text-caption text-medium-emphasis mb-2" }, " What this sitting put in front of the learner, by content tag. A response with several tags counts toward each. ", -1)),
943
+ s(z, {
944
+ density: "compact",
945
+ class: "sk-mini-table"
946
+ }, {
947
+ default: x(() => [c[10] || (c[10] = a("thead", null, [a("tr", null, [
948
+ a("th", null, "Tag"),
949
+ a("th", { class: "text-right" }, "Resp."),
950
+ a("th", { class: "text-right" }, "Cards"),
951
+ a("th", { class: "text-right" }, "Accuracy"),
952
+ a("th", { class: "text-right" }, "Time")
953
+ ])], -1)), a("tbody", null, [(p(!0), i(e, null, h(S.value, (n) => (p(), i("tr", { key: n.tag }, [
954
+ a("td", null, [a("code", { class: u(y(M)(t.interpreters, n.tag) ? "text-primary" : "") }, v(n.tag), 3)]),
955
+ a("td", it, v(n.responses), 1),
956
+ a("td", at, v(n.cards), 1),
957
+ a("td", { class: u(["text-right", y(accuracyClass)(n.accuracy)]) }, [n.accuracy === null ? (p(), i(e, { key: 0 }, [o("—")], 64)) : (p(), i(e, { key: 1 }, [o(v(Math.round(n.accuracy * 100)) + "% ", 1), a("span", ot, "(" + v(n.correct) + "/" + v(n.correct + n.incorrect) + ")", 1)], 64))], 2),
958
+ a("td", st, v((n.totalTimeMs / 1e3).toFixed(1)) + "s", 1)
959
+ ]))), 128))])]),
960
+ _: 1
961
+ })
962
+ ]),
963
+ _: 1
964
+ })) : r("", !0),
965
+ w.value ? (p(), n(B, {
966
+ key: 2,
967
+ variant: "outlined",
968
+ class: "pa-3 mb-4"
969
+ }, {
970
+ default: x(() => [
971
+ c[15] || (c[15] = a("div", { class: "text-subtitle-2 mb-1" }, "ELO ledger", -1)),
972
+ c[16] || (c[16] = a("div", { class: "text-caption text-medium-emphasis mb-3" }, " The per-response learner global-ELO exchanges that net to the session's ELO move. Wins above the learner's level pay little; losses below it cost a lot, so a high hit rate can still net negative. ", -1)),
973
+ a("div", ct, [
974
+ a("div", null, [c[13] || (c[13] = a("div", { class: "text-caption text-medium-emphasis" }, "net", -1)), a("div", { class: u(["text-h6", y(deltaClass)(w.value.net)]) }, v(y(k)(w.value.net)), 3)]),
975
+ a("div", null, [a("div", lt, "gained · " + v(w.value.wins) + " win(s)", 1), a("div", ut, v(y(k)(w.value.gained)), 1)]),
976
+ a("div", null, [a("div", dt, "lost · " + v(w.value.losses) + " loss(es)", 1), a("div", ft, v(y(k)(w.value.lost)), 1)]),
977
+ a("div", null, [
978
+ c[14] || (c[14] = a("div", { class: "text-caption text-medium-emphasis" }, "exchanges", -1)),
979
+ a("div", pt, v(w.value.count), 1),
980
+ a("div", mt, "of " + v(T.value) + " response(s)", 1)
981
+ ])
982
+ ])
983
+ ]),
984
+ _: 1
985
+ })) : r("", !0),
986
+ a("div", ht, [c[17] || (c[17] = a("h2", { class: "text-h6" }, "Timeline", -1)), s(W, {
987
+ modelValue: D.value,
988
+ "onUpdate:modelValue": c[1] || (c[1] = (e) => D.value = e),
989
+ density: "compact",
990
+ color: "primary",
991
+ variant: "outlined"
992
+ }, {
993
+ default: x(() => [
994
+ s(A, {
995
+ value: "all",
996
+ size: "small"
997
+ }, {
998
+ default: x(() => [o("All (" + v(_.value.timeline.length) + ")", 1)]),
999
+ _: 1
1000
+ }),
1001
+ s(A, {
1002
+ value: "response",
1003
+ size: "small"
1004
+ }, {
1005
+ default: x(() => [o("Responses (" + v(T.value) + ")", 1)]),
1006
+ _: 1
1007
+ }),
1008
+ s(A, {
1009
+ value: "run",
1010
+ size: "small"
1011
+ }, {
1012
+ default: x(() => [o("Runs (" + v(_.value.timeline.length - T.value) + ")", 1)]),
1013
+ _: 1
1014
+ })
1015
+ ]),
1016
+ _: 1
1017
+ }, 8, ["modelValue"])]),
1018
+ _.value.timeline.length === 0 ? (p(), n(l, {
1019
+ key: 3,
1020
+ type: "info",
1021
+ variant: "tonal"
1022
+ }, {
1023
+ default: x(() => c[18] || (c[18] = [o("Nothing recorded for this session.")])),
1024
+ _: 1
1025
+ })) : (p(), i("div", gt, [(p(!0), i(e, null, h(j.value, (l, g) => (p(), i("div", {
1026
+ key: g,
1027
+ class: u(["sk-tl-row", rowClass(l)])
1028
+ }, [
1029
+ a("div", _t, v(y(fmtClock)(l.at)), 1),
1030
+ a("div", vt, [s(m, {
1031
+ size: "small",
1032
+ color: entryColor(l)
1033
+ }, {
1034
+ default: x(() => [o(v(entryIcon(l)), 1)]),
1035
+ _: 2
1036
+ }, 1032, ["color"])]),
1037
+ l.kind === "run" ? (p(), i("div", yt, [a("div", bt, [
1038
+ a("span", xt, v(l.run.label), 1),
1039
+ l.run.mode ? (p(), n(d, {
1040
+ key: 0,
1041
+ size: "x-small",
1042
+ variant: "tonal"
1043
+ }, {
1044
+ default: x(() => [o(v(l.run.mode), 1)]),
1045
+ _: 2
1046
+ }, 1024)) : r("", !0),
1047
+ a("span", St, " generated " + v(l.run.generatedCount) + " → selected " + v(l.run.finalCount) + " (" + v(l.run.newSelected) + " new, " + v(l.run.reviewsSelected) + " review) ", 1),
1048
+ l.run.generatedCount > 0 && l.run.finalCount === 0 ? (p(), i("span", Ct, " nothing survived ")) : r("", !0),
1049
+ typeof l.run.userElo == "number" ? (p(), n(d, {
1050
+ key: 2,
1051
+ size: "x-small",
1052
+ variant: "tonal",
1053
+ color: "grey",
1054
+ class: "ml-2"
1055
+ }, {
1056
+ default: x(() => [o(" ELO " + v(Math.round(l.run.userElo)), 1)]),
1057
+ _: 2
1058
+ }, 1024)) : r("", !0),
1059
+ s(E),
1060
+ s(A, {
1061
+ icon: N.value.has(l.run.runId) ? "mdi-chevron-up" : "mdi-chevron-down",
1062
+ size: "x-small",
1063
+ variant: "text",
1064
+ onClick: (e) => toggle(l.run.runId)
1065
+ }, null, 8, ["icon", "onClick"]),
1066
+ s(A, {
1067
+ icon: y(P) === l.run.runId ? "mdi-check" : "mdi-content-copy",
1068
+ size: "x-small",
1069
+ variant: "text",
1070
+ color: y(P) === l.run.runId ? "success" : void 0,
1071
+ title: "Copy run as text",
1072
+ onClick: (e) => y(F)(y(L)(l.run), l.run.runId)
1073
+ }, null, 8, [
1074
+ "icon",
1075
+ "color",
1076
+ "onClick"
1077
+ ])
1078
+ ]), N.value.has(l.run.runId) ? (p(), n(Y, {
1079
+ key: 0,
1080
+ run: l.run,
1081
+ class: "mt-1",
1082
+ onOpenCard: c[2] || (c[2] = (e) => f("open-card", e))
1083
+ }, null, 8, ["run"])) : r("", !0)])) : (p(), i("div", wt, [
1084
+ a("a", {
1085
+ class: "sk-card-link",
1086
+ title: `Open ${l.cardId} in card history`,
1087
+ onClick: (e) => f("open-card", l.cardId)
1088
+ }, [o(v(l.questionType ?? "card") + " ", 1), a("code", Et, v(y(shortId)(l.cardId)), 1)], 8, Tt),
1089
+ (p(!0), i(e, null, h(skillTags(l.tags), (e) => (p(), n(d, {
1090
+ key: e,
1091
+ size: "x-small",
1092
+ variant: "outlined",
1093
+ color: "primary",
1094
+ class: "ml-1"
1095
+ }, {
1096
+ default: x(() => [o(v(y(H)(t.interpreters, e)), 1)]),
1097
+ _: 2
1098
+ }, 1024))), 128)),
1099
+ a("span", Dt, [o(v((l.timeSpentMs / 1e3).toFixed(1)) + "s ", 1), l.priorAttempts ? (p(), i(e, { key: 0 }, [o(" · attempt " + v(l.priorAttempts + 1), 1)], 64)) : r("", !0)]),
1100
+ l.userAnswer == null ? r("", !0) : (p(), i("span", Ot, "“" + v(y(O)(l.userAnswer)) + "”", 1)),
1101
+ l.elo ? (p(), i(e, { key: 1 }, [s(d, {
1102
+ size: "x-small",
1103
+ variant: "tonal",
1104
+ color: y(deltaColor)(l.elo.global.delta),
1105
+ class: "ml-2",
1106
+ title: `learner global ELO ${l.elo.global.before} → ${l.elo.global.after} · userScore ${l.elo.userScore.toFixed(2)}`
1107
+ }, {
1108
+ default: x(() => [o(" ELO " + v(y(k)(l.elo.global.delta)), 1)]),
1109
+ _: 2
1110
+ }, 1032, ["color", "title"]), (p(!0), i(e, null, h(y(R)(l.elo), (e) => (p(), n(d, {
1111
+ key: e.tag,
1112
+ size: "x-small",
1113
+ variant: "text",
1114
+ color: y(deltaColor)(e.delta),
1115
+ class: "sk-elo-tag",
1116
+ title: `${e.tag}: ${e.before} → ${e.after} (score ${e.score?.toFixed(2)})`
1117
+ }, {
1118
+ default: x(() => [o(v(y(H)(t.interpreters, e.tag)) + " " + v(y(k)(e.delta)), 1)]),
1119
+ _: 2
1120
+ }, 1032, ["color", "title"]))), 128))], 64)) : r("", !0)
1121
+ ]))
1122
+ ], 2))), 128))]))
1123
+ ], 64)) : (p(), n(l, {
1124
+ key: 0,
1125
+ type: "info",
1126
+ variant: "tonal"
1127
+ }, {
1128
+ default: x(() => [o("No session with id " + v(t.sessionId) + ".", 1)]),
1129
+ _: 1
1130
+ }))]);
1131
+ };
1132
+ }
1133
+ }), [["__scopeId", "data-v-36c9215c"]]), kt = { class: "text-h6" }, At = { class: "text-caption" }, jt = { class: "d-flex align-center flex-wrap mb-3 ga-3" }, Mt = ["onClick"], Nt = { class: "text-no-wrap" }, Pt = { class: "text-right" }, Ft = { class: "text-right" }, It = {
1134
+ key: 1,
1135
+ class: "text-disabled"
1136
+ }, Lt = { class: "text-right" }, Rt = { class: "text-no-wrap text-caption" }, zt = { class: "text-no-wrap text-caption" }, Bt = { key: 0 }, Vt = {
1137
+ key: 0,
1138
+ class: "text-disabled"
1139
+ }, Ht = { class: "text-right" }, Z = /* @__PURE__ */ K(/* @__PURE__ */ c({
1140
+ __name: "LearnerSessionList",
1141
+ props: {
1142
+ dataset: {},
1143
+ interpreters: {}
1144
+ },
1145
+ emits: ["open"],
1146
+ setup(c, { emit: l }) {
1147
+ let d = c, f = l, _ = t(() => U(d.dataset, d.interpreters)), b = m("all"), S = t(() => {
1148
+ let e = /* @__PURE__ */ new Map();
1149
+ for (let t of _.value) for (let n of t.flags) e.set(n, (e.get(n) ?? 0) + 1);
1150
+ return [...e.entries()].map(([e, t]) => ({
1151
+ flag: e,
1152
+ count: t
1153
+ })).sort((e, t) => t.count - e.count);
1154
+ }), w = t(() => !b.value || b.value === "all" ? _.value : _.value.filter((e) => e.flags.includes(b.value))), T = t(() => {
1155
+ let e = _.value, t = e.filter((e) => e.doc.status === "closed").length, n = e.reduce((e, t) => e + (t.doc.tally?.responses ?? 0), 0), r = e.reduce((e, t) => e + (t.doc.tally?.correct ?? 0), 0), i = e.reduce((e, t) => e + (t.durationSeconds ?? 0), 0);
1156
+ return [
1157
+ {
1158
+ label: "sessions",
1159
+ value: e.length,
1160
+ color: "primary"
1161
+ },
1162
+ {
1163
+ label: "completed",
1164
+ value: t,
1165
+ color: "success"
1166
+ },
1167
+ {
1168
+ label: "not completed",
1169
+ value: e.length - t,
1170
+ color: "error"
1171
+ },
1172
+ {
1173
+ label: "responses",
1174
+ value: n,
1175
+ color: "primary"
1176
+ },
1177
+ {
1178
+ label: "overall acc.",
1179
+ value: n > 0 ? `${Math.round(r / n * 100)}%` : "—",
1180
+ color: "primary"
1181
+ },
1182
+ {
1183
+ label: "time studied",
1184
+ value: A(i),
1185
+ color: "grey"
1186
+ }
1187
+ ];
1188
+ });
1189
+ function deltas(e) {
1190
+ let t = [];
1191
+ e.eloNet !== null && t.push({
1192
+ label: "elo",
1193
+ delta: Math.round(e.eloNet)
1194
+ });
1195
+ for (let n of d.interpreters?.progressMetrics ?? []) {
1196
+ let r = e.state.find((e) => e.label === n);
1197
+ r && t.push({
1198
+ label: n,
1199
+ delta: r.delta
1200
+ });
1201
+ }
1202
+ return t;
1203
+ }
1204
+ function clockText(e) {
1205
+ let t = A(e.doc.plannedSeconds);
1206
+ return e.durationSeconds === null ? `— of ${t}` : `${A(e.durationSeconds)} of ${t}`;
1207
+ }
1208
+ return (t, c) => {
1209
+ let l = g("v-card"), d = g("v-col"), m = g("v-row"), E = g("v-btn"), D = g("v-icon"), O = g("v-btn-toggle"), A = g("v-alert"), j = g("v-chip"), M = g("v-table");
1210
+ return p(), i("div", null, [
1211
+ c[4] || (c[4] = a("p", { class: "text-caption text-medium-emphasis mb-4" }, [
1212
+ o(" One row per sitting. Flags name a specific way the session failed to do its job; open one to read its interleaved timeline of responses and pipeline runs. "),
1213
+ a("strong", null, "Δ"),
1214
+ o(" is movement across the session: ELO, then any progress metrics the course declares. ")
1215
+ ], -1)),
1216
+ s(m, {
1217
+ dense: "",
1218
+ class: "mb-2"
1219
+ }, {
1220
+ default: x(() => [(p(!0), i(e, null, h(T.value, (e) => (p(), n(d, {
1221
+ key: e.label,
1222
+ cols: "6",
1223
+ sm: "4",
1224
+ md: "2"
1225
+ }, {
1226
+ default: x(() => [s(l, {
1227
+ variant: "tonal",
1228
+ color: e.color,
1229
+ class: "pa-2 text-center"
1230
+ }, {
1231
+ default: x(() => [a("div", kt, v(e.value), 1), a("div", At, v(e.label), 1)]),
1232
+ _: 2
1233
+ }, 1032, ["color"])]),
1234
+ _: 2
1235
+ }, 1024))), 128))]),
1236
+ _: 1
1237
+ }),
1238
+ a("div", jt, [s(O, {
1239
+ modelValue: b.value,
1240
+ "onUpdate:modelValue": c[0] || (c[0] = (e) => b.value = e),
1241
+ density: "compact",
1242
+ color: "primary",
1243
+ variant: "outlined"
1244
+ }, {
1245
+ default: x(() => [s(E, {
1246
+ value: "all",
1247
+ size: "small"
1248
+ }, {
1249
+ default: x(() => [o("All (" + v(_.value.length) + ")", 1)]),
1250
+ _: 1
1251
+ }), (p(!0), i(e, null, h(S.value, (e) => (p(), n(E, {
1252
+ key: e.flag,
1253
+ value: e.flag,
1254
+ size: "small",
1255
+ title: y(C)[e.flag].help
1256
+ }, {
1257
+ default: x(() => [s(D, {
1258
+ start: "",
1259
+ size: "small",
1260
+ color: y(G)[e.flag].color
1261
+ }, {
1262
+ default: x(() => [o(v(y(G)[e.flag].icon), 1)]),
1263
+ _: 2
1264
+ }, 1032, ["color"]), o(" " + v(y(C)[e.flag].label) + " (" + v(e.count) + ") ", 1)]),
1265
+ _: 2
1266
+ }, 1032, ["value", "title"]))), 128))]),
1267
+ _: 1
1268
+ }, 8, ["modelValue"])]),
1269
+ _.value.length === 0 ? (p(), n(A, {
1270
+ key: 0,
1271
+ type: "info",
1272
+ variant: "tonal"
1273
+ }, {
1274
+ default: x(() => c[1] || (c[1] = [o(" No study sessions recorded. Session records began with a framework release; there is no backfill, so earlier activity shows only in card history. ")])),
1275
+ _: 1
1276
+ })) : (p(), n(M, {
1277
+ key: 1,
1278
+ density: "compact",
1279
+ class: "sk-admin-table"
1280
+ }, {
1281
+ default: x(() => [c[3] || (c[3] = a("thead", null, [a("tr", null, [
1282
+ a("th", null, "Started"),
1283
+ a("th", null, "Status"),
1284
+ a("th", { class: "text-right" }, "Cards"),
1285
+ a("th", { class: "text-right" }, "Acc."),
1286
+ a("th", { class: "text-right" }, "Runs"),
1287
+ a("th", null, "Clock"),
1288
+ a("th", null, "Δ"),
1289
+ a("th", null, "Flags"),
1290
+ a("th")
1291
+ ])], -1)), a("tbody", null, [(p(!0), i(e, null, h(w.value, (t) => (p(), i("tr", {
1292
+ key: t.doc.sessionId,
1293
+ class: "sk-admin-row",
1294
+ onClick: (e) => f("open", t.doc.sessionId)
1295
+ }, [
1296
+ a("td", Nt, v(y(fmtTime)(t.doc.startTime)), 1),
1297
+ a("td", null, [s(j, {
1298
+ size: "x-small",
1299
+ variant: "flat",
1300
+ color: y(statusColor)(t.doc.status)
1301
+ }, {
1302
+ default: x(() => [o(v(t.doc.status), 1)]),
1303
+ _: 2
1304
+ }, 1032, ["color"])]),
1305
+ a("td", Pt, v(t.doc.tally?.cardsPresented ?? "—"), 1),
1306
+ a("td", Ft, [t.accuracy === null ? (p(), i("span", It, "—")) : (p(), i("span", {
1307
+ key: 0,
1308
+ class: u(y(accuracyClass)(t.accuracy))
1309
+ }, v(Math.round(t.accuracy * 100)) + "% ", 3))]),
1310
+ a("td", Lt, v(t.doc.runs?.length ?? 0), 1),
1311
+ a("td", Rt, v(clockText(t)), 1),
1312
+ a("td", zt, [(p(!0), i(e, null, h(deltas(t), (t, n) => (p(), i(e, { key: t.label }, [n > 0 ? (p(), i("span", Bt, " · ")) : r("", !0), a("span", { class: u(y(deltaClass)(t.delta)) }, v(y(k)(t.delta)) + "\xA0" + v(t.label), 3)], 64))), 128)), deltas(t).length === 0 ? (p(), i("span", Vt, "—")) : r("", !0)]),
1313
+ a("td", null, [(p(!0), i(e, null, h(t.flags, (e) => (p(), n(D, {
1314
+ key: e,
1315
+ size: "small",
1316
+ class: "mr-1",
1317
+ color: y(G)[e].color,
1318
+ title: `${y(C)[e].label}: ${y(C)[e].help}`
1319
+ }, {
1320
+ default: x(() => [o(v(y(G)[e].icon), 1)]),
1321
+ _: 2
1322
+ }, 1032, ["color", "title"]))), 128))]),
1323
+ a("td", Ht, [s(D, {
1324
+ size: "small",
1325
+ class: "text-disabled"
1326
+ }, {
1327
+ default: x(() => c[2] || (c[2] = [o("mdi-chevron-right")])),
1328
+ _: 1
1329
+ })])
1330
+ ], 8, Mt))), 128))])]),
1331
+ _: 1
1332
+ }))
1333
+ ]);
1334
+ };
1335
+ }
1336
+ }), [["__scopeId", "data-v-5c46cc58"]]), Ut = { class: "sk-pipeline" }, Wt = { class: "d-flex flex-wrap align-center ga-2 mb-3" }, Gt = { class: "text-caption" }, Kt = {
1337
+ key: 0,
1338
+ class: "text-caption text-medium-emphasis"
1339
+ }, qt = { class: "sk-stage" }, Jt = { class: "d-flex align-center mb-1" }, Yt = { class: "sk-stage-num" }, Xt = { class: "font-weight-medium" }, Zt = { class: "text-caption text-medium-emphasis mb-2" }, Qt = { class: "d-flex flex-wrap align-center ga-2" }, $t = { class: "font-weight-medium" }, en = { class: "text-caption" }, tn = {
1340
+ key: 0,
1341
+ class: "text-caption mt-1"
1342
+ }, nn = {
1343
+ key: 1,
1344
+ class: "text-caption mt-1"
1345
+ }, rn = {
1346
+ key: 1,
1347
+ class: "text-medium-emphasis"
1348
+ }, an = {
1349
+ key: 2,
1350
+ class: "mt-1"
1351
+ }, on = ["onClick"], sn = {
1352
+ key: 0,
1353
+ class: "sk-config"
1354
+ }, cn = {
1355
+ key: 0,
1356
+ class: "text-caption text-medium-emphasis"
1357
+ }, ln = { class: "d-flex flex-wrap align-center ga-2" }, un = { class: "font-weight-medium" }, dn = { class: "text-caption" }, fn = {
1358
+ key: 0,
1359
+ class: "text-caption mt-1"
1360
+ }, pn = {
1361
+ key: 1,
1362
+ class: "text-caption mt-1 text-medium-emphasis"
1363
+ }, mn = {
1364
+ key: 2,
1365
+ class: "mt-1"
1366
+ }, hn = { class: "sk-impact-bar" }, gn = ["title"], _n = { class: "text-caption" }, vn = {
1367
+ key: 1,
1368
+ class: "text-caption text-medium-emphasis"
1369
+ }, yn = {
1370
+ key: 3,
1371
+ class: "mt-1"
1372
+ }, bn = ["onClick"], Q = {
1373
+ key: 0,
1374
+ class: "sk-config"
1375
+ }, xn = { class: "sk-nav" }, Sn = { class: "d-flex flex-wrap align-center ga-2" }, Cn = { class: "text-caption mt-1" }, wn = {
1376
+ key: 0,
1377
+ class: "text-caption mt-1"
1378
+ }, Tn = { class: "sk-nav" }, En = { class: "d-flex flex-wrap align-center ga-2" }, Dn = { class: "text-caption mt-1" }, On = {
1379
+ key: 0,
1380
+ class: "text-caption mt-1"
1381
+ }, kn = {
1382
+ key: 0,
1383
+ class: "text-caption text-medium-emphasis"
1384
+ }, An = {
1385
+ key: 3,
1386
+ class: "text-caption"
1387
+ }, jn = {
1388
+ key: 0,
1389
+ class: "sk-stage-arrow"
1390
+ }, Mn = {
1391
+ key: 0,
1392
+ class: "text-caption text-medium-emphasis mt-3"
1393
+ }, $ = /* @__PURE__ */ K(/* @__PURE__ */ c({
1394
+ __name: "PipelineOverview",
1395
+ props: {
1396
+ strategies: {},
1397
+ courseId: {},
1398
+ stats: {},
1399
+ statsLabel: {}
1400
+ },
1401
+ setup(c) {
1402
+ let l = c, f = {
1403
+ "strategy-doc": {
1404
+ label: "strategy doc",
1405
+ color: "primary"
1406
+ },
1407
+ "assembler-default": {
1408
+ label: "added by the assembler",
1409
+ color: "secondary"
1410
+ },
1411
+ "default-pipeline": {
1412
+ label: "default pipeline",
1413
+ color: "warning"
1414
+ }
1415
+ }, regStat = (e) => l.stats?.regulators.find((t) => t.name === e);
1416
+ function regStatText(e) {
1417
+ return `In these runs: active (×>1) in ${e.runsActive}/${e.runs}, mean ×${e.meanMultiplier.toFixed(2)}, max ×${e.maxMultiplier.toFixed(2)}. Last: ${I(e.last)}.`;
1418
+ }
1419
+ let _ = {
1420
+ gate: "Gate: its penalties mean not ready yet; regulators will not lift what it penalized.",
1421
+ signal: "Signal: its penalties express preference."
1422
+ };
1423
+ function filterKind(e) {
1424
+ let t = e.kind ?? filterStat(e.name)?.kind;
1425
+ return t ? {
1426
+ kind: t,
1427
+ label: t,
1428
+ help: _[t]
1429
+ } : {
1430
+ kind: "gate",
1431
+ label: "gate (undeclared)",
1432
+ help: `${_.gate} Its class declares no kind, so it counts as a gate.`
1433
+ };
1434
+ }
1435
+ let b = t(() => ee(l.strategies, l.courseId)), S = t(() => new Map((l.stats?.generators ?? []).map((e) => [e.name, e]))), C = t(() => new Map((l.stats?.filters ?? []).map((e) => [e.name, e]))), genStat = (e) => S.value.get(e), filterStat = (e) => C.value.get(e), w = t(() => {
1436
+ let e = [...b.value.notes], t = l.stats;
1437
+ if (!t) return e;
1438
+ for (let t of b.value.filters) {
1439
+ let n = filterStat(t.name);
1440
+ n && n.boosted + n.penalized + n.removed === 0 && e.push(`${t.name} changed no candidate's score in ${n.runs} runs.`);
1441
+ }
1442
+ if (t.runsWithSelection > 0) for (let t of b.value.generators) {
1443
+ let n = genStat(t.name);
1444
+ n && n.runs > 0 && n.selected === 0 && e.push(`${t.name} proposed candidates in ${n.runs} runs, and none was selected.`);
1445
+ }
1446
+ return e;
1447
+ });
1448
+ function accuracyOf(e) {
1449
+ return e.firstAttempts > 0 ? e.firstAttemptsCorrect / e.firstAttempts : null;
1450
+ }
1451
+ function impactParts(e) {
1452
+ let t = e.boosted + e.penalized + e.passed + e.removed || 1;
1453
+ return [
1454
+ {
1455
+ key: "boosted",
1456
+ count: e.boosted,
1457
+ color: "success"
1458
+ },
1459
+ {
1460
+ key: "penalized",
1461
+ count: e.penalized,
1462
+ color: "warning"
1463
+ },
1464
+ {
1465
+ key: "passed",
1466
+ count: e.passed,
1467
+ color: "grey"
1468
+ },
1469
+ {
1470
+ key: "removed",
1471
+ count: e.removed,
1472
+ color: "error"
1473
+ }
1474
+ ].map((e) => ({
1475
+ ...e,
1476
+ share: e.count / t
1477
+ }));
1478
+ }
1479
+ function fmtNum(e) {
1480
+ return e >= 10 ? Math.round(e).toString() : e.toFixed(1);
1481
+ }
1482
+ function fmtPct(e) {
1483
+ return e === 0 ? "0%" : e < .01 ? "<1%" : `${Math.round(e * 100)}%`;
1484
+ }
1485
+ let T = m(/* @__PURE__ */ new Set());
1486
+ function toggleConfig(e) {
1487
+ let t = new Set(T.value);
1488
+ t.has(e) ? t.delete(e) : t.add(e), T.value = t;
1489
+ }
1490
+ function prettyConfig(e) {
1491
+ if (!e) return "";
1492
+ try {
1493
+ let t = JSON.parse(e), n = JSON.stringify(t, null, 2);
1494
+ return n === "{}" ? "" : n;
1495
+ } catch {
1496
+ return e;
1497
+ }
1498
+ }
1499
+ return (t, c) => {
1500
+ let l = g("v-chip"), m = g("v-alert"), _ = g("v-icon");
1501
+ return p(), i("div", Ut, [
1502
+ a("div", Wt, [
1503
+ s(l, {
1504
+ size: "small",
1505
+ variant: "tonal",
1506
+ color: b.value.kind === "default" ? "warning" : "primary"
1507
+ }, {
1508
+ default: x(() => [o(v(b.value.kind === "default" ? "default pipeline: the course has no strategy docs" : "assembled from strategy docs"), 1)]),
1509
+ _: 1
1510
+ }, 8, ["color"]),
1511
+ a("span", Gt, v(b.value.generators.length) + " generator(s) · " + v(b.value.filters.length) + " filter(s) ", 1),
1512
+ t.stats ? (p(), i("span", Kt, " · run stats" + v(t.statsLabel ? ` for ${t.statsLabel}` : "") + ": " + v(t.stats.runs) + " runs over " + v(t.stats.sessions) + " sessions, " + v(y(fmtTime)(t.stats.firstRunAt)) + " – " + v(y(fmtTime)(t.stats.lastRunAt)), 1)) : r("", !0)
1513
+ ]),
1514
+ (p(!0), i(e, null, h(b.value.skipped, (e) => (p(), n(m, {
1515
+ key: e.strategy._id,
1516
+ type: "warning",
1517
+ variant: "tonal",
1518
+ density: "compact",
1519
+ class: "mb-2"
1520
+ }, {
1521
+ default: x(() => [
1522
+ a("strong", null, v(e.strategy.name), 1),
1523
+ c[0] || (c[0] = o(" (")),
1524
+ a("code", null, v(e.strategy.implementingClass), 1),
1525
+ o(") is " + v(e.reason) + " in this app, so it would not run here. A course's own navigators only plan correctly in the app that registers them. ", 1)
1526
+ ]),
1527
+ _: 2
1528
+ }, 1024))), 128)),
1529
+ (p(!0), i(e, null, h(b.value.warnings, (e) => (p(), n(m, {
1530
+ key: e,
1531
+ type: "warning",
1532
+ variant: "tonal",
1533
+ density: "compact",
1534
+ class: "mb-2"
1535
+ }, {
1536
+ default: x(() => [o(v(e), 1)]),
1537
+ _: 2
1538
+ }, 1024))), 128)),
1539
+ (p(!0), i(e, null, h(w.value, (e) => (p(), n(m, {
1540
+ key: e,
1541
+ type: "info",
1542
+ variant: "tonal",
1543
+ density: "compact",
1544
+ class: "mb-2"
1545
+ }, {
1546
+ default: x(() => [o(v(e), 1)]),
1547
+ _: 2
1548
+ }, 1024))), 128)),
1549
+ (p(!0), i(e, null, h(y(W), (m, g) => (p(), i("div", { key: m.key }, [a("div", qt, [
1550
+ a("div", Jt, [a("span", Yt, v(g + 1), 1), a("span", Xt, v(m.title), 1)]),
1551
+ a("div", Zt, v(m.summary), 1),
1552
+ m.key === "generate" ? (p(!0), i(e, { key: 0 }, h(b.value.generators, (n) => (p(), i("div", {
1553
+ key: n.name,
1554
+ class: "sk-nav"
1555
+ }, [
1556
+ a("div", Qt, [
1557
+ a("span", $t, v(n.name), 1),
1558
+ a("code", en, v(n.implementingClass), 1),
1559
+ s(l, {
1560
+ size: "x-small",
1561
+ variant: "outlined",
1562
+ color: f[n.origin].color
1563
+ }, {
1564
+ default: x(() => [o(v(f[n.origin].label), 1)]),
1565
+ _: 2
1566
+ }, 1032, ["color"])
1567
+ ]),
1568
+ n.strategy?.description ? (p(), i("div", tn, v(n.strategy.description), 1)) : r("", !0),
1569
+ t.stats ? (p(), i("div", nn, [genStat(n.name) ? (p(), i(e, { key: 0 }, [o(v(fmtNum(genStat(n.name).meanCards)) + " candidates/run (" + v(fmtNum(genStat(n.name).meanNew)) + " new, " + v(fmtNum(genStat(n.name).meanReview)) + " review) · top score " + v(genStat(n.name).topScoreLast.toFixed(2)) + " last, " + v(genStat(n.name).topScoreMax.toFixed(2)) + " max ", 1), t.stats.runsWithSelection > 0 ? (p(), i(e, { key: 0 }, [
1570
+ c[1] || (c[1] = o(" · ")),
1571
+ a("strong", null, v(genStat(n.name).selected), 1),
1572
+ c[2] || (c[2] = o(" selected ")),
1573
+ genStat(n.name).firstAttempts > 0 ? (p(), i(e, { key: 0 }, [o(" · first attempts " + v(genStat(n.name).firstAttemptsCorrect) + "/" + v(genStat(n.name).firstAttempts) + " correct ", 1), a("span", { class: u(y(accuracyClass)(accuracyOf(genStat(n.name)))) }, " (" + v(Math.round(100 * accuracyOf(genStat(n.name)))) + "%) ", 3)], 64)) : r("", !0)
1574
+ ], 64)) : r("", !0)], 64)) : (p(), i("span", rn, "absent from these runs"))])) : r("", !0),
1575
+ prettyConfig(n.strategy?.serializedData) ? (p(), i("div", an, [a("a", {
1576
+ class: "sk-config-link text-caption",
1577
+ onClick: (e) => toggleConfig(`g:${n.name}`)
1578
+ }, v(T.value.has(`g:${n.name}`) ? "Hide" : "Show") + " config ", 9, on), T.value.has(`g:${n.name}`) ? (p(), i("pre", sn, v(prettyConfig(n.strategy?.serializedData)), 1)) : r("", !0)])) : r("", !0)
1579
+ ]))), 128)) : m.key === "filter" ? (p(), i(e, { key: 1 }, [b.value.filters.length === 0 ? (p(), i("div", cn, "No filters.")) : r("", !0), (p(!0), i(e, null, h(b.value.filters, (m) => (p(), i("div", {
1580
+ key: m.name,
1581
+ class: "sk-nav"
1582
+ }, [
1583
+ a("div", ln, [
1584
+ a("span", un, v(m.name), 1),
1585
+ a("code", dn, v(m.implementingClass), 1),
1586
+ s(l, {
1587
+ size: "x-small",
1588
+ variant: "outlined",
1589
+ color: f[m.origin].color
1590
+ }, {
1591
+ default: x(() => [o(v(f[m.origin].label), 1)]),
1592
+ _: 2
1593
+ }, 1032, ["color"]),
1594
+ m.strategy?.learnable ? (p(), n(l, {
1595
+ key: 0,
1596
+ size: "x-small",
1597
+ variant: "outlined"
1598
+ }, {
1599
+ default: x(() => c[3] || (c[3] = [o("learnable weight")])),
1600
+ _: 1
1601
+ })) : r("", !0),
1602
+ s(l, {
1603
+ size: "x-small",
1604
+ variant: "tonal",
1605
+ color: filterKind(m).kind === "gate" ? "deep-orange" : "blue-grey",
1606
+ title: filterKind(m).help
1607
+ }, {
1608
+ default: x(() => [o(v(filterKind(m).label), 1)]),
1609
+ _: 2
1610
+ }, 1032, ["color", "title"])
1611
+ ]),
1612
+ m.strategy?.description ? (p(), i("div", fn, v(m.strategy.description), 1)) : m.strategy ? r("", !0) : (p(), i("div", pn, " Built in code; no strategy document. ")),
1613
+ t.stats ? (p(), i("div", mn, [filterStat(m.name) ? (p(), i(e, { key: 0 }, [a("div", hn, [(p(!0), i(e, null, h(impactParts(filterStat(m.name)), (e) => (p(), i("div", {
1614
+ key: e.key,
1615
+ class: u(`bg-${e.color}`),
1616
+ style: d({ width: `${e.share * 100}%` }),
1617
+ title: `${e.key}: ${e.count}`
1618
+ }, null, 14, gn))), 128))]), a("div", _n, [o(" over " + v(filterStat(m.name).runs) + " runs: ", 1), (p(!0), i(e, null, h(impactParts(filterStat(m.name)), (e) => (p(), i("span", {
1619
+ key: e.key,
1620
+ class: "mr-2"
1621
+ }, [a("span", { class: u(`text-${e.color}`) }, "●", 2), o(" " + v(e.key) + " " + v(fmtPct(e.share)), 1)]))), 128))])], 64)) : (p(), i("span", vn, "absent from these runs"))])) : r("", !0),
1622
+ prettyConfig(m.strategy?.serializedData) ? (p(), i("div", yn, [a("a", {
1623
+ class: "sk-config-link text-caption",
1624
+ onClick: (e) => toggleConfig(`f:${m.name}`)
1625
+ }, v(T.value.has(`f:${m.name}`) ? "Hide" : "Show") + " config ", 9, bn), T.value.has(`f:${m.name}`) ? (p(), i("pre", Q, v(prettyConfig(m.strategy?.serializedData)), 1)) : r("", !0)])) : r("", !0)
1626
+ ]))), 128))], 64)) : m.key === "regulate" ? (p(), i(e, { key: 2 }, [
1627
+ a("div", xn, [
1628
+ a("div", Sn, [c[5] || (c[5] = a("span", { class: "font-weight-medium" }, "Review mass", -1)), s(l, {
1629
+ size: "x-small",
1630
+ variant: "outlined"
1631
+ }, {
1632
+ default: x(() => c[4] || (c[4] = [o("on review cards")])),
1633
+ _: 1
1634
+ })]),
1635
+ a("div", Cn, " Each due review weighs its SRS urgency (0–1) times the filters' net effect on it. Above " + v(b.value.regulators.reviewMass.healthyMass) + " card-equivalents, every review is multiplied by ×" + v(b.value.regulators.reviewMass.rate) + " per " + v(b.value.regulators.reviewMass.healthyMass) + " in excess (uncapped). Reviews without an urgency weigh their score against " + v(b.value.regulators.reviewMass.referenceScore) + ". ", 1),
1636
+ regStat("review-mass") ? (p(), i("div", wn, v(regStatText(regStat("review-mass"))), 1)) : r("", !0)
1637
+ ]),
1638
+ a("div", Tn, [
1639
+ a("div", En, [c[7] || (c[7] = a("span", { class: "font-weight-medium" }, "Intake", -1)), s(l, {
1640
+ size: "x-small",
1641
+ variant: "outlined"
1642
+ }, {
1643
+ default: x(() => c[6] || (c[6] = [o("on new cards no gate penalized")])),
1644
+ _: 1
1645
+ })]),
1646
+ a("div", Dn, " ×" + v(b.value.regulators.intake.rate) + " per " + v(b.value.regulators.intake.scaleHours) + " hours since a new card was last presented (uncapped). The clock stops while no eligible new card exists. ", 1),
1647
+ regStat("intake") ? (p(), i("div", On, v(regStatText(regStat("intake"))), 1)) : r("", !0)
1648
+ ]),
1649
+ t.stats && t.stats.regulators.length === 0 ? (p(), i("div", kn, " None of these runs recorded regulator readings (recorded from 0.2.29). ")) : r("", !0)
1650
+ ], 64)) : m.key === "select" && t.stats && t.stats.runsWithSelection > 0 ? (p(), i("div", An, [
1651
+ o(v(t.stats.selectedUnattributed) + " selected card(s) had no generator: forced in by a hint's ", 1),
1652
+ c[8] || (c[8] = a("code", null, "requireCards", -1)),
1653
+ c[9] || (c[9] = o(". "))
1654
+ ])) : r("", !0)
1655
+ ]), g < y(W).length - 1 ? (p(), i("div", jn, [s(_, { size: "small" }, {
1656
+ default: x(() => c[10] || (c[10] = [o("mdi-arrow-down")])),
1657
+ _: 1
1658
+ })])) : r("", !0)]))), 128)),
1659
+ t.stats && t.stats.runsWithSelection === 0 ? (p(), i("div", Mn, " None of these runs recorded which cards they selected (recorded from 0.2.28), so selection and first-attempt counts are not shown. ")) : r("", !0)
1660
+ ]);
1661
+ };
1662
+ }
1663
+ }), [["__scopeId", "data-v-24b6038f"]]), Nn = /* @__PURE__ */ c({
1664
+ __name: "LearnerDiagnostics",
1665
+ props: {
1666
+ dataset: {},
1667
+ interpreters: {}
1668
+ },
1669
+ setup(a) {
1670
+ let c = a, l = m("sessions"), u = m(null), d = m(void 0), f = t(() => P(c.dataset));
1671
+ function openCard(e) {
1672
+ d.value = void 0, l.value = "cards", setTimeout(() => d.value = e);
1673
+ }
1674
+ return b(() => c.dataset, () => {
1675
+ l.value = "sessions", u.value = null, d.value = void 0;
1676
+ }), (t, a) => {
1677
+ let c = g("v-tab"), m = g("v-tabs"), h = g("v-btn");
1678
+ return p(), i("div", null, [s(m, {
1679
+ modelValue: l.value,
1680
+ "onUpdate:modelValue": a[0] || (a[0] = (e) => l.value = e),
1681
+ density: "compact",
1682
+ class: "mb-4"
1683
+ }, {
1684
+ default: x(() => [
1685
+ s(c, { value: "sessions" }, {
1686
+ default: x(() => [o("Sessions (" + v(t.dataset.sessions.length) + ")", 1)]),
1687
+ _: 1
1688
+ }),
1689
+ s(c, { value: "cards" }, {
1690
+ default: x(() => [o("Cards (" + v(t.dataset.cardHistories.length) + ")", 1)]),
1691
+ _: 1
1692
+ }),
1693
+ t.dataset.strategies ? (p(), n(c, {
1694
+ key: 0,
1695
+ value: "pipeline"
1696
+ }, {
1697
+ default: x(() => a[3] || (a[3] = [o("Pipeline")])),
1698
+ _: 1
1699
+ })) : r("", !0)
1700
+ ]),
1701
+ _: 1
1702
+ }, 8, ["modelValue"]), l.value === "sessions" ? (p(), i(e, { key: 0 }, [u.value ? (p(), i(e, { key: 0 }, [s(h, {
1703
+ variant: "text",
1704
+ size: "small",
1705
+ "prepend-icon": "mdi-arrow-left",
1706
+ class: "mb-2",
1707
+ onClick: a[1] || (a[1] = (e) => u.value = null)
1708
+ }, {
1709
+ default: x(() => a[4] || (a[4] = [o(" All sessions ")])),
1710
+ _: 1
1711
+ }), s(X, {
1712
+ dataset: t.dataset,
1713
+ "session-id": u.value,
1714
+ interpreters: t.interpreters,
1715
+ onOpenCard: openCard
1716
+ }, null, 8, [
1717
+ "dataset",
1718
+ "session-id",
1719
+ "interpreters"
1720
+ ])], 64)) : (p(), n(Z, {
1721
+ key: 1,
1722
+ dataset: t.dataset,
1723
+ interpreters: t.interpreters,
1724
+ onOpen: a[2] || (a[2] = (e) => u.value = e)
1725
+ }, null, 8, ["dataset", "interpreters"]))], 64)) : l.value === "cards" ? (p(), n(J, {
1726
+ key: 1,
1727
+ dataset: t.dataset,
1728
+ interpreters: t.interpreters,
1729
+ focus: d.value
1730
+ }, null, 8, [
1731
+ "dataset",
1732
+ "interpreters",
1733
+ "focus"
1734
+ ])) : l.value === "pipeline" && t.dataset.strategies ? (p(), n($, {
1735
+ key: 2,
1736
+ strategies: t.dataset.strategies,
1737
+ "course-id": t.dataset.courseId,
1738
+ stats: f.value,
1739
+ "stats-label": t.dataset.username
1740
+ }, null, 8, [
1741
+ "strategies",
1742
+ "course-id",
1743
+ "stats",
1744
+ "stats-label"
1745
+ ])) : r("", !0)]);
1746
+ };
1747
+ }
1748
+ });
1749
+ //#endregion
1750
+ export { q as CardDossierDetail, J as LearnerCardHistory, ie as LearnerDatasetSource, Nn as LearnerDiagnostics, X as LearnerSessionDetail, Z as LearnerSessionList, $ as PipelineOverview, Y as RunDetail, downloadLearnerDump, useLearnerDataset };
1751
+
1752
+ //# sourceMappingURL=common-ui-admin.es.js.map