@vue-skuilder/common 0.1.34 → 0.1.35

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.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;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"}
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;AAiDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,CA8DA"}
package/dist/elo.js CHANGED
@@ -137,15 +137,26 @@ function adjustCourseScores(aElo, bElo, userScore, options) {
137
137
  cardElo,
138
138
  };
139
139
  }
140
+ /**
141
+ * Hyperbolic K-factor: high sensitivity at low count (uncertain rating),
142
+ * converging to a stable floor as evidence accumulates.
143
+ *
144
+ * K(0) = 64 — first encounter, large update
145
+ * K(10) = 40 — midpoint at halflife
146
+ * K(∞) → 16 — well-established rating
147
+ */
148
+ function kForCount(count) {
149
+ const K_MIN = 16;
150
+ const K_MAX = 64;
151
+ const HALFLIFE = 10;
152
+ return K_MIN + (K_MAX - K_MIN) / (1 + count / HALFLIFE);
153
+ }
140
154
  function adjustScores(userElo, cardElo, userScore) {
141
155
  if (userScore < 0 || userScore > 1) {
142
156
  throw new Error(`ELO performance rating must be between 0 and 1 - received ${userScore}`);
143
157
  }
144
- // todo: how to calculate here?
145
- // todo: should / must these be equal?
146
- // todo: 176 - these K values should be a fcn of `.count` values of userElo and cardElo
147
- const userRanker = new EloRanker(16);
148
- const cardRanker = new EloRanker(16);
158
+ const userRanker = new EloRanker(kForCount(userElo.count));
159
+ const cardRanker = new EloRanker(kForCount(cardElo.count));
149
160
  const exp = userRanker.getExpected(userElo.score, cardElo.score);
150
161
  const updatedUserElo = userRanker.updateRating(exp, userScore, userElo.score);
151
162
  const updatedCardElo = cardRanker.updateRating(1 - exp, 1 - userScore, cardElo.score);
@@ -218,11 +229,16 @@ function adjustCourseScoresPerTag(aElo, bElo, taggedPerformance) {
218
229
  if (typeof tagScore !== 'number' || tagScore < 0 || tagScore > 1) {
219
230
  throw new Error(`ELO tag score for '${key}' must be between 0 and 1 - received ${tagScore}`);
220
231
  }
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
- };
232
+ // Initialize tag ELO on user if missing or if the existing entry is the
233
+ // count-only sentinel (-1). A sentinel entry means the tag was only seen
234
+ // via null/exposure interactions; treat the first real-scored interaction
235
+ // as a clean start from global ELO so it isn't penalized against a
236
+ // fictitiously low prior. Preserve accumulated count so exposure-based
237
+ // count-threshold progress is not discarded.
238
+ const existingUserTagElo = userElo.tags[key];
239
+ const userTagElo = (existingUserTagElo && existingUserTagElo.score !== -1)
240
+ ? existingUserTagElo
241
+ : { count: existingUserTagElo?.count ?? 0, score: userElo.global.score };
226
242
  // Initialize tag ELO on card if missing (use global score as baseline)
227
243
  const cardTagElo = cardElo.tags[key] ?? {
228
244
  count: 0,
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
@@ -126,15 +126,26 @@ export function adjustCourseScores(aElo, bElo, userScore, options) {
126
126
  cardElo,
127
127
  };
128
128
  }
129
+ /**
130
+ * Hyperbolic K-factor: high sensitivity at low count (uncertain rating),
131
+ * converging to a stable floor as evidence accumulates.
132
+ *
133
+ * K(0) = 64 — first encounter, large update
134
+ * K(10) = 40 — midpoint at halflife
135
+ * K(∞) → 16 — well-established rating
136
+ */
137
+ function kForCount(count) {
138
+ const K_MIN = 16;
139
+ const K_MAX = 64;
140
+ const HALFLIFE = 10;
141
+ return K_MIN + (K_MAX - K_MIN) / (1 + count / HALFLIFE);
142
+ }
129
143
  function adjustScores(userElo, cardElo, userScore) {
130
144
  if (userScore < 0 || userScore > 1) {
131
145
  throw new Error(`ELO performance rating must be between 0 and 1 - received ${userScore}`);
132
146
  }
133
- // todo: how to calculate here?
134
- // todo: should / must these be equal?
135
- // todo: 176 - these K values should be a fcn of `.count` values of userElo and cardElo
136
- const userRanker = new EloRanker(16);
137
- const cardRanker = new EloRanker(16);
147
+ const userRanker = new EloRanker(kForCount(userElo.count));
148
+ const cardRanker = new EloRanker(kForCount(cardElo.count));
138
149
  const exp = userRanker.getExpected(userElo.score, cardElo.score);
139
150
  const updatedUserElo = userRanker.updateRating(exp, userScore, userElo.score);
140
151
  const updatedCardElo = cardRanker.updateRating(1 - exp, 1 - userScore, cardElo.score);
@@ -207,11 +218,16 @@ export function adjustCourseScoresPerTag(aElo, bElo, taggedPerformance) {
207
218
  if (typeof tagScore !== 'number' || tagScore < 0 || tagScore > 1) {
208
219
  throw new Error(`ELO tag score for '${key}' must be between 0 and 1 - received ${tagScore}`);
209
220
  }
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
- };
221
+ // Initialize tag ELO on user if missing or if the existing entry is the
222
+ // count-only sentinel (-1). A sentinel entry means the tag was only seen
223
+ // via null/exposure interactions; treat the first real-scored interaction
224
+ // as a clean start from global ELO so it isn't penalized against a
225
+ // fictitiously low prior. Preserve accumulated count so exposure-based
226
+ // count-threshold progress is not discarded.
227
+ const existingUserTagElo = userElo.tags[key];
228
+ const userTagElo = (existingUserTagElo && existingUserTagElo.score !== -1)
229
+ ? existingUserTagElo
230
+ : { count: existingUserTagElo?.count ?? 0, score: userElo.global.score };
215
231
  // Initialize tag ELO on card if missing (use global score as baseline)
216
232
  const cardTagElo = cardElo.tags[key] ?? {
217
233
  count: 0,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.34",
6
+ "version": "0.1.35",
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.34"
39
+ "stableVersion": "0.1.35"
40
40
  }
package/src/elo.ts CHANGED
@@ -159,6 +159,21 @@ export function adjustCourseScores(
159
159
  };
160
160
  }
161
161
 
162
+ /**
163
+ * Hyperbolic K-factor: high sensitivity at low count (uncertain rating),
164
+ * converging to a stable floor as evidence accumulates.
165
+ *
166
+ * K(0) = 64 — first encounter, large update
167
+ * K(10) = 40 — midpoint at halflife
168
+ * K(∞) → 16 — well-established rating
169
+ */
170
+ function kForCount(count: number): number {
171
+ const K_MIN = 16;
172
+ const K_MAX = 64;
173
+ const HALFLIFE = 10;
174
+ return K_MIN + (K_MAX - K_MIN) / (1 + count / HALFLIFE);
175
+ }
176
+
162
177
  function adjustScores(
163
178
  userElo: EloRank,
164
179
  cardElo: EloRank,
@@ -171,11 +186,8 @@ function adjustScores(
171
186
  throw new Error(`ELO performance rating must be between 0 and 1 - received ${userScore}`);
172
187
  }
173
188
 
174
- // todo: how to calculate here?
175
- // todo: should / must these be equal?
176
- // todo: 176 - these K values should be a fcn of `.count` values of userElo and cardElo
177
- const userRanker = new EloRanker(16);
178
- const cardRanker = new EloRanker(16);
189
+ const userRanker = new EloRanker(kForCount(userElo.count));
190
+ const cardRanker = new EloRanker(kForCount(cardElo.count));
179
191
 
180
192
  const exp = userRanker.getExpected(userElo.score, cardElo.score);
181
193
 
@@ -264,11 +276,16 @@ export function adjustCourseScoresPerTag(
264
276
  throw new Error(`ELO tag score for '${key}' must be between 0 and 1 - received ${tagScore}`);
265
277
  }
266
278
 
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
- };
279
+ // Initialize tag ELO on user if missing or if the existing entry is the
280
+ // count-only sentinel (-1). A sentinel entry means the tag was only seen
281
+ // via null/exposure interactions; treat the first real-scored interaction
282
+ // as a clean start from global ELO so it isn't penalized against a
283
+ // fictitiously low prior. Preserve accumulated count so exposure-based
284
+ // count-threshold progress is not discarded.
285
+ const existingUserTagElo = userElo.tags[key];
286
+ const userTagElo: EloRank = (existingUserTagElo && existingUserTagElo.score !== -1)
287
+ ? existingUserTagElo
288
+ : { count: existingUserTagElo?.count ?? 0, score: userElo.global.score };
272
289
 
273
290
  // Initialize tag ELO on card if missing (use global score as baseline)
274
291
  const cardTagElo: EloRank = cardElo.tags[key] ?? {