@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.
- package/dist/models/BayesianScore.d.ts +19 -0
- package/dist/models/BayesianScore.js +30 -0
- package/dist/models/Phrase.d.ts +2 -2
- package/dist/models/Progress.d.ts +15 -5
- package/dist/models/Progress.js +38 -8
- package/dist/models/Term.d.ts +3 -3
- package/dist/models/Term.js +11 -7
- package/package.json +1 -1
|
@@ -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;
|
package/dist/models/Phrase.d.ts
CHANGED
|
@@ -6,14 +6,14 @@ export interface PhraseExample {
|
|
|
6
6
|
text: string;
|
|
7
7
|
pinyin: string;
|
|
8
8
|
translation: string;
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/models/Progress.js
CHANGED
|
@@ -23,7 +23,14 @@ class Progress extends Content_1.Content {
|
|
|
23
23
|
updated_at: now
|
|
24
24
|
},
|
|
25
25
|
payload: {
|
|
26
|
-
|
|
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
|
-
|
|
40
|
-
this.checkDisposed("Progress.
|
|
41
|
-
return this.payload.
|
|
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
|
-
|
|
52
|
-
this.checkDisposed("Progress.
|
|
53
|
-
|
|
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', '
|
|
85
|
+
batch.setPathValue(['payload', 'skill_level', 'value'], value);
|
|
56
86
|
batch.commit();
|
|
57
87
|
}
|
|
58
88
|
}
|
package/dist/models/Term.d.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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
|
-
|
|
40
|
+
getDifficulty(): number;
|
|
41
41
|
getAlpha(): number;
|
|
42
42
|
getBeta(): number;
|
|
43
43
|
getTheta(): number;
|
package/dist/models/Term.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
this.checkDisposed("Term.
|
|
114
|
-
return this.payload.
|
|
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) {
|