@vue-skuilder/common 0.2.24 → 0.2.26
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.
- package/dist/elo.d.ts +31 -1
- package/dist/elo.d.ts.map +1 -1
- package/dist/elo.js +22 -2
- package/dist/elo.js.map +1 -1
- package/dist/elo.mjs +20 -1
- package/package.json +2 -2
- package/src/elo.ts +51 -2
package/dist/elo.d.ts
CHANGED
|
@@ -16,10 +16,40 @@ export type CourseElo = {
|
|
|
16
16
|
[eloID: string]: EloRank;
|
|
17
17
|
};
|
|
18
18
|
};
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* One presentation of a card that exercised a tag, as seen by the learner.
|
|
21
|
+
* Recorded once per presentation (first attempt only — retries within the
|
|
22
|
+
* same presentation are not evidence). The ring buffer of these on a tag's
|
|
23
|
+
* EloRank is the mastery *signal*; how it is read (window, weighting,
|
|
24
|
+
* thresholds) is a consumer policy, deliberately not encoded here.
|
|
25
|
+
*/
|
|
26
|
+
export type TagPresentation = {
|
|
27
|
+
/** ISO timestamp of the presentation */
|
|
28
|
+
at: string;
|
|
29
|
+
/** First-attempt correct */
|
|
30
|
+
ok: boolean;
|
|
31
|
+
/** The 0–1 tag performance the record carried */
|
|
32
|
+
perf: number;
|
|
33
|
+
/** cardID, so a reader can tell "one card, twelve times" from breadth */
|
|
34
|
+
card: string;
|
|
35
|
+
};
|
|
36
|
+
/** Ring-buffer capacity for `EloRank.recent`. Oldest entry is dropped on push. */
|
|
37
|
+
export declare const TAG_RECENT_MAX = 12;
|
|
38
|
+
export type EloRank = {
|
|
20
39
|
score: number;
|
|
21
40
|
count: number;
|
|
41
|
+
/**
|
|
42
|
+
* Most recent presentations, oldest first, length ≤ TAG_RECENT_MAX.
|
|
43
|
+
* Optional: absent on docs written before the buffer existed, and never
|
|
44
|
+
* present on card-side ranks. Preserved (not stripped) by the adjusters.
|
|
45
|
+
*/
|
|
46
|
+
recent?: TagPresentation[];
|
|
22
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* Append a presentation to a rank's ring buffer, returning a new rank.
|
|
50
|
+
* Pure: neither `rank` nor its `recent` array is mutated.
|
|
51
|
+
*/
|
|
52
|
+
export declare function pushTagPresentation(rank: EloRank, entry: TagPresentation, max?: number): EloRank;
|
|
23
53
|
type Eloish = number | EloRank | CourseElo;
|
|
24
54
|
export declare function blankCourseElo(): CourseElo;
|
|
25
55
|
export declare function EloToNumber(elo: Eloish): number;
|
package/dist/elo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elo.d.ts","sourceRoot":"","sources":["../src/elo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,qBAAa,SAAS;IACD,CAAC,EAAE,MAAM;gBAAT,CAAC,GAAE,MAAW;IAEjC,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAG3B,UAAU,IAAI,MAAM;IAIpB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM;IAGzC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;CAGxE;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE;QACJ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;IACF,IAAI,EAAE;QACJ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;CACH,CAAC;AAEF,KAAK,OAAO,GAAG;
|
|
1
|
+
{"version":3,"file":"elo.d.ts","sourceRoot":"","sources":["../src/elo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,qBAAa,SAAS;IACD,CAAC,EAAE,MAAM;gBAAT,CAAC,GAAE,MAAW;IAEjC,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAG3B,UAAU,IAAI,MAAM;IAIpB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM;IAGzC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;CAGxE;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE;QACJ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;IACF,IAAI,EAAE;QACJ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;CACH,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,4BAA4B;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,eAAe,EACtB,GAAG,GAAE,MAAuB,GAC3B,OAAO,CAKT;AAED,KAAK,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3C,wBAAgB,cAAc,IAAI,SAAS,CAS1C;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAS/C;AACD,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CASpD;AACD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CA+B9D;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,SAAS,CAMtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;IACR,UAAU,EAAE,OAAO,CAAC;CACrB,GACA;IACD,OAAO,EAAE,SAAS,CAAC;IACnB,OAAO,EAAE,SAAS,CAAC;CACpB,CA+BA;AAoDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,iBAAiB,EAAE,iBAAiB,GACnC;IACD,OAAO,EAAE,SAAS,CAAC;IACnB,OAAO,EAAE,SAAS,CAAC;CACpB,CAkEA"}
|
package/dist/elo.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.EloRanker = void 0;
|
|
3
|
+
exports.TAG_RECENT_MAX = exports.EloRanker = void 0;
|
|
4
|
+
exports.pushTagPresentation = pushTagPresentation;
|
|
4
5
|
exports.blankCourseElo = blankCourseElo;
|
|
5
6
|
exports.EloToNumber = EloToNumber;
|
|
6
7
|
exports.toElo = toElo;
|
|
@@ -27,6 +28,18 @@ class EloRanker {
|
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
exports.EloRanker = EloRanker;
|
|
31
|
+
/** Ring-buffer capacity for `EloRank.recent`. Oldest entry is dropped on push. */
|
|
32
|
+
exports.TAG_RECENT_MAX = 12;
|
|
33
|
+
/**
|
|
34
|
+
* Append a presentation to a rank's ring buffer, returning a new rank.
|
|
35
|
+
* Pure: neither `rank` nor its `recent` array is mutated.
|
|
36
|
+
*/
|
|
37
|
+
function pushTagPresentation(rank, entry, max = exports.TAG_RECENT_MAX) {
|
|
38
|
+
const prior = rank.recent ?? [];
|
|
39
|
+
const next = [...prior, entry];
|
|
40
|
+
const trimmed = next.length > max ? next.slice(next.length - max) : next;
|
|
41
|
+
return { ...rank, recent: trimmed };
|
|
42
|
+
}
|
|
30
43
|
function blankCourseElo() {
|
|
31
44
|
return {
|
|
32
45
|
global: {
|
|
@@ -160,12 +173,15 @@ function adjustScores(userElo, cardElo, userScore) {
|
|
|
160
173
|
const exp = userRanker.getExpected(userElo.score, cardElo.score);
|
|
161
174
|
const updatedUserElo = userRanker.updateRating(exp, userScore, userElo.score);
|
|
162
175
|
const updatedCardElo = cardRanker.updateRating(1 - exp, 1 - userScore, cardElo.score);
|
|
176
|
+
// Spread first so sibling fields (e.g. `recent`) survive the adjustment.
|
|
163
177
|
return {
|
|
164
178
|
userElo: {
|
|
179
|
+
...userElo,
|
|
165
180
|
score: updatedUserElo,
|
|
166
181
|
count: userElo.count + 1,
|
|
167
182
|
},
|
|
168
183
|
cardElo: {
|
|
184
|
+
...cardElo,
|
|
169
185
|
score: updatedCardElo,
|
|
170
186
|
count: cardElo.count + 1,
|
|
171
187
|
},
|
|
@@ -238,7 +254,11 @@ function adjustCourseScoresPerTag(aElo, bElo, taggedPerformance) {
|
|
|
238
254
|
const existingUserTagElo = userElo.tags[key];
|
|
239
255
|
const userTagElo = (existingUserTagElo && existingUserTagElo.score !== -1)
|
|
240
256
|
? existingUserTagElo
|
|
241
|
-
: {
|
|
257
|
+
: {
|
|
258
|
+
...(existingUserTagElo ?? {}),
|
|
259
|
+
count: existingUserTagElo?.count ?? 0,
|
|
260
|
+
score: userElo.global.score,
|
|
261
|
+
};
|
|
242
262
|
// Initialize tag ELO on card if missing (use global score as baseline)
|
|
243
263
|
const cardTagElo = cardElo.tags[key] ?? {
|
|
244
264
|
count: 0,
|
package/dist/elo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elo.js","sourceRoot":"","sources":["../src/elo.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,SAAS;IACD;IAAnB,YAAmB,IAAY,EAAE;QAAd,MAAC,GAAD,CAAC,CAAa;IAAG,CAAC;IAErC,UAAU,CAAC,CAAS;QAClB,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACb,CAAC;IACD,UAAU;QACR,OAAO,IAAI,CAAC,CAAC,CAAC;IAChB,CAAC;IAED,WAAW,CAAC,CAAS,EAAE,CAAS;QAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,YAAY,CAAC,QAAgB,EAAE,MAAc,EAAE,OAAe;QAC5D,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;IAC5D,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"elo.js","sourceRoot":"","sources":["../src/elo.ts"],"names":[],"mappings":"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"}
|
package/dist/elo.mjs
CHANGED
|
@@ -16,6 +16,18 @@ export class EloRanker {
|
|
|
16
16
|
return Math.round(current + this.k * (actual - expected));
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
/** Ring-buffer capacity for `EloRank.recent`. Oldest entry is dropped on push. */
|
|
20
|
+
export const TAG_RECENT_MAX = 12;
|
|
21
|
+
/**
|
|
22
|
+
* Append a presentation to a rank's ring buffer, returning a new rank.
|
|
23
|
+
* Pure: neither `rank` nor its `recent` array is mutated.
|
|
24
|
+
*/
|
|
25
|
+
export function pushTagPresentation(rank, entry, max = TAG_RECENT_MAX) {
|
|
26
|
+
const prior = rank.recent ?? [];
|
|
27
|
+
const next = [...prior, entry];
|
|
28
|
+
const trimmed = next.length > max ? next.slice(next.length - max) : next;
|
|
29
|
+
return { ...rank, recent: trimmed };
|
|
30
|
+
}
|
|
19
31
|
export function blankCourseElo() {
|
|
20
32
|
return {
|
|
21
33
|
global: {
|
|
@@ -149,12 +161,15 @@ function adjustScores(userElo, cardElo, userScore) {
|
|
|
149
161
|
const exp = userRanker.getExpected(userElo.score, cardElo.score);
|
|
150
162
|
const updatedUserElo = userRanker.updateRating(exp, userScore, userElo.score);
|
|
151
163
|
const updatedCardElo = cardRanker.updateRating(1 - exp, 1 - userScore, cardElo.score);
|
|
164
|
+
// Spread first so sibling fields (e.g. `recent`) survive the adjustment.
|
|
152
165
|
return {
|
|
153
166
|
userElo: {
|
|
167
|
+
...userElo,
|
|
154
168
|
score: updatedUserElo,
|
|
155
169
|
count: userElo.count + 1,
|
|
156
170
|
},
|
|
157
171
|
cardElo: {
|
|
172
|
+
...cardElo,
|
|
158
173
|
score: updatedCardElo,
|
|
159
174
|
count: cardElo.count + 1,
|
|
160
175
|
},
|
|
@@ -227,7 +242,11 @@ export function adjustCourseScoresPerTag(aElo, bElo, taggedPerformance) {
|
|
|
227
242
|
const existingUserTagElo = userElo.tags[key];
|
|
228
243
|
const userTagElo = (existingUserTagElo && existingUserTagElo.score !== -1)
|
|
229
244
|
? existingUserTagElo
|
|
230
|
-
: {
|
|
245
|
+
: {
|
|
246
|
+
...(existingUserTagElo ?? {}),
|
|
247
|
+
count: existingUserTagElo?.count ?? 0,
|
|
248
|
+
score: userElo.global.score,
|
|
249
|
+
};
|
|
231
250
|
// Initialize tag ELO on card if missing (use global score as baseline)
|
|
232
251
|
const cardTagElo = cardElo.tags[key] ?? {
|
|
233
252
|
count: 0,
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.26",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"module": "dist/index.mjs",
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
"zod": "^3.23.8",
|
|
37
37
|
"zod-to-json-schema": "^3.23.5"
|
|
38
38
|
},
|
|
39
|
-
"stableVersion": "0.2.
|
|
39
|
+
"stableVersion": "0.2.26"
|
|
40
40
|
}
|
package/src/elo.ts
CHANGED
|
@@ -28,11 +28,53 @@ export type CourseElo = {
|
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* One presentation of a card that exercised a tag, as seen by the learner.
|
|
33
|
+
* Recorded once per presentation (first attempt only — retries within the
|
|
34
|
+
* same presentation are not evidence). The ring buffer of these on a tag's
|
|
35
|
+
* EloRank is the mastery *signal*; how it is read (window, weighting,
|
|
36
|
+
* thresholds) is a consumer policy, deliberately not encoded here.
|
|
37
|
+
*/
|
|
38
|
+
export type TagPresentation = {
|
|
39
|
+
/** ISO timestamp of the presentation */
|
|
40
|
+
at: string;
|
|
41
|
+
/** First-attempt correct */
|
|
42
|
+
ok: boolean;
|
|
43
|
+
/** The 0–1 tag performance the record carried */
|
|
44
|
+
perf: number;
|
|
45
|
+
/** cardID, so a reader can tell "one card, twelve times" from breadth */
|
|
46
|
+
card: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Ring-buffer capacity for `EloRank.recent`. Oldest entry is dropped on push. */
|
|
50
|
+
export const TAG_RECENT_MAX = 12;
|
|
51
|
+
|
|
52
|
+
export type EloRank = {
|
|
32
53
|
score: number;
|
|
33
54
|
count: number;
|
|
55
|
+
/**
|
|
56
|
+
* Most recent presentations, oldest first, length ≤ TAG_RECENT_MAX.
|
|
57
|
+
* Optional: absent on docs written before the buffer existed, and never
|
|
58
|
+
* present on card-side ranks. Preserved (not stripped) by the adjusters.
|
|
59
|
+
*/
|
|
60
|
+
recent?: TagPresentation[];
|
|
34
61
|
};
|
|
35
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Append a presentation to a rank's ring buffer, returning a new rank.
|
|
65
|
+
* Pure: neither `rank` nor its `recent` array is mutated.
|
|
66
|
+
*/
|
|
67
|
+
export function pushTagPresentation(
|
|
68
|
+
rank: EloRank,
|
|
69
|
+
entry: TagPresentation,
|
|
70
|
+
max: number = TAG_RECENT_MAX
|
|
71
|
+
): EloRank {
|
|
72
|
+
const prior = rank.recent ?? [];
|
|
73
|
+
const next = [...prior, entry];
|
|
74
|
+
const trimmed = next.length > max ? next.slice(next.length - max) : next;
|
|
75
|
+
return { ...rank, recent: trimmed };
|
|
76
|
+
}
|
|
77
|
+
|
|
36
78
|
type Eloish = number | EloRank | CourseElo;
|
|
37
79
|
|
|
38
80
|
export function blankCourseElo(): CourseElo {
|
|
@@ -194,12 +236,15 @@ function adjustScores(
|
|
|
194
236
|
const updatedUserElo = userRanker.updateRating(exp, userScore, userElo.score);
|
|
195
237
|
const updatedCardElo = cardRanker.updateRating(1 - exp, 1 - userScore, cardElo.score);
|
|
196
238
|
|
|
239
|
+
// Spread first so sibling fields (e.g. `recent`) survive the adjustment.
|
|
197
240
|
return {
|
|
198
241
|
userElo: {
|
|
242
|
+
...userElo,
|
|
199
243
|
score: updatedUserElo,
|
|
200
244
|
count: userElo.count + 1,
|
|
201
245
|
},
|
|
202
246
|
cardElo: {
|
|
247
|
+
...cardElo,
|
|
203
248
|
score: updatedCardElo,
|
|
204
249
|
count: cardElo.count + 1,
|
|
205
250
|
},
|
|
@@ -285,7 +330,11 @@ export function adjustCourseScoresPerTag(
|
|
|
285
330
|
const existingUserTagElo = userElo.tags[key];
|
|
286
331
|
const userTagElo: EloRank = (existingUserTagElo && existingUserTagElo.score !== -1)
|
|
287
332
|
? existingUserTagElo
|
|
288
|
-
: {
|
|
333
|
+
: {
|
|
334
|
+
...(existingUserTagElo ?? {}),
|
|
335
|
+
count: existingUserTagElo?.count ?? 0,
|
|
336
|
+
score: userElo.global.score,
|
|
337
|
+
};
|
|
289
338
|
|
|
290
339
|
// Initialize tag ELO on card if missing (use global score as baseline)
|
|
291
340
|
const cardTagElo: EloRank = cardElo.tags[key] ?? {
|