@shaxpir/duiduidui-models 1.4.26 → 1.4.27

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.
@@ -1,9 +1,28 @@
1
1
  import { ReviewResult } from "./Review";
2
+ export interface Bounds {
3
+ lower: number;
4
+ upper: number;
5
+ }
2
6
  export interface BayesianScore {
3
7
  alpha: number;
4
8
  beta: number;
5
9
  theta: number;
10
+ uncertainty: number;
6
11
  }
7
12
  export declare class BayesianScoreModel {
8
13
  static apply(prevScore: BayesianScore, result: ReviewResult, weight: number): BayesianScore;
14
+ /**
15
+ * Calculate uncertainty for a given Bayesian score.
16
+ * Higher values indicate less confidence in the theta estimate.
17
+ */
18
+ static getUncertainty(score: BayesianScore): number;
19
+ /**
20
+ * Calculate confidence interval for theta estimate.
21
+ * Uses properties of the Beta distribution to estimate bounds.
22
+ */
23
+ static getConfidenceInterval(score: BayesianScore, confidence?: number): Bounds;
24
+ /**
25
+ * Check if we have high confidence in this score (low uncertainty).
26
+ */
27
+ static isHighConfidence(score: BayesianScore, threshold?: number): boolean;
9
28
  }
@@ -21,7 +21,37 @@ class BayesianScoreModel {
21
21
  nextScore.beta += (weight * 1.0);
22
22
  }
23
23
  nextScore.theta = nextScore.alpha / (nextScore.alpha + nextScore.beta);
24
+ nextScore.uncertainty = BayesianScoreModel.getUncertainty(nextScore);
24
25
  return nextScore;
25
26
  }
27
+ /**
28
+ * Calculate uncertainty for a given Bayesian score.
29
+ * Higher values indicate less confidence in the theta estimate.
30
+ */
31
+ static getUncertainty(score) {
32
+ return 1 / (score.alpha + score.beta + 1);
33
+ }
34
+ /**
35
+ * Calculate confidence interval for theta estimate.
36
+ * Uses properties of the Beta distribution to estimate bounds.
37
+ */
38
+ static getConfidenceInterval(score, confidence = 0.95) {
39
+ // Calculate variance of Beta distribution
40
+ const variance = (score.alpha * score.beta) /
41
+ ((score.alpha + score.beta) ** 2 * (score.alpha + score.beta + 1));
42
+ const stdDev = Math.sqrt(variance);
43
+ // Z-scores for confidence levels
44
+ const z = confidence === 0.95 ? 1.96 : confidence === 0.99 ? 2.58 : 1.96;
45
+ return {
46
+ lower: Math.max(0, score.theta - z * stdDev),
47
+ upper: Math.min(1, score.theta + z * stdDev)
48
+ };
49
+ }
50
+ /**
51
+ * Check if we have high confidence in this score (low uncertainty).
52
+ */
53
+ static isHighConfidence(score, threshold = 0.1) {
54
+ return score.uncertainty < threshold;
55
+ }
26
56
  }
27
57
  exports.BayesianScoreModel = BayesianScoreModel;
@@ -6,14 +6,14 @@ export interface PhraseExample {
6
6
  text: string;
7
7
  pinyin: string;
8
8
  translation: string;
9
- learn_rank: number;
9
+ difficulty: number;
10
10
  sense_rank: number;
11
11
  }
12
12
  export interface Phrase {
13
13
  text: string;
14
14
  hanzi_count: number;
15
15
  sense_rank: number;
16
- learn_rank: number;
16
+ difficulty: number;
17
17
  pinyin: string;
18
18
  pinyin_tokenized: string;
19
19
  transliteration: string;
@@ -1,16 +1,21 @@
1
1
  import { Doc } from '@shaxpir/sharedb/lib/client';
2
2
  import { CompactDateTime } from "@shaxpir/shaxpir-common";
3
3
  import { ShareSync } from '../repo';
4
- import { BayesianScore } from './BayesianScore';
4
+ import { BayesianScore, Bounds } from './BayesianScore';
5
5
  import { Content, ContentBody, ContentId, ContentMeta } from "./Content";
6
6
  export interface PhraseProgress extends BayesianScore {
7
7
  text: string;
8
8
  sense_rank: number;
9
- learn_rank: number;
9
+ difficulty: number;
10
10
  last_review_utc?: CompactDateTime;
11
11
  }
12
+ export interface SkillLevel {
13
+ value: number;
14
+ uncertainty: number;
15
+ bounds: Bounds;
16
+ }
12
17
  export interface ProgressPayload {
13
- user_rank: number;
18
+ skill_level: SkillLevel;
14
19
  phrases: Record<string, PhraseProgress>;
15
20
  cognitive_load: number;
16
21
  }
@@ -23,10 +28,15 @@ export declare class Progress extends Content {
23
28
  static create(userId: ContentId): Progress;
24
29
  constructor(doc: Doc, shouldAcquire: boolean, shareSync: ShareSync);
25
30
  get payload(): ProgressPayload;
26
- getUserRank(): number;
31
+ getSkillLevel(): SkillLevel;
32
+ getSkillLevelValue(): number;
33
+ getSkillLevelUncertainty(): number;
34
+ getSkillLevelBounds(): Bounds;
35
+ needsCalibration(): boolean;
27
36
  getCognitiveLoad(): number;
28
37
  getPhrases(): Record<string, PhraseProgress>;
29
- setUserRank(value: number): void;
38
+ setSkillLevel(skillLevel: SkillLevel): void;
39
+ setSkillLevelValue(value: number): void;
30
40
  setCognitiveLoad(value: number): void;
31
41
  updatePhraseProgress(phraseId: string, progress: PhraseProgress): void;
32
42
  updateMultiplePhrases(updates: Record<string, PhraseProgress>): void;
@@ -23,7 +23,14 @@ class Progress extends Content_1.Content {
23
23
  updated_at: now
24
24
  },
25
25
  payload: {
26
- user_rank: 0,
26
+ skill_level: {
27
+ value: 0, // Start at absolute beginner
28
+ uncertainty: 1000, // Maximum uncertainty
29
+ bounds: {
30
+ lower: 0, // Can't be worse than absolute beginner
31
+ upper: 1000 // Could potentially be advanced
32
+ }
33
+ },
27
34
  phrases: {},
28
35
  cognitive_load: 0
29
36
  }
@@ -36,9 +43,26 @@ class Progress extends Content_1.Content {
36
43
  this.checkDisposed("Progress.payload");
37
44
  return this.doc.data.payload;
38
45
  }
39
- getUserRank() {
40
- this.checkDisposed("Progress.getUserRank");
41
- return this.payload.user_rank;
46
+ getSkillLevel() {
47
+ this.checkDisposed("Progress.getSkillLevel");
48
+ return this.payload.skill_level;
49
+ }
50
+ getSkillLevelValue() {
51
+ this.checkDisposed("Progress.getSkillLevelValue");
52
+ return this.payload.skill_level.value;
53
+ }
54
+ getSkillLevelUncertainty() {
55
+ this.checkDisposed("Progress.getSkillLevelUncertainty");
56
+ return this.payload.skill_level.uncertainty;
57
+ }
58
+ getSkillLevelBounds() {
59
+ this.checkDisposed("Progress.getSkillLevelBounds");
60
+ return this.payload.skill_level.bounds;
61
+ }
62
+ needsCalibration() {
63
+ this.checkDisposed("Progress.needsCalibration");
64
+ // High uncertainty means we need more data to calibrate user level
65
+ return this.payload.skill_level.uncertainty > 50;
42
66
  }
43
67
  getCognitiveLoad() {
44
68
  this.checkDisposed("Progress.getCognitiveLoad");
@@ -48,11 +72,17 @@ class Progress extends Content_1.Content {
48
72
  this.checkDisposed("Progress.getPhrases");
49
73
  return this.payload.phrases;
50
74
  }
51
- setUserRank(value) {
52
- this.checkDisposed("Progress.setUserRank");
53
- if (this.payload.user_rank !== value) {
75
+ setSkillLevel(skillLevel) {
76
+ this.checkDisposed("Progress.setSkillLevel");
77
+ const batch = new Operation_1.BatchOperation(this);
78
+ batch.setPathValue(['payload', 'skill_level'], skillLevel);
79
+ batch.commit();
80
+ }
81
+ setSkillLevelValue(value) {
82
+ this.checkDisposed("Progress.setSkillLevelValue");
83
+ if (this.payload.skill_level.value !== value) {
54
84
  const batch = new Operation_1.BatchOperation(this);
55
- batch.setPathValue(['payload', 'user_rank'], value);
85
+ batch.setPathValue(['payload', 'skill_level', 'value'], value);
56
86
  batch.commit();
57
87
  }
58
88
  }
@@ -8,7 +8,7 @@ import { ShareSync } from '../repo';
8
8
  export interface TermPayload extends BayesianScore {
9
9
  text: string;
10
10
  sense_rank: number;
11
- learn_rank: number;
11
+ difficulty: number;
12
12
  phrase_id: number | null;
13
13
  starred_at: CompactDateTime | null;
14
14
  hanzi_count?: number;
@@ -31,13 +31,13 @@ export declare class Term extends Content {
31
31
  static makeTermId(userId: ContentId, text: string, senseRank: number): ContentId;
32
32
  private _reviewsView;
33
33
  constructor(doc: Doc, shouldAcquire: boolean, shareSync: ShareSync);
34
- static forUserPhrase(userId: ContentId, text: string, senseRank: number, learnRank: number, phraseId?: number): Term;
34
+ static forUserPhrase(userId: ContentId, text: string, senseRank: number, difficulty: number, phraseId?: number): Term;
35
35
  static forUserPhrase(userId: ContentId, phrase: Phrase): Term;
36
36
  static forBuiltinPhrase(userId: ContentId, phrase: BuiltInPhrase): Term;
37
37
  get payload(): TermPayload;
38
38
  getText(): string;
39
39
  getSenseRank(): number;
40
- getLearnRank(): number;
40
+ getDifficulty(): number;
41
41
  getAlpha(): number;
42
42
  getBeta(): number;
43
43
  getTheta(): number;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Term = void 0;
4
4
  const shaxpir_common_1 = require("@shaxpir/shaxpir-common");
5
+ const BayesianScore_1 = require("./BayesianScore");
5
6
  const Content_1 = require("./Content");
6
7
  const ContentKind_1 = require("./ContentKind");
7
8
  const repo_1 = require("../repo");
@@ -15,7 +16,7 @@ class Term extends Content_1.Content {
15
16
  super(doc, shouldAcquire, shareSync);
16
17
  this._reviewsView = new ArrayView_1.ArrayView(this, ['payload', 'reviews']);
17
18
  }
18
- static forUserPhrase(userId, textOrPhrase, senseRank, learnRank, phraseId) {
19
+ static forUserPhrase(userId, textOrPhrase, senseRank, difficulty, phraseId) {
19
20
  const now = shaxpir_common_1.ClockService.getClock().now();
20
21
  // Handle overloaded parameters
21
22
  let phrase;
@@ -24,7 +25,7 @@ class Term extends Content_1.Content {
24
25
  phrase = {
25
26
  text: textOrPhrase,
26
27
  sense_rank: senseRank,
27
- learn_rank: learnRank,
28
+ difficulty: difficulty,
28
29
  hanzi_count: textOrPhrase.length,
29
30
  pinyin: '',
30
31
  pinyin_tokenized: '',
@@ -54,7 +55,7 @@ class Term extends Content_1.Content {
54
55
  text: phrase.text,
55
56
  sense_rank: phrase.sense_rank,
56
57
  hanzi_count: phrase.hanzi_count,
57
- learn_rank: phrase.learn_rank,
58
+ difficulty: phrase.difficulty,
58
59
  pinyin: phrase.pinyin,
59
60
  pinyin_tokenized: phrase.pinyin_tokenized,
60
61
  transliteration: phrase.transliteration,
@@ -67,6 +68,7 @@ class Term extends Content_1.Content {
67
68
  alpha: 0.5,
68
69
  beta: 0.5,
69
70
  theta: 0.5,
71
+ uncertainty: BayesianScore_1.BayesianScoreModel.getUncertainty({ alpha: 0.5, beta: 0.5, theta: 0.5, uncertainty: 0 }),
70
72
  reviews: []
71
73
  }
72
74
  });
@@ -86,13 +88,14 @@ class Term extends Content_1.Content {
86
88
  payload: {
87
89
  text: phrase.text,
88
90
  sense_rank: phrase.sense_rank,
89
- learn_rank: phrase.learn_rank,
91
+ difficulty: phrase.difficulty,
90
92
  phrase_id: phrase.id,
91
93
  starred_at: null,
92
94
  tags: [],
93
95
  alpha: 1,
94
96
  beta: 1,
95
97
  theta: 0.5,
98
+ uncertainty: BayesianScore_1.BayesianScoreModel.getUncertainty({ alpha: 1, beta: 1, theta: 0.5, uncertainty: 0 }),
96
99
  reviews: []
97
100
  }
98
101
  });
@@ -109,9 +112,9 @@ class Term extends Content_1.Content {
109
112
  this.checkDisposed("Term.getSenseRank");
110
113
  return this.payload.sense_rank;
111
114
  }
112
- getLearnRank() {
113
- this.checkDisposed("Term.getLearnRank");
114
- return this.payload.learn_rank;
115
+ getDifficulty() {
116
+ this.checkDisposed("Term.getDifficulty");
117
+ return this.payload.difficulty;
115
118
  }
116
119
  getAlpha() {
117
120
  this.checkDisposed("Term.getAlpha");
@@ -147,6 +150,7 @@ class Term extends Content_1.Content {
147
150
  batch.setPathValue(['payload', 'alpha'], alpha);
148
151
  batch.setPathValue(['payload', 'beta'], beta);
149
152
  batch.setPathValue(['payload', 'theta'], theta);
153
+ batch.setPathValue(['payload', 'uncertainty'], BayesianScore_1.BayesianScoreModel.getUncertainty({ alpha, beta, theta, uncertainty: 0 }));
150
154
  batch.commit();
151
155
  }
152
156
  addReview(review) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shaxpir/duiduidui-models",
3
- "version": "1.4.26",
3
+ "version": "1.4.27",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/shaxpir/duiduidui-models"