@shaxpir/duiduidui-models 1.7.2 → 1.7.4
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/Metric.d.ts +1 -1
- package/dist/models/Metric.js +9 -1
- package/dist/models/Progress.d.ts +3 -0
- package/dist/models/Progress.js +14 -1
- package/dist/models/Session.d.ts +8 -8
- package/dist/models/Session.js +33 -16
- package/dist/models/Streaks.d.ts +14 -0
- package/dist/models/Streaks.js +165 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +1 -0
- package/package.json +1 -1
package/dist/models/Metric.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export declare class Metric extends Content {
|
|
|
34
34
|
get maxDate(): CompactDate;
|
|
35
35
|
get length(): number;
|
|
36
36
|
get(index: number): MetricEntry;
|
|
37
|
-
sumBetween(minDate
|
|
37
|
+
sumBetween(minDate?: CompactDate | null, maxDate?: CompactDate | null): number;
|
|
38
38
|
forDate(date: CompactDate): number;
|
|
39
39
|
nonZeroDayCount(): number;
|
|
40
40
|
setDateAmount(date: CompactDate, amount: number): void;
|
package/dist/models/Metric.js
CHANGED
|
@@ -97,8 +97,16 @@ class Metric extends Content_1.Content {
|
|
|
97
97
|
}
|
|
98
98
|
sumBetween(minDate, maxDate) {
|
|
99
99
|
this.checkDisposed("Metric.sumBetween");
|
|
100
|
+
// Use payload min/max dates as defaults for null parameters
|
|
101
|
+
const effectiveMinDate = minDate || this.payload.min_date;
|
|
102
|
+
const effectiveMaxDate = maxDate || this.payload.max_date;
|
|
103
|
+
// If we still don't have dates (empty metric), return 0
|
|
104
|
+
if (!effectiveMinDate || !effectiveMaxDate) {
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
// Sum between the effective dates
|
|
100
108
|
let sum = 0;
|
|
101
|
-
const dates = shaxpir_common_1.Time.datesBetween(
|
|
109
|
+
const dates = shaxpir_common_1.Time.datesBetween(effectiveMinDate, effectiveMaxDate);
|
|
102
110
|
for (let i = 0, len = dates.length; i < len; i++) {
|
|
103
111
|
const date = dates[i];
|
|
104
112
|
sum += this.forDate(date);
|
|
@@ -30,6 +30,7 @@ export interface ProgressPayload {
|
|
|
30
30
|
skill_level: UncertainValue;
|
|
31
31
|
cognitive_load: number;
|
|
32
32
|
streaks?: StreakData;
|
|
33
|
+
total_review_count: number;
|
|
33
34
|
}
|
|
34
35
|
export interface ProgressBody extends ContentBody {
|
|
35
36
|
meta: ContentMeta;
|
|
@@ -55,4 +56,6 @@ export declare class Progress extends Content {
|
|
|
55
56
|
setCognitiveLoad(value: number): void;
|
|
56
57
|
getStreaks(): StreakData | undefined;
|
|
57
58
|
setStreaks(streaks: StreakData): void;
|
|
59
|
+
getTotalReviewCount(): number;
|
|
60
|
+
setTotalReviewCount(count: number): void;
|
|
58
61
|
}
|
package/dist/models/Progress.js
CHANGED
|
@@ -31,7 +31,8 @@ class Progress extends Content_1.Content {
|
|
|
31
31
|
upper: 1000 // Could potentially be advanced
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
|
-
cognitive_load: 0
|
|
34
|
+
cognitive_load: 0,
|
|
35
|
+
total_review_count: 0 // Start with zero lifetime reviews
|
|
35
36
|
}
|
|
36
37
|
});
|
|
37
38
|
}
|
|
@@ -134,5 +135,17 @@ class Progress extends Content_1.Content {
|
|
|
134
135
|
batch.commit();
|
|
135
136
|
}
|
|
136
137
|
}
|
|
138
|
+
getTotalReviewCount() {
|
|
139
|
+
this.checkDisposed("Progress.getTotalReviewCount");
|
|
140
|
+
return this.payload.total_review_count;
|
|
141
|
+
}
|
|
142
|
+
setTotalReviewCount(count) {
|
|
143
|
+
this.checkDisposed("Progress.setTotalReviewCount");
|
|
144
|
+
if (this.payload.total_review_count !== count) {
|
|
145
|
+
const batch = new Operation_1.BatchOperation(this);
|
|
146
|
+
batch.setPathValue(['payload', 'total_review_count'], count);
|
|
147
|
+
batch.commit();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
137
150
|
}
|
|
138
151
|
exports.Progress = Progress;
|
package/dist/models/Session.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export interface SessionConfig {
|
|
|
23
23
|
auto_play_speech?: boolean;
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
export interface
|
|
26
|
+
export interface SessionStats {
|
|
27
27
|
skill_level: UncertainValue;
|
|
28
28
|
cognitive_load: number;
|
|
29
29
|
theta_distribution: {
|
|
@@ -58,9 +58,9 @@ export interface SessionPayload {
|
|
|
58
58
|
is_complete: boolean;
|
|
59
59
|
app_version?: string;
|
|
60
60
|
config?: SessionConfig;
|
|
61
|
-
|
|
62
|
-
before?:
|
|
63
|
-
after?:
|
|
61
|
+
stats?: {
|
|
62
|
+
before?: SessionStats;
|
|
63
|
+
after?: SessionStats;
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
export interface SessionBody extends ContentBody {
|
|
@@ -87,10 +87,10 @@ export declare class Session extends Content {
|
|
|
87
87
|
setConfig(config: SessionConfig): void;
|
|
88
88
|
get isComplete(): boolean;
|
|
89
89
|
markComplete(): void;
|
|
90
|
-
get
|
|
91
|
-
|
|
92
|
-
get
|
|
93
|
-
|
|
90
|
+
get statsBefore(): SessionStats | undefined;
|
|
91
|
+
setStatsBefore(stats: SessionStats): void;
|
|
92
|
+
get statsAfter(): SessionStats | undefined;
|
|
93
|
+
setStatsAfter(stats: SessionStats): void;
|
|
94
94
|
isActive(): boolean;
|
|
95
95
|
get appVersion(): string | undefined;
|
|
96
96
|
setAppVersion(version: string): void;
|
package/dist/models/Session.js
CHANGED
|
@@ -8,6 +8,8 @@ const Content_1 = require("./Content");
|
|
|
8
8
|
const ContentKind_1 = require("./ContentKind");
|
|
9
9
|
const Metric_1 = require("./Metric");
|
|
10
10
|
const Operation_1 = require("./Operation");
|
|
11
|
+
const Progress_1 = require("./Progress");
|
|
12
|
+
const Streaks_1 = require("./Streaks");
|
|
11
13
|
const Workspace_1 = require("./Workspace");
|
|
12
14
|
class Session extends Content_1.Content {
|
|
13
15
|
constructor(doc, shouldAcquire, shareSync) {
|
|
@@ -128,30 +130,30 @@ class Session extends Content_1.Content {
|
|
|
128
130
|
batch.setPathValue(['payload', 'is_complete'], true);
|
|
129
131
|
batch.commit();
|
|
130
132
|
}
|
|
131
|
-
get
|
|
132
|
-
this.checkDisposed("Session.
|
|
133
|
-
return this.payload.
|
|
133
|
+
get statsBefore() {
|
|
134
|
+
this.checkDisposed("Session.statsBefore");
|
|
135
|
+
return this.payload.stats?.before;
|
|
134
136
|
}
|
|
135
|
-
|
|
136
|
-
this.checkDisposed("Session.
|
|
137
|
+
setStatsBefore(stats) {
|
|
138
|
+
this.checkDisposed("Session.setStatsBefore");
|
|
137
139
|
const batch = new Operation_1.BatchOperation(this);
|
|
138
|
-
if (!this.payload.
|
|
139
|
-
batch.setPathValue(['payload', '
|
|
140
|
+
if (!this.payload.stats) {
|
|
141
|
+
batch.setPathValue(['payload', 'stats'], {});
|
|
140
142
|
}
|
|
141
|
-
batch.setPathValue(['payload', '
|
|
143
|
+
batch.setPathValue(['payload', 'stats', 'before'], stats);
|
|
142
144
|
batch.commit();
|
|
143
145
|
}
|
|
144
|
-
get
|
|
145
|
-
this.checkDisposed("Session.
|
|
146
|
-
return this.payload.
|
|
146
|
+
get statsAfter() {
|
|
147
|
+
this.checkDisposed("Session.statsAfter");
|
|
148
|
+
return this.payload.stats?.after;
|
|
147
149
|
}
|
|
148
|
-
|
|
149
|
-
this.checkDisposed("Session.
|
|
150
|
+
setStatsAfter(stats) {
|
|
151
|
+
this.checkDisposed("Session.setStatsAfter");
|
|
150
152
|
const batch = new Operation_1.BatchOperation(this);
|
|
151
|
-
if (!this.payload.
|
|
152
|
-
batch.setPathValue(['payload', '
|
|
153
|
+
if (!this.payload.stats) {
|
|
154
|
+
batch.setPathValue(['payload', 'stats'], {});
|
|
153
155
|
}
|
|
154
|
-
batch.setPathValue(['payload', '
|
|
156
|
+
batch.setPathValue(['payload', 'stats', 'after'], stats);
|
|
155
157
|
batch.commit();
|
|
156
158
|
}
|
|
157
159
|
isActive() {
|
|
@@ -201,7 +203,22 @@ class Session extends Content_1.Content {
|
|
|
201
203
|
const reviewCountMetricId = Metric_1.Metric.makeMetricId(userId, Metric_1.MetricName.REVIEW_COUNT);
|
|
202
204
|
const reviewCountMetric = await shareSync.acquire(ContentKind_1.ContentKind.METRIC, reviewCountMetricId);
|
|
203
205
|
reviewCountMetric.setDateAmount(date, reviewCountOnDate);
|
|
206
|
+
// Calculate total review count using sumBetween with null arguments (sums all entries)
|
|
207
|
+
const totalReviewCount = reviewCountMetric.sumBetween(null, null);
|
|
204
208
|
reviewCountMetric.release();
|
|
209
|
+
// Update total review count in Progress
|
|
210
|
+
const progressId = Progress_1.Progress.makeProgressId(userId);
|
|
211
|
+
const progress = await shareSync.acquire(ContentKind_1.ContentKind.PROGRESS, progressId);
|
|
212
|
+
progress.setTotalReviewCount(totalReviewCount);
|
|
213
|
+
// Calculate streaks based on metrics
|
|
214
|
+
const now = shaxpir_common_1.ClockService.getClock().now();
|
|
215
|
+
const today = shaxpir_common_1.Time.dateFrom(now.local_time);
|
|
216
|
+
// Use the Streaks class to calculate streak data
|
|
217
|
+
const streakData = Streaks_1.Streaks.calculateFromMetric(minutesStudyingMetric, today);
|
|
218
|
+
if (streakData) {
|
|
219
|
+
progress.setStreaks(streakData);
|
|
220
|
+
}
|
|
221
|
+
progress.release();
|
|
205
222
|
}
|
|
206
223
|
}
|
|
207
224
|
exports.Session = Session;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CompactDate } from "@shaxpir/shaxpir-common";
|
|
2
|
+
import { Metric } from './Metric';
|
|
3
|
+
import { StreakData } from './Progress';
|
|
4
|
+
export declare class Streaks {
|
|
5
|
+
/**
|
|
6
|
+
* Calculate streak data from a Metric containing activity dates.
|
|
7
|
+
* Returns daily, weekly, and monthly streak information.
|
|
8
|
+
*
|
|
9
|
+
* @param metric - The Metric model containing activity data
|
|
10
|
+
* @param today - The current date for streak calculation
|
|
11
|
+
* @returns StreakData object with streak information, or null if no activity
|
|
12
|
+
*/
|
|
13
|
+
static calculateFromMetric(metric: Metric, today: CompactDate): StreakData | null;
|
|
14
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Streaks = void 0;
|
|
4
|
+
const shaxpir_common_1 = require("@shaxpir/shaxpir-common");
|
|
5
|
+
class Streaks {
|
|
6
|
+
/**
|
|
7
|
+
* Calculate streak data from a Metric containing activity dates.
|
|
8
|
+
* Returns daily, weekly, and monthly streak information.
|
|
9
|
+
*
|
|
10
|
+
* @param metric - The Metric model containing activity data
|
|
11
|
+
* @param today - The current date for streak calculation
|
|
12
|
+
* @returns StreakData object with streak information, or null if no activity
|
|
13
|
+
*/
|
|
14
|
+
static calculateFromMetric(metric, today) {
|
|
15
|
+
const minDate = metric.minDate;
|
|
16
|
+
const maxDate = metric.maxDate;
|
|
17
|
+
if (!minDate || !maxDate) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
// Get all dates between min and max using Metric's datesBetween
|
|
21
|
+
const allDates = shaxpir_common_1.Time.datesBetween(minDate, maxDate);
|
|
22
|
+
const activityDates = [];
|
|
23
|
+
// Collect dates with actual activity
|
|
24
|
+
for (const date of allDates) {
|
|
25
|
+
if (metric.forDate(date) > 0) {
|
|
26
|
+
activityDates.push(date);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (activityDates.length === 0) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
// Calculate daily streak
|
|
33
|
+
let dailyCurrent = 0;
|
|
34
|
+
let dailyLongest = 0;
|
|
35
|
+
let tempStreak = 0;
|
|
36
|
+
let lastDate = null;
|
|
37
|
+
for (const date of activityDates) {
|
|
38
|
+
if (!lastDate) {
|
|
39
|
+
tempStreak = 1;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Calculate days between dates
|
|
43
|
+
const datesBetween = shaxpir_common_1.Time.datesBetween(lastDate, date);
|
|
44
|
+
const daysBetween = datesBetween.length - 1; // Subtract 1 because datesBetween includes both dates
|
|
45
|
+
if (daysBetween === 1) {
|
|
46
|
+
tempStreak++;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Streak broken
|
|
50
|
+
dailyLongest = Math.max(dailyLongest, tempStreak);
|
|
51
|
+
tempStreak = 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
lastDate = date;
|
|
55
|
+
}
|
|
56
|
+
// Check if current streak is still active (includes today or yesterday)
|
|
57
|
+
if (lastDate) {
|
|
58
|
+
const datesSinceLastActivity = shaxpir_common_1.Time.datesBetween(lastDate, today);
|
|
59
|
+
const daysSinceLastActivity = datesSinceLastActivity.length - 1;
|
|
60
|
+
if (daysSinceLastActivity <= 1) {
|
|
61
|
+
dailyCurrent = tempStreak;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
dailyLongest = Math.max(dailyLongest, tempStreak);
|
|
65
|
+
// Calculate weekly streak (at least one activity per week)
|
|
66
|
+
let weeklyCurrent = 0;
|
|
67
|
+
let weeklyStreak = 0;
|
|
68
|
+
// Check for weekly continuity - if there's activity within each 7-day window
|
|
69
|
+
for (let i = activityDates.length - 1; i >= 0; i--) {
|
|
70
|
+
const currentDate = activityDates[i];
|
|
71
|
+
// For the most recent date, start counting
|
|
72
|
+
if (i === activityDates.length - 1) {
|
|
73
|
+
// Check if this was within the last 7 days
|
|
74
|
+
const daysFromToday = shaxpir_common_1.Time.datesBetween(currentDate, today).length - 1;
|
|
75
|
+
if (daysFromToday <= 7) {
|
|
76
|
+
weeklyStreak = 1;
|
|
77
|
+
weeklyCurrent = 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// Check if there's a gap of more than 7 days
|
|
82
|
+
const nextDate = activityDates[i + 1];
|
|
83
|
+
const daysBetween = shaxpir_common_1.Time.datesBetween(currentDate, nextDate).length - 1;
|
|
84
|
+
if (daysBetween <= 7) {
|
|
85
|
+
weeklyStreak++;
|
|
86
|
+
if (weeklyCurrent > 0) {
|
|
87
|
+
weeklyCurrent = weeklyStreak;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Weekly streak broken
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Calculate monthly streak
|
|
97
|
+
// Extract YYYY-MM from CompactDate (format: YYYY-MM-DD)
|
|
98
|
+
const monthlyActivity = new Set();
|
|
99
|
+
for (const date of activityDates) {
|
|
100
|
+
const month = date.substring(0, 7); // Extract YYYY-MM
|
|
101
|
+
monthlyActivity.add(month);
|
|
102
|
+
}
|
|
103
|
+
const sortedMonths = Array.from(monthlyActivity).sort();
|
|
104
|
+
let monthlyCurrent = 0;
|
|
105
|
+
let monthlyStreak = 0;
|
|
106
|
+
let lastMonth = null;
|
|
107
|
+
for (const month of sortedMonths) {
|
|
108
|
+
if (!lastMonth) {
|
|
109
|
+
monthlyStreak = 1;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// Check if consecutive month
|
|
113
|
+
const [lastYear, lastMonthNum] = lastMonth.split('-').map(Number);
|
|
114
|
+
const [currentYear, currentMonthNum] = month.split('-').map(Number);
|
|
115
|
+
if ((currentYear === lastYear && currentMonthNum === lastMonthNum + 1) ||
|
|
116
|
+
(currentYear === lastYear + 1 && lastMonthNum === 12 && currentMonthNum === 1)) {
|
|
117
|
+
monthlyStreak++;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
monthlyStreak = 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
lastMonth = month;
|
|
124
|
+
}
|
|
125
|
+
// Check if current month streak is active
|
|
126
|
+
const currentMonth = today.substring(0, 7); // Extract YYYY-MM
|
|
127
|
+
const lastActiveMonth = sortedMonths[sortedMonths.length - 1];
|
|
128
|
+
const lastMonthDate = shaxpir_common_1.Time.plus(today, -30, 'days');
|
|
129
|
+
const previousMonth = shaxpir_common_1.Time.dateFrom(lastMonthDate).substring(0, 7);
|
|
130
|
+
if (lastActiveMonth === currentMonth || lastActiveMonth === previousMonth) {
|
|
131
|
+
monthlyCurrent = monthlyStreak;
|
|
132
|
+
}
|
|
133
|
+
// Get week string for last activity (ISO week format)
|
|
134
|
+
const getWeekString = (date) => {
|
|
135
|
+
// Simple week calculation: YYYY-Www where ww is week of year
|
|
136
|
+
const [year, month, day] = date.split('-').map(Number);
|
|
137
|
+
const dateObj = new Date(year, month - 1, day);
|
|
138
|
+
const startOfYear = new Date(year, 0, 1);
|
|
139
|
+
const dayOfYear = Math.floor((dateObj.getTime() - startOfYear.getTime()) / (24 * 60 * 60 * 1000)) + 1;
|
|
140
|
+
const weekNumber = Math.ceil(dayOfYear / 7);
|
|
141
|
+
return `${year}-W${weekNumber.toString().padStart(2, '0')}`;
|
|
142
|
+
};
|
|
143
|
+
// We know lastDate has a value since activityDates.length > 0
|
|
144
|
+
const finalLastDate = lastDate || activityDates[activityDates.length - 1];
|
|
145
|
+
return {
|
|
146
|
+
daily: {
|
|
147
|
+
current: dailyCurrent,
|
|
148
|
+
longest: dailyLongest
|
|
149
|
+
},
|
|
150
|
+
weekly: {
|
|
151
|
+
current: weeklyCurrent
|
|
152
|
+
},
|
|
153
|
+
monthly: {
|
|
154
|
+
current: monthlyCurrent
|
|
155
|
+
},
|
|
156
|
+
last_activity: {
|
|
157
|
+
date: finalLastDate,
|
|
158
|
+
week: getWeekString(finalLastDate),
|
|
159
|
+
month: finalLastDate.substring(0, 7)
|
|
160
|
+
},
|
|
161
|
+
total_days: activityDates.length
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.Streaks = Streaks;
|
package/dist/models/index.d.ts
CHANGED
package/dist/models/index.js
CHANGED
|
@@ -38,6 +38,7 @@ __exportStar(require("./Progress"), exports);
|
|
|
38
38
|
__exportStar(require("./Review"), exports);
|
|
39
39
|
__exportStar(require("./Session"), exports);
|
|
40
40
|
__exportStar(require("./Social"), exports);
|
|
41
|
+
__exportStar(require("./Streaks"), exports);
|
|
41
42
|
__exportStar(require("./Term"), exports);
|
|
42
43
|
__exportStar(require("./User"), exports);
|
|
43
44
|
__exportStar(require("./Workspace"), exports);
|