@vue-skuilder/common 0.1.24 → 0.1.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/course-data.d.ts +55 -4
- package/dist/course-data.d.ts.map +1 -1
- package/dist/course-data.js +7 -0
- package/dist/course-data.js.map +1 -1
- package/dist/course-data.mjs +6 -0
- package/dist/elo.d.ts +38 -0
- package/dist/elo.d.ts.map +1 -1
- package/dist/elo.js +83 -0
- package/dist/elo.js.map +1 -1
- package/dist/elo.mjs +82 -0
- package/package.json +2 -2
- package/src/course-data.ts +60 -5
- package/src/elo.ts +101 -0
package/dist/course-data.d.ts
CHANGED
|
@@ -10,8 +10,59 @@ export interface Evaluation {
|
|
|
10
10
|
isCorrect: boolean;
|
|
11
11
|
performance: Performance;
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Performance can be a simple number (0-1) for overall score,
|
|
15
|
+
* or a structured object with per-tag granularity.
|
|
16
|
+
*/
|
|
17
|
+
export type Performance = number | TaggedPerformance;
|
|
18
|
+
/**
|
|
19
|
+
* Structured performance with per-tag scoring.
|
|
20
|
+
*
|
|
21
|
+
* Questions that exercise multiple skills (e.g., spelling with multiple GPCs)
|
|
22
|
+
* can provide individual scores per tag for granular ELO updates.
|
|
23
|
+
*
|
|
24
|
+
* Tags can have scores (for exercise tags) or `null` (for count-only exposure tags).
|
|
25
|
+
* Count-only tags increment their count but maintain a sentinel score of -1,
|
|
26
|
+
* making them easily identifiable and preventing them from polluting real ELO data.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // Spelling "cat" as "kat" - got 'a' and 't' right, but 'c' wrong
|
|
30
|
+
* {
|
|
31
|
+
* _global: 0.67, // 2/3 correct, used for SRS and global ELO
|
|
32
|
+
* 'gpc:exercise:c-K': 0, // incorrect
|
|
33
|
+
* 'gpc:exercise:a-AE': 1, // correct
|
|
34
|
+
* 'gpc:exercise:t-T': 1, // correct
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // WhoSaidThat card exercising 'sh' while exposing distractors
|
|
39
|
+
* {
|
|
40
|
+
* _global: 1.0,
|
|
41
|
+
* 'gpc:exercise:sh-SH': 1.0, // exercised and correct
|
|
42
|
+
* 'gpc:expose:s-S': null, // count-only exposure (no score)
|
|
43
|
+
* 'gpc:expose:ch-CH': null, // count-only exposure (no score)
|
|
44
|
+
* }
|
|
45
|
+
*/
|
|
46
|
+
export interface TaggedPerformance {
|
|
47
|
+
/**
|
|
48
|
+
* Overall score for SRS scheduling and global ELO updates.
|
|
49
|
+
* Required when using structured performance.
|
|
50
|
+
* Range: [0, 1]
|
|
51
|
+
*/
|
|
52
|
+
_global: number;
|
|
53
|
+
/**
|
|
54
|
+
* Per-tag scores or count-only markers.
|
|
55
|
+
*
|
|
56
|
+
* - **Number (0-1)**: Tag is exercised; score updates via ELO formula
|
|
57
|
+
* - **null**: Count-only tag (e.g., exposure); increments count, score stays -1 (sentinel)
|
|
58
|
+
*
|
|
59
|
+
* Tags not present on the card will be created dynamically.
|
|
60
|
+
* Count-only tags (null) do not update card ELO.
|
|
61
|
+
*/
|
|
62
|
+
[tag: string]: number | null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Type guard to check if performance is structured (TaggedPerformance).
|
|
66
|
+
*/
|
|
67
|
+
export declare function isTaggedPerformance(p: Performance): p is TaggedPerformance;
|
|
17
68
|
//# sourceMappingURL=course-data.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"course-data.d.ts","sourceRoot":"","sources":["../src/course-data.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAW,MAAM,SAAS,CAAC;AAEnD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAKtD,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,SAAS,EAGhB,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,CAAC,EAAE;IAAE,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,cAAc,CAAA;CAAE,GACrD,eAAe,CAsFjB;AAED;;GAEG;AAEH,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,
|
|
1
|
+
{"version":3,"file":"course-data.d.ts","sourceRoot":"","sources":["../src/course-data.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAW,MAAM,SAAS,CAAC;AAEnD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAKtD,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,SAAS,EAGhB,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,CAAC,EAAE;IAAE,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,cAAc,CAAA;CAAE,GACrD,eAAe,CAsFjB;AAED;;GAEG;AAEH,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,iBAAiB,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;;;;;OAQG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,WAAW,GAAG,CAAC,IAAI,iBAAiB,CAE1E"}
|
package/dist/course-data.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.prepareNote55 = prepareNote55;
|
|
4
|
+
exports.isTaggedPerformance = isTaggedPerformance;
|
|
4
5
|
const db_js_1 = require("./db.js");
|
|
5
6
|
const namespacer_js_1 = require("./namespacer.js");
|
|
6
7
|
const FieldType_js_1 = require("./enums/FieldType.js");
|
|
@@ -84,4 +85,10 @@ data, author, _tags, uploads) {
|
|
|
84
85
|
});
|
|
85
86
|
return payload;
|
|
86
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Type guard to check if performance is structured (TaggedPerformance).
|
|
90
|
+
*/
|
|
91
|
+
function isTaggedPerformance(p) {
|
|
92
|
+
return typeof p === 'object' && p !== null && '_global' in p;
|
|
93
|
+
}
|
|
87
94
|
//# sourceMappingURL=course-data.js.map
|
package/dist/course-data.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"course-data.js","sourceRoot":"","sources":["../src/course-data.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"course-data.js","sourceRoot":"","sources":["../src/course-data.ts"],"names":[],"mappings":"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"}
|
package/dist/course-data.mjs
CHANGED
|
@@ -81,4 +81,10 @@ data, author, _tags, uploads) {
|
|
|
81
81
|
});
|
|
82
82
|
return payload;
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Type guard to check if performance is structured (TaggedPerformance).
|
|
86
|
+
*/
|
|
87
|
+
export function isTaggedPerformance(p) {
|
|
88
|
+
return typeof p === 'object' && p !== null && '_global' in p;
|
|
89
|
+
}
|
|
84
90
|
//# sourceMappingURL=course-data.js.map
|
package/dist/elo.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TaggedPerformance } from './course-data.js';
|
|
1
2
|
export declare class EloRanker {
|
|
2
3
|
k: number;
|
|
3
4
|
constructor(k?: number);
|
|
@@ -40,5 +41,42 @@ export declare function adjustCourseScores(aElo: Eloish, bElo: Eloish, userScore
|
|
|
40
41
|
userElo: CourseElo;
|
|
41
42
|
cardElo: CourseElo;
|
|
42
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Adjusts ELO scores with per-tag granularity.
|
|
46
|
+
*
|
|
47
|
+
* Unlike adjustCourseScores which applies the same score to all tags,
|
|
48
|
+
* this function allows different scores per tag for granular skill tracking.
|
|
49
|
+
*
|
|
50
|
+
* Tags can be scored (number 0-1) or count-only (null). Count-only tags are
|
|
51
|
+
* useful for exposure tracking (e.g., gpc:expose:*) where we only care about
|
|
52
|
+
* "how many times has the user seen this?" without measuring performance.
|
|
53
|
+
*
|
|
54
|
+
* @param aElo - User's current ELO (will be converted to CourseElo)
|
|
55
|
+
* @param bElo - Card's current ELO (will be converted to CourseElo)
|
|
56
|
+
* @param taggedPerformance - Object with _global score and per-tag scores/null
|
|
57
|
+
* @returns Updated user and card ELOs
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* // Spelling "cat" as "kat" - got 'a' and 't' right, but 'c' wrong
|
|
61
|
+
* adjustCourseScoresPerTag(userElo, cardElo, {
|
|
62
|
+
* _global: 0.67,
|
|
63
|
+
* 'gpc:exercise:c-K': 0,
|
|
64
|
+
* 'gpc:exercise:a-AE': 1,
|
|
65
|
+
* 'gpc:exercise:t-T': 1,
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* // WhoSaidThat - exercise target, expose distractors (count-only)
|
|
70
|
+
* adjustCourseScoresPerTag(userElo, cardElo, {
|
|
71
|
+
* _global: 1.0,
|
|
72
|
+
* 'gpc:exercise:sh-SH': 1.0,
|
|
73
|
+
* 'gpc:expose:s-S': null, // count-only
|
|
74
|
+
* 'gpc:expose:ch-CH': null, // count-only
|
|
75
|
+
* });
|
|
76
|
+
*/
|
|
77
|
+
export declare function adjustCourseScoresPerTag(aElo: Eloish, bElo: Eloish, taggedPerformance: TaggedPerformance): {
|
|
78
|
+
userElo: CourseElo;
|
|
79
|
+
cardElo: CourseElo;
|
|
80
|
+
};
|
|
43
81
|
export {};
|
|
44
82
|
//# sourceMappingURL=elo.d.ts.map
|
package/dist/elo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elo.d.ts","sourceRoot":"","sources":["../src/elo.ts"],"names":[],"mappings":"AAAA,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;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,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"}
|
|
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;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,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;AAqCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,CAyDA"}
|
package/dist/elo.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.toElo = toElo;
|
|
|
7
7
|
exports.toCourseElo = toCourseElo;
|
|
8
8
|
exports.isCourseElo = isCourseElo;
|
|
9
9
|
exports.adjustCourseScores = adjustCourseScores;
|
|
10
|
+
exports.adjustCourseScoresPerTag = adjustCourseScoresPerTag;
|
|
10
11
|
class EloRanker {
|
|
11
12
|
k;
|
|
12
13
|
constructor(k = 32) {
|
|
@@ -159,4 +160,86 @@ function adjustScores(userElo, cardElo, userScore) {
|
|
|
159
160
|
},
|
|
160
161
|
};
|
|
161
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Adjusts ELO scores with per-tag granularity.
|
|
165
|
+
*
|
|
166
|
+
* Unlike adjustCourseScores which applies the same score to all tags,
|
|
167
|
+
* this function allows different scores per tag for granular skill tracking.
|
|
168
|
+
*
|
|
169
|
+
* Tags can be scored (number 0-1) or count-only (null). Count-only tags are
|
|
170
|
+
* useful for exposure tracking (e.g., gpc:expose:*) where we only care about
|
|
171
|
+
* "how many times has the user seen this?" without measuring performance.
|
|
172
|
+
*
|
|
173
|
+
* @param aElo - User's current ELO (will be converted to CourseElo)
|
|
174
|
+
* @param bElo - Card's current ELO (will be converted to CourseElo)
|
|
175
|
+
* @param taggedPerformance - Object with _global score and per-tag scores/null
|
|
176
|
+
* @returns Updated user and card ELOs
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* // Spelling "cat" as "kat" - got 'a' and 't' right, but 'c' wrong
|
|
180
|
+
* adjustCourseScoresPerTag(userElo, cardElo, {
|
|
181
|
+
* _global: 0.67,
|
|
182
|
+
* 'gpc:exercise:c-K': 0,
|
|
183
|
+
* 'gpc:exercise:a-AE': 1,
|
|
184
|
+
* 'gpc:exercise:t-T': 1,
|
|
185
|
+
* });
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* // WhoSaidThat - exercise target, expose distractors (count-only)
|
|
189
|
+
* adjustCourseScoresPerTag(userElo, cardElo, {
|
|
190
|
+
* _global: 1.0,
|
|
191
|
+
* 'gpc:exercise:sh-SH': 1.0,
|
|
192
|
+
* 'gpc:expose:s-S': null, // count-only
|
|
193
|
+
* 'gpc:expose:ch-CH': null, // count-only
|
|
194
|
+
* });
|
|
195
|
+
*/
|
|
196
|
+
function adjustCourseScoresPerTag(aElo, bElo, taggedPerformance) {
|
|
197
|
+
const globalScore = taggedPerformance._global;
|
|
198
|
+
if (globalScore < 0 || globalScore > 1) {
|
|
199
|
+
throw new Error(`ELO _global score must be between 0 and 1 - received ${globalScore}`);
|
|
200
|
+
}
|
|
201
|
+
const userElo = toCourseElo(aElo);
|
|
202
|
+
const cardElo = toCourseElo(bElo);
|
|
203
|
+
// Process each tag in the performance object
|
|
204
|
+
for (const [key, tagScore] of Object.entries(taggedPerformance)) {
|
|
205
|
+
if (key === '_global')
|
|
206
|
+
continue;
|
|
207
|
+
// Count-only tag (exposure tracking): increment count, use -1 sentinel score
|
|
208
|
+
if (tagScore === null) {
|
|
209
|
+
userElo.tags[key] = userElo.tags[key] ?? { count: 0, score: -1 };
|
|
210
|
+
userElo.tags[key] = {
|
|
211
|
+
...userElo.tags[key],
|
|
212
|
+
count: userElo.tags[key].count + 1,
|
|
213
|
+
score: -1, // Sentinel: clearly not a real ELO score
|
|
214
|
+
};
|
|
215
|
+
// Skip card ELO update for count-only tags
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (typeof tagScore !== 'number' || tagScore < 0 || tagScore > 1) {
|
|
219
|
+
throw new Error(`ELO tag score for '${key}' must be between 0 and 1 - received ${tagScore}`);
|
|
220
|
+
}
|
|
221
|
+
// Initialize tag ELO on user if missing (use global score as baseline)
|
|
222
|
+
const userTagElo = userElo.tags[key] ?? {
|
|
223
|
+
count: 0,
|
|
224
|
+
score: userElo.global.score,
|
|
225
|
+
};
|
|
226
|
+
// Initialize tag ELO on card if missing (use global score as baseline)
|
|
227
|
+
const cardTagElo = cardElo.tags[key] ?? {
|
|
228
|
+
count: 0,
|
|
229
|
+
score: cardElo.global.score,
|
|
230
|
+
};
|
|
231
|
+
// Apply per-tag score
|
|
232
|
+
const adjusted = adjustScores(userTagElo, cardTagElo, tagScore);
|
|
233
|
+
userElo.tags[key] = adjusted.userElo;
|
|
234
|
+
cardElo.tags[key] = adjusted.cardElo;
|
|
235
|
+
}
|
|
236
|
+
// Apply global score to global ELO
|
|
237
|
+
const adjustedGlobal = adjustScores(userElo.global, cardElo.global, globalScore);
|
|
238
|
+
userElo.global = adjustedGlobal.userElo;
|
|
239
|
+
cardElo.global = adjustedGlobal.cardElo;
|
|
240
|
+
return {
|
|
241
|
+
userElo,
|
|
242
|
+
cardElo,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
162
245
|
//# sourceMappingURL=elo.js.map
|
package/dist/elo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elo.js","sourceRoot":"","sources":["../src/elo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"elo.js","sourceRoot":"","sources":["../src/elo.ts"],"names":[],"mappings":"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"}
|
package/dist/elo.mjs
CHANGED
|
@@ -149,4 +149,86 @@ function adjustScores(userElo, cardElo, userScore) {
|
|
|
149
149
|
},
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Adjusts ELO scores with per-tag granularity.
|
|
154
|
+
*
|
|
155
|
+
* Unlike adjustCourseScores which applies the same score to all tags,
|
|
156
|
+
* this function allows different scores per tag for granular skill tracking.
|
|
157
|
+
*
|
|
158
|
+
* Tags can be scored (number 0-1) or count-only (null). Count-only tags are
|
|
159
|
+
* useful for exposure tracking (e.g., gpc:expose:*) where we only care about
|
|
160
|
+
* "how many times has the user seen this?" without measuring performance.
|
|
161
|
+
*
|
|
162
|
+
* @param aElo - User's current ELO (will be converted to CourseElo)
|
|
163
|
+
* @param bElo - Card's current ELO (will be converted to CourseElo)
|
|
164
|
+
* @param taggedPerformance - Object with _global score and per-tag scores/null
|
|
165
|
+
* @returns Updated user and card ELOs
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* // Spelling "cat" as "kat" - got 'a' and 't' right, but 'c' wrong
|
|
169
|
+
* adjustCourseScoresPerTag(userElo, cardElo, {
|
|
170
|
+
* _global: 0.67,
|
|
171
|
+
* 'gpc:exercise:c-K': 0,
|
|
172
|
+
* 'gpc:exercise:a-AE': 1,
|
|
173
|
+
* 'gpc:exercise:t-T': 1,
|
|
174
|
+
* });
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* // WhoSaidThat - exercise target, expose distractors (count-only)
|
|
178
|
+
* adjustCourseScoresPerTag(userElo, cardElo, {
|
|
179
|
+
* _global: 1.0,
|
|
180
|
+
* 'gpc:exercise:sh-SH': 1.0,
|
|
181
|
+
* 'gpc:expose:s-S': null, // count-only
|
|
182
|
+
* 'gpc:expose:ch-CH': null, // count-only
|
|
183
|
+
* });
|
|
184
|
+
*/
|
|
185
|
+
export function adjustCourseScoresPerTag(aElo, bElo, taggedPerformance) {
|
|
186
|
+
const globalScore = taggedPerformance._global;
|
|
187
|
+
if (globalScore < 0 || globalScore > 1) {
|
|
188
|
+
throw new Error(`ELO _global score must be between 0 and 1 - received ${globalScore}`);
|
|
189
|
+
}
|
|
190
|
+
const userElo = toCourseElo(aElo);
|
|
191
|
+
const cardElo = toCourseElo(bElo);
|
|
192
|
+
// Process each tag in the performance object
|
|
193
|
+
for (const [key, tagScore] of Object.entries(taggedPerformance)) {
|
|
194
|
+
if (key === '_global')
|
|
195
|
+
continue;
|
|
196
|
+
// Count-only tag (exposure tracking): increment count, use -1 sentinel score
|
|
197
|
+
if (tagScore === null) {
|
|
198
|
+
userElo.tags[key] = userElo.tags[key] ?? { count: 0, score: -1 };
|
|
199
|
+
userElo.tags[key] = {
|
|
200
|
+
...userElo.tags[key],
|
|
201
|
+
count: userElo.tags[key].count + 1,
|
|
202
|
+
score: -1, // Sentinel: clearly not a real ELO score
|
|
203
|
+
};
|
|
204
|
+
// Skip card ELO update for count-only tags
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (typeof tagScore !== 'number' || tagScore < 0 || tagScore > 1) {
|
|
208
|
+
throw new Error(`ELO tag score for '${key}' must be between 0 and 1 - received ${tagScore}`);
|
|
209
|
+
}
|
|
210
|
+
// Initialize tag ELO on user if missing (use global score as baseline)
|
|
211
|
+
const userTagElo = userElo.tags[key] ?? {
|
|
212
|
+
count: 0,
|
|
213
|
+
score: userElo.global.score,
|
|
214
|
+
};
|
|
215
|
+
// Initialize tag ELO on card if missing (use global score as baseline)
|
|
216
|
+
const cardTagElo = cardElo.tags[key] ?? {
|
|
217
|
+
count: 0,
|
|
218
|
+
score: cardElo.global.score,
|
|
219
|
+
};
|
|
220
|
+
// Apply per-tag score
|
|
221
|
+
const adjusted = adjustScores(userTagElo, cardTagElo, tagScore);
|
|
222
|
+
userElo.tags[key] = adjusted.userElo;
|
|
223
|
+
cardElo.tags[key] = adjusted.cardElo;
|
|
224
|
+
}
|
|
225
|
+
// Apply global score to global ELO
|
|
226
|
+
const adjustedGlobal = adjustScores(userElo.global, cardElo.global, globalScore);
|
|
227
|
+
userElo.global = adjustedGlobal.userElo;
|
|
228
|
+
cardElo.global = adjustedGlobal.cardElo;
|
|
229
|
+
return {
|
|
230
|
+
userElo,
|
|
231
|
+
cardElo,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
152
234
|
//# sourceMappingURL=elo.js.map
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.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.1.
|
|
39
|
+
"stableVersion": "0.1.26"
|
|
40
40
|
}
|
package/src/course-data.ts
CHANGED
|
@@ -112,8 +112,63 @@ export interface Evaluation {
|
|
|
112
112
|
performance: Performance;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Performance can be a simple number (0-1) for overall score,
|
|
117
|
+
* or a structured object with per-tag granularity.
|
|
118
|
+
*/
|
|
119
|
+
export type Performance = number | TaggedPerformance;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Structured performance with per-tag scoring.
|
|
123
|
+
*
|
|
124
|
+
* Questions that exercise multiple skills (e.g., spelling with multiple GPCs)
|
|
125
|
+
* can provide individual scores per tag for granular ELO updates.
|
|
126
|
+
*
|
|
127
|
+
* Tags can have scores (for exercise tags) or `null` (for count-only exposure tags).
|
|
128
|
+
* Count-only tags increment their count but maintain a sentinel score of -1,
|
|
129
|
+
* making them easily identifiable and preventing them from polluting real ELO data.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* // Spelling "cat" as "kat" - got 'a' and 't' right, but 'c' wrong
|
|
133
|
+
* {
|
|
134
|
+
* _global: 0.67, // 2/3 correct, used for SRS and global ELO
|
|
135
|
+
* 'gpc:exercise:c-K': 0, // incorrect
|
|
136
|
+
* 'gpc:exercise:a-AE': 1, // correct
|
|
137
|
+
* 'gpc:exercise:t-T': 1, // correct
|
|
138
|
+
* }
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* // WhoSaidThat card exercising 'sh' while exposing distractors
|
|
142
|
+
* {
|
|
143
|
+
* _global: 1.0,
|
|
144
|
+
* 'gpc:exercise:sh-SH': 1.0, // exercised and correct
|
|
145
|
+
* 'gpc:expose:s-S': null, // count-only exposure (no score)
|
|
146
|
+
* 'gpc:expose:ch-CH': null, // count-only exposure (no score)
|
|
147
|
+
* }
|
|
148
|
+
*/
|
|
149
|
+
export interface TaggedPerformance {
|
|
150
|
+
/**
|
|
151
|
+
* Overall score for SRS scheduling and global ELO updates.
|
|
152
|
+
* Required when using structured performance.
|
|
153
|
+
* Range: [0, 1]
|
|
154
|
+
*/
|
|
155
|
+
_global: number;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Per-tag scores or count-only markers.
|
|
159
|
+
*
|
|
160
|
+
* - **Number (0-1)**: Tag is exercised; score updates via ELO formula
|
|
161
|
+
* - **null**: Count-only tag (e.g., exposure); increments count, score stays -1 (sentinel)
|
|
162
|
+
*
|
|
163
|
+
* Tags not present on the card will be created dynamically.
|
|
164
|
+
* Count-only tags (null) do not update card ELO.
|
|
165
|
+
*/
|
|
166
|
+
[tag: string]: number | null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Type guard to check if performance is structured (TaggedPerformance).
|
|
171
|
+
*/
|
|
172
|
+
export function isTaggedPerformance(p: Performance): p is TaggedPerformance {
|
|
173
|
+
return typeof p === 'object' && p !== null && '_global' in p;
|
|
174
|
+
}
|
package/src/elo.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { TaggedPerformance } from './course-data.js';
|
|
2
|
+
|
|
1
3
|
export class EloRanker {
|
|
2
4
|
constructor(public k: number = 32) {}
|
|
3
5
|
|
|
@@ -191,3 +193,102 @@ function adjustScores(
|
|
|
191
193
|
},
|
|
192
194
|
};
|
|
193
195
|
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Adjusts ELO scores with per-tag granularity.
|
|
199
|
+
*
|
|
200
|
+
* Unlike adjustCourseScores which applies the same score to all tags,
|
|
201
|
+
* this function allows different scores per tag for granular skill tracking.
|
|
202
|
+
*
|
|
203
|
+
* Tags can be scored (number 0-1) or count-only (null). Count-only tags are
|
|
204
|
+
* useful for exposure tracking (e.g., gpc:expose:*) where we only care about
|
|
205
|
+
* "how many times has the user seen this?" without measuring performance.
|
|
206
|
+
*
|
|
207
|
+
* @param aElo - User's current ELO (will be converted to CourseElo)
|
|
208
|
+
* @param bElo - Card's current ELO (will be converted to CourseElo)
|
|
209
|
+
* @param taggedPerformance - Object with _global score and per-tag scores/null
|
|
210
|
+
* @returns Updated user and card ELOs
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* // Spelling "cat" as "kat" - got 'a' and 't' right, but 'c' wrong
|
|
214
|
+
* adjustCourseScoresPerTag(userElo, cardElo, {
|
|
215
|
+
* _global: 0.67,
|
|
216
|
+
* 'gpc:exercise:c-K': 0,
|
|
217
|
+
* 'gpc:exercise:a-AE': 1,
|
|
218
|
+
* 'gpc:exercise:t-T': 1,
|
|
219
|
+
* });
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* // WhoSaidThat - exercise target, expose distractors (count-only)
|
|
223
|
+
* adjustCourseScoresPerTag(userElo, cardElo, {
|
|
224
|
+
* _global: 1.0,
|
|
225
|
+
* 'gpc:exercise:sh-SH': 1.0,
|
|
226
|
+
* 'gpc:expose:s-S': null, // count-only
|
|
227
|
+
* 'gpc:expose:ch-CH': null, // count-only
|
|
228
|
+
* });
|
|
229
|
+
*/
|
|
230
|
+
export function adjustCourseScoresPerTag(
|
|
231
|
+
aElo: Eloish,
|
|
232
|
+
bElo: Eloish,
|
|
233
|
+
taggedPerformance: TaggedPerformance
|
|
234
|
+
): {
|
|
235
|
+
userElo: CourseElo;
|
|
236
|
+
cardElo: CourseElo;
|
|
237
|
+
} {
|
|
238
|
+
const globalScore = taggedPerformance._global;
|
|
239
|
+
|
|
240
|
+
if (globalScore < 0 || globalScore > 1) {
|
|
241
|
+
throw new Error(`ELO _global score must be between 0 and 1 - received ${globalScore}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const userElo: CourseElo = toCourseElo(aElo);
|
|
245
|
+
const cardElo: CourseElo = toCourseElo(bElo);
|
|
246
|
+
|
|
247
|
+
// Process each tag in the performance object
|
|
248
|
+
for (const [key, tagScore] of Object.entries(taggedPerformance)) {
|
|
249
|
+
if (key === '_global') continue;
|
|
250
|
+
|
|
251
|
+
// Count-only tag (exposure tracking): increment count, use -1 sentinel score
|
|
252
|
+
if (tagScore === null) {
|
|
253
|
+
userElo.tags[key] = userElo.tags[key] ?? { count: 0, score: -1 };
|
|
254
|
+
userElo.tags[key] = {
|
|
255
|
+
...userElo.tags[key],
|
|
256
|
+
count: userElo.tags[key].count + 1,
|
|
257
|
+
score: -1, // Sentinel: clearly not a real ELO score
|
|
258
|
+
};
|
|
259
|
+
// Skip card ELO update for count-only tags
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (typeof tagScore !== 'number' || tagScore < 0 || tagScore > 1) {
|
|
264
|
+
throw new Error(`ELO tag score for '${key}' must be between 0 and 1 - received ${tagScore}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Initialize tag ELO on user if missing (use global score as baseline)
|
|
268
|
+
const userTagElo: EloRank = userElo.tags[key] ?? {
|
|
269
|
+
count: 0,
|
|
270
|
+
score: userElo.global.score,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// Initialize tag ELO on card if missing (use global score as baseline)
|
|
274
|
+
const cardTagElo: EloRank = cardElo.tags[key] ?? {
|
|
275
|
+
count: 0,
|
|
276
|
+
score: cardElo.global.score,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// Apply per-tag score
|
|
280
|
+
const adjusted = adjustScores(userTagElo, cardTagElo, tagScore);
|
|
281
|
+
userElo.tags[key] = adjusted.userElo;
|
|
282
|
+
cardElo.tags[key] = adjusted.cardElo;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Apply global score to global ELO
|
|
286
|
+
const adjustedGlobal = adjustScores(userElo.global, cardElo.global, globalScore);
|
|
287
|
+
userElo.global = adjustedGlobal.userElo;
|
|
288
|
+
cardElo.global = adjustedGlobal.cardElo;
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
userElo,
|
|
292
|
+
cardElo,
|
|
293
|
+
};
|
|
294
|
+
}
|