@vue-skuilder/common 0.1.24 → 0.1.25

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.
@@ -10,8 +10,42 @@ export interface Evaluation {
10
10
  isCorrect: boolean;
11
11
  performance: Performance;
12
12
  }
13
- type Performance = number | {
14
- [dimension: string]: Performance;
15
- };
16
- export {};
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
+ * @example
25
+ * // Spelling "cat" as "kat" - got 'a' and 't' right, but 'c' wrong
26
+ * {
27
+ * _global: 0.67, // 2/3 correct, used for SRS and global ELO
28
+ * 'GPC-c-K': 0, // incorrect
29
+ * 'GPC-a-AE': 1, // correct
30
+ * 'GPC-t-T': 1, // correct
31
+ * }
32
+ */
33
+ export interface TaggedPerformance {
34
+ /**
35
+ * Overall score for SRS scheduling and global ELO updates.
36
+ * Required when using structured performance.
37
+ * Range: [0, 1]
38
+ */
39
+ _global: number;
40
+ /**
41
+ * Per-tag scores. Keys are tag IDs (e.g., 'GPC-c-K').
42
+ * Tags not present on the card will be created dynamically.
43
+ * Range: [0, 1] for each value.
44
+ */
45
+ [tag: string]: number;
46
+ }
47
+ /**
48
+ * Type guard to check if performance is structured (TaggedPerformance).
49
+ */
50
+ export declare function isTaggedPerformance(p: Performance): p is TaggedPerformance;
17
51
  //# 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,KAAK,WAAW,GACZ,MAAM,GACN;IACE,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,CAAC;CAClC,CAAC"}
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;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,WAAW,GAAG,CAAC,IAAI,iBAAiB,CAE1E"}
@@ -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
@@ -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":"AAAA,OAAO,EAAmB,OAAO,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD,MAAM,UAAU,aAAa,CAC3B,QAAgB,EAChB,UAAkB,EAClB,KAAgB;AAChB,iBAAiB;AACjB,8DAA8D;AAC9D,IAAS,EACT,MAAc,EACd,KAAe,EACf,OAAsD;IAEtD,MAAM,WAAW,GAAG,UAAU,CAAC,kBAAkB,CAAC;QAChD,MAAM,EAAE,UAAU;QAClB,SAAS,EAAE,KAAK,CAAC,IAAI;KACtB,CAAC,CAAC;IAEH,MAAM,gBAAgB,GAAG,KAAK,CAAC,MAAM;SAClC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,uDAAuD;QACvD,MAAM,IAAI,GAAoB;YAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,KAAK,CAAC,IAAI;SACjB,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QAChB,OAAO,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,KAAK,CAAC;IAC1E,CAAC,CAAC;SACD,MAAM,CAAC;QACN;YACE,IAAI,EAAE,eAAe;YACrB,IAAI,EAAE,SAAS,CAAC,KAAK;SACtB;KACF,CAAC,CAAC;IAEL,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,IAAI,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC;YACvB,gBAAgB,CAAC,IAAI,CAAC;gBACpB,IAAI,EAAE,SAAS,CAAC,EAAE;gBAClB,IAAI,EAAE,SAAS,CAAC,KAAK;aACtB,CAAC,CAAC;QACL,CAAC;QAED,IAAI,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC;YACvB,gBAAgB,CAAC,IAAI,CAAC;gBACpB,IAAI,EAAE,SAAS,CAAC,EAAE;gBAClB,IAAI,EAAE,SAAS,CAAC,KAAK;aACtB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,WAAW,GAAqD,EAAE,CAAC;IACzE,MAAM,OAAO,GAAoB;QAC/B,MAAM,EAAE,QAAQ;QAChB,IAAI,EAAE,EAAE;QACR,OAAO,EAAE,OAAO,CAAC,gBAAgB;QACjC,YAAY,EAAE,WAAW;KAC1B,CAAC;IAEF,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;IAC1B,CAAC;IAED,gBAAgB,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;QACpC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE;IACF,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YACjC,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9E,OAAO,CAAC,YAAY,GAAG,WAAW,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,MAAM;SACT,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QAChB,OAAO,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,KAAK,CAAC;IAC1E,CAAC,CAAC;SACD,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QACjB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAChB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;SACvB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,OAAO,OAAO,CAAC;AACjB,CAAC;AAgDD;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAAc;IAChD,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,SAAS,IAAI,CAAC,CAAC;AAC/D,CAAC"}
@@ -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,29 @@ 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
+ * @param aElo - User's current ELO (will be converted to CourseElo)
51
+ * @param bElo - Card's current ELO (will be converted to CourseElo)
52
+ * @param taggedPerformance - Object with _global score and per-tag scores
53
+ * @returns Updated user and card ELOs
54
+ *
55
+ * @example
56
+ * // Spelling "cat" as "kat" - got 'a' and 't' right, but 'c' wrong
57
+ * adjustCourseScoresPerTag(userElo, cardElo, {
58
+ * _global: 0.67,
59
+ * 'GPC-c-K': 0,
60
+ * 'GPC-a-AE': 1,
61
+ * 'GPC-t-T': 1,
62
+ * });
63
+ */
64
+ export declare function adjustCourseScoresPerTag(aElo: Eloish, bElo: Eloish, taggedPerformance: TaggedPerformance): {
65
+ userElo: CourseElo;
66
+ cardElo: CourseElo;
67
+ };
43
68
  export {};
44
69
  //# 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;;;;;;;;;;;;;;;;;;;GAmBG;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,CA6CA"}
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,62 @@ 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
+ * @param aElo - User's current ELO (will be converted to CourseElo)
170
+ * @param bElo - Card's current ELO (will be converted to CourseElo)
171
+ * @param taggedPerformance - Object with _global score and per-tag scores
172
+ * @returns Updated user and card ELOs
173
+ *
174
+ * @example
175
+ * // Spelling "cat" as "kat" - got 'a' and 't' right, but 'c' wrong
176
+ * adjustCourseScoresPerTag(userElo, cardElo, {
177
+ * _global: 0.67,
178
+ * 'GPC-c-K': 0,
179
+ * 'GPC-a-AE': 1,
180
+ * 'GPC-t-T': 1,
181
+ * });
182
+ */
183
+ function adjustCourseScoresPerTag(aElo, bElo, taggedPerformance) {
184
+ const globalScore = taggedPerformance._global;
185
+ if (globalScore < 0 || globalScore > 1) {
186
+ throw new Error(`ELO _global score must be between 0 and 1 - received ${globalScore}`);
187
+ }
188
+ const userElo = toCourseElo(aElo);
189
+ const cardElo = toCourseElo(bElo);
190
+ // Process each tag in the performance object
191
+ for (const [key, tagScore] of Object.entries(taggedPerformance)) {
192
+ if (key === '_global')
193
+ continue;
194
+ if (typeof tagScore !== 'number' || tagScore < 0 || tagScore > 1) {
195
+ throw new Error(`ELO tag score for '${key}' must be between 0 and 1 - received ${tagScore}`);
196
+ }
197
+ // Initialize tag ELO on user if missing (use global score as baseline)
198
+ const userTagElo = userElo.tags[key] ?? {
199
+ count: 0,
200
+ score: userElo.global.score,
201
+ };
202
+ // Initialize tag ELO on card if missing (use global score as baseline)
203
+ const cardTagElo = cardElo.tags[key] ?? {
204
+ count: 0,
205
+ score: cardElo.global.score,
206
+ };
207
+ // Apply per-tag score
208
+ const adjusted = adjustScores(userTagElo, cardTagElo, tagScore);
209
+ userElo.tags[key] = adjusted.userElo;
210
+ cardElo.tags[key] = adjusted.cardElo;
211
+ }
212
+ // Apply global score to global ELO
213
+ const adjustedGlobal = adjustScores(userElo.global, cardElo.global, globalScore);
214
+ userElo.global = adjustedGlobal.userElo;
215
+ cardElo.global = adjustedGlobal.cardElo;
216
+ return {
217
+ userElo,
218
+ cardElo,
219
+ };
220
+ }
162
221
  //# 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":"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"}
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,62 @@ 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
+ * @param aElo - User's current ELO (will be converted to CourseElo)
159
+ * @param bElo - Card's current ELO (will be converted to CourseElo)
160
+ * @param taggedPerformance - Object with _global score and per-tag scores
161
+ * @returns Updated user and card ELOs
162
+ *
163
+ * @example
164
+ * // Spelling "cat" as "kat" - got 'a' and 't' right, but 'c' wrong
165
+ * adjustCourseScoresPerTag(userElo, cardElo, {
166
+ * _global: 0.67,
167
+ * 'GPC-c-K': 0,
168
+ * 'GPC-a-AE': 1,
169
+ * 'GPC-t-T': 1,
170
+ * });
171
+ */
172
+ export function adjustCourseScoresPerTag(aElo, bElo, taggedPerformance) {
173
+ const globalScore = taggedPerformance._global;
174
+ if (globalScore < 0 || globalScore > 1) {
175
+ throw new Error(`ELO _global score must be between 0 and 1 - received ${globalScore}`);
176
+ }
177
+ const userElo = toCourseElo(aElo);
178
+ const cardElo = toCourseElo(bElo);
179
+ // Process each tag in the performance object
180
+ for (const [key, tagScore] of Object.entries(taggedPerformance)) {
181
+ if (key === '_global')
182
+ continue;
183
+ if (typeof tagScore !== 'number' || tagScore < 0 || tagScore > 1) {
184
+ throw new Error(`ELO tag score for '${key}' must be between 0 and 1 - received ${tagScore}`);
185
+ }
186
+ // Initialize tag ELO on user if missing (use global score as baseline)
187
+ const userTagElo = userElo.tags[key] ?? {
188
+ count: 0,
189
+ score: userElo.global.score,
190
+ };
191
+ // Initialize tag ELO on card if missing (use global score as baseline)
192
+ const cardTagElo = cardElo.tags[key] ?? {
193
+ count: 0,
194
+ score: cardElo.global.score,
195
+ };
196
+ // Apply per-tag score
197
+ const adjusted = adjustScores(userTagElo, cardTagElo, tagScore);
198
+ userElo.tags[key] = adjusted.userElo;
199
+ cardElo.tags[key] = adjusted.cardElo;
200
+ }
201
+ // Apply global score to global ELO
202
+ const adjustedGlobal = adjustScores(userElo.global, cardElo.global, globalScore);
203
+ userElo.global = adjustedGlobal.userElo;
204
+ cardElo.global = adjustedGlobal.cardElo;
205
+ return {
206
+ userElo,
207
+ cardElo,
208
+ };
209
+ }
152
210
  //# 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.24",
6
+ "version": "0.1.25",
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.24"
39
+ "stableVersion": "0.1.25"
40
40
  }
@@ -112,8 +112,46 @@ export interface Evaluation {
112
112
  performance: Performance;
113
113
  }
114
114
 
115
- type Performance =
116
- | number
117
- | {
118
- [dimension: string]: Performance;
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
+ * @example
128
+ * // Spelling "cat" as "kat" - got 'a' and 't' right, but 'c' wrong
129
+ * {
130
+ * _global: 0.67, // 2/3 correct, used for SRS and global ELO
131
+ * 'GPC-c-K': 0, // incorrect
132
+ * 'GPC-a-AE': 1, // correct
133
+ * 'GPC-t-T': 1, // correct
134
+ * }
135
+ */
136
+ export interface TaggedPerformance {
137
+ /**
138
+ * Overall score for SRS scheduling and global ELO updates.
139
+ * Required when using structured performance.
140
+ * Range: [0, 1]
141
+ */
142
+ _global: number;
143
+
144
+ /**
145
+ * Per-tag scores. Keys are tag IDs (e.g., 'GPC-c-K').
146
+ * Tags not present on the card will be created dynamically.
147
+ * Range: [0, 1] for each value.
148
+ */
149
+ [tag: string]: number;
150
+ }
151
+
152
+ /**
153
+ * Type guard to check if performance is structured (TaggedPerformance).
154
+ */
155
+ export function isTaggedPerformance(p: Performance): p is TaggedPerformance {
156
+ return typeof p === 'object' && p !== null && '_global' in p;
157
+ }
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,77 @@ 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
+ * @param aElo - User's current ELO (will be converted to CourseElo)
204
+ * @param bElo - Card's current ELO (will be converted to CourseElo)
205
+ * @param taggedPerformance - Object with _global score and per-tag scores
206
+ * @returns Updated user and card ELOs
207
+ *
208
+ * @example
209
+ * // Spelling "cat" as "kat" - got 'a' and 't' right, but 'c' wrong
210
+ * adjustCourseScoresPerTag(userElo, cardElo, {
211
+ * _global: 0.67,
212
+ * 'GPC-c-K': 0,
213
+ * 'GPC-a-AE': 1,
214
+ * 'GPC-t-T': 1,
215
+ * });
216
+ */
217
+ export function adjustCourseScoresPerTag(
218
+ aElo: Eloish,
219
+ bElo: Eloish,
220
+ taggedPerformance: TaggedPerformance
221
+ ): {
222
+ userElo: CourseElo;
223
+ cardElo: CourseElo;
224
+ } {
225
+ const globalScore = taggedPerformance._global;
226
+
227
+ if (globalScore < 0 || globalScore > 1) {
228
+ throw new Error(`ELO _global score must be between 0 and 1 - received ${globalScore}`);
229
+ }
230
+
231
+ const userElo: CourseElo = toCourseElo(aElo);
232
+ const cardElo: CourseElo = toCourseElo(bElo);
233
+
234
+ // Process each tag in the performance object
235
+ for (const [key, tagScore] of Object.entries(taggedPerformance)) {
236
+ if (key === '_global') continue;
237
+
238
+ if (typeof tagScore !== 'number' || tagScore < 0 || tagScore > 1) {
239
+ throw new Error(`ELO tag score for '${key}' must be between 0 and 1 - received ${tagScore}`);
240
+ }
241
+
242
+ // Initialize tag ELO on user if missing (use global score as baseline)
243
+ const userTagElo: EloRank = userElo.tags[key] ?? {
244
+ count: 0,
245
+ score: userElo.global.score,
246
+ };
247
+
248
+ // Initialize tag ELO on card if missing (use global score as baseline)
249
+ const cardTagElo: EloRank = cardElo.tags[key] ?? {
250
+ count: 0,
251
+ score: cardElo.global.score,
252
+ };
253
+
254
+ // Apply per-tag score
255
+ const adjusted = adjustScores(userTagElo, cardTagElo, tagScore);
256
+ userElo.tags[key] = adjusted.userElo;
257
+ cardElo.tags[key] = adjusted.cardElo;
258
+ }
259
+
260
+ // Apply global score to global ELO
261
+ const adjustedGlobal = adjustScores(userElo.global, cardElo.global, globalScore);
262
+ userElo.global = adjustedGlobal.userElo;
263
+ cardElo.global = adjustedGlobal.cardElo;
264
+
265
+ return {
266
+ userElo,
267
+ cardElo,
268
+ };
269
+ }