@readerseye2/cr_type 1.0.123 → 1.0.126
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.
|
@@ -4,11 +4,11 @@ export interface ReadRange {
|
|
|
4
4
|
to: number;
|
|
5
5
|
}
|
|
6
6
|
/** 캘리브레이션 종류 */
|
|
7
|
-
export type CalibrationType =
|
|
7
|
+
export type CalibrationType = "quick" | "full";
|
|
8
8
|
/** 섹션 읽기 중 발생한 캘리브레이션 기간 */
|
|
9
9
|
export interface CalibrationPeriod {
|
|
10
10
|
type: CalibrationType;
|
|
11
|
-
/** 사용한 포인트 수 (quick:
|
|
11
|
+
/** 사용한 포인트 수 (quick: 3, full: 5) */
|
|
12
12
|
points: number;
|
|
13
13
|
startedAt: string;
|
|
14
14
|
endedAt: string;
|
|
@@ -16,6 +16,32 @@ export interface CalibrationPeriod {
|
|
|
16
16
|
/** 캘리브레이션 결과 품질 (0~1, 기기 제공) */
|
|
17
17
|
quality?: number;
|
|
18
18
|
}
|
|
19
|
+
/** 퀴즈 시도 결과 (개별 퀴즈) */
|
|
20
|
+
export interface QuizAttemptResult {
|
|
21
|
+
quizId: string;
|
|
22
|
+
/** 획득 점수 */
|
|
23
|
+
score: number;
|
|
24
|
+
/** 만점 */
|
|
25
|
+
maxScore: number;
|
|
26
|
+
isCorrect: boolean;
|
|
27
|
+
/** 소요 시간 (ms) */
|
|
28
|
+
timeMs: number;
|
|
29
|
+
/** 타임아웃 여부 */
|
|
30
|
+
timedOut: boolean;
|
|
31
|
+
attemptedAt: string;
|
|
32
|
+
}
|
|
33
|
+
/** 찾아본 단어 기록 */
|
|
34
|
+
export interface LookedUpWord {
|
|
35
|
+
/** 원문 텍스트 */
|
|
36
|
+
text: string;
|
|
37
|
+
/** 시작 GI */
|
|
38
|
+
startGI: number;
|
|
39
|
+
/** 끝 GI */
|
|
40
|
+
endGI: number;
|
|
41
|
+
/** 번역 결과 (있으면) */
|
|
42
|
+
translatedText?: string;
|
|
43
|
+
lookedUpAt: string;
|
|
44
|
+
}
|
|
19
45
|
export interface ReadingProgressReport {
|
|
20
46
|
bookIdx: number;
|
|
21
47
|
sectionId: string;
|
|
@@ -34,8 +60,26 @@ export interface ReadingProgressReport {
|
|
|
34
60
|
* 예: { "101": 300, "102": 450, "103": 67 }
|
|
35
61
|
*/
|
|
36
62
|
gazeDwellMap: Record<string, number>;
|
|
37
|
-
/** 이 구간 내 눈 깜빡임 횟수 (
|
|
63
|
+
/** 이 구간 내 눈 깜빡임 횟수 (valid 전환 감지) */
|
|
38
64
|
blinkCount: number;
|
|
65
|
+
/**
|
|
66
|
+
* 이 구간에서 시선이 텍스트([data-g]) 위에 있었던 시간 (ms)
|
|
67
|
+
* → 집중도(concentration) 계산용: gazeOnTextMs / durationMs * 100
|
|
68
|
+
* → 시선 추적 없으면 0
|
|
69
|
+
*/
|
|
70
|
+
gazeOnTextMs: number;
|
|
71
|
+
/**
|
|
72
|
+
* 오디오 재생 중 시선이 현재 하이라이트 GI 근처에 있었던 시간 (ms)
|
|
73
|
+
* → 시선일치도(gazeAlignment) 계산용
|
|
74
|
+
* → 오디오 미재생 또는 시선추적 없으면 null
|
|
75
|
+
*/
|
|
76
|
+
gazeAlignedMs: number | null;
|
|
77
|
+
/** 오디오 재생 중 시선추적이 활성이었던 총 시간 (ms) */
|
|
78
|
+
audioGazeActiveMs: number | null;
|
|
79
|
+
/** 이 구간에서 찾아본 단어 */
|
|
80
|
+
lookedUpWords: LookedUpWord[];
|
|
81
|
+
/** 이 구간에서 완료한 퀴즈 */
|
|
82
|
+
quizResults: QuizAttemptResult[];
|
|
39
83
|
/** 소요 시간 ms */
|
|
40
84
|
durationMs: number;
|
|
41
85
|
}
|
|
@@ -44,16 +88,48 @@ export interface ChildSectionProgress {
|
|
|
44
88
|
bookIdx: number;
|
|
45
89
|
sectionId: string;
|
|
46
90
|
sectionGIMax: number;
|
|
91
|
+
/** 섹션 단어 수 (SectionSummary.word_count, WPM 계산용) */
|
|
92
|
+
sectionWordCount: number;
|
|
47
93
|
scrolledRanges: ReadRange[];
|
|
94
|
+
/** unique GI 수 (mergeReadRanges 후) */
|
|
48
95
|
scrolledCount: number;
|
|
96
|
+
/** scrolledCount / sectionGIMax */
|
|
49
97
|
scrolledCoverage: number;
|
|
50
98
|
gazeReadRanges: ReadRange[];
|
|
51
99
|
gazeReadCount: number;
|
|
52
100
|
gazeReadCoverage: number;
|
|
101
|
+
/** 모든 flush의 (scrolledTo - scrolledFrom + 1) 합산 */
|
|
102
|
+
totalRawScrolledGIs: number;
|
|
103
|
+
/** 모든 flush의 gaze dwell ≥ threshold GI 수 합산 */
|
|
104
|
+
totalRawGazeReadGIs: number;
|
|
53
105
|
totalReadMs: number;
|
|
54
106
|
lastGlobalIndex: number;
|
|
55
107
|
lastReadAt: string;
|
|
108
|
+
/** 검증된 읽기속도: gazeReadCount / (totalReadMs / 60000) — WPM 근사 (GI ≈ word) */
|
|
109
|
+
readingSpeedWPM: number | null;
|
|
110
|
+
/** 미검증 읽기속도: scrolledCount / (totalReadMs / 60000) */
|
|
111
|
+
unverifiedReadingSpeedWPM: number | null;
|
|
112
|
+
/** 누적: 시선이 텍스트 위에 있었던 시간 (ms) */
|
|
113
|
+
totalGazeOnTextMs: number;
|
|
114
|
+
/** 집중도: totalGazeOnTextMs / totalReadMs * 100 (%, 시선추적 없으면 null) */
|
|
115
|
+
concentrationPercent: number | null;
|
|
116
|
+
/** 누적: 오디오 재생 중 시선이 하이라이트 GI 근처에 있었던 시간 */
|
|
117
|
+
totalGazeAlignedMs: number;
|
|
118
|
+
/** 누적: 오디오 재생 중 시선추적이 활성이었던 시간 */
|
|
119
|
+
totalAudioGazeActiveMs: number;
|
|
120
|
+
/** 시선일치도: totalGazeAlignedMs / totalAudioGazeActiveMs (0~1, 데이터 없으면 null) */
|
|
121
|
+
gazeAlignmentScore: number | null;
|
|
56
122
|
totalBlinks: number;
|
|
123
|
+
/** 찾아본 단어 목록 (unique text 기준) */
|
|
124
|
+
lookedUpWords: LookedUpWord[];
|
|
125
|
+
quizScore: number;
|
|
126
|
+
quizMaxScore: number;
|
|
127
|
+
quizCorrectCount: number;
|
|
128
|
+
quizTotalCount: number;
|
|
129
|
+
/** quizCorrectCount / quizTotalCount (0~1, 퀴즈 없으면 null) */
|
|
130
|
+
quizAccuracy: number | null;
|
|
131
|
+
/** 누적 읽기 점수 (알고리즘 산출, 읽기마다 추가) */
|
|
132
|
+
readingScore: number;
|
|
57
133
|
calibrations: CalibrationPeriod[];
|
|
58
134
|
createdAt: string;
|
|
59
135
|
updatedAt: string;
|
|
@@ -68,6 +144,13 @@ export interface ChildBookBookmark {
|
|
|
68
144
|
totalScrolledCoverage: number;
|
|
69
145
|
totalGazeReadCount: number;
|
|
70
146
|
totalGazeReadCoverage: number;
|
|
147
|
+
totalReadingScore: number;
|
|
148
|
+
/** 책 전체 평균 WPM (검증된 속도) */
|
|
149
|
+
avgReadingSpeedWPM: number | null;
|
|
150
|
+
/** 책 전체 집중도 (%) */
|
|
151
|
+
avgConcentrationPercent: number | null;
|
|
152
|
+
/** 책 전체 퀴즈 정답률 */
|
|
153
|
+
totalQuizAccuracy: number | null;
|
|
71
154
|
createdAt: string;
|
|
72
155
|
updatedAt: string;
|
|
73
156
|
}
|
|
@@ -80,6 +163,15 @@ export interface ChildReadingLog {
|
|
|
80
163
|
/** 전체 dwell map 그대로 보존 */
|
|
81
164
|
gazeDwellMap: Record<string, number>;
|
|
82
165
|
blinkCount: number;
|
|
166
|
+
/** 시선이 텍스트 위에 있었던 시간 */
|
|
167
|
+
gazeOnTextMs: number;
|
|
168
|
+
/** 오디오 재생 중 시선 일치 시간 */
|
|
169
|
+
gazeAlignedMs: number | null;
|
|
170
|
+
audioGazeActiveMs: number | null;
|
|
171
|
+
/** 찾아본 단어 */
|
|
172
|
+
lookedUpWords: LookedUpWord[];
|
|
173
|
+
/** 퀴즈 결과 */
|
|
174
|
+
quizResults: QuizAttemptResult[];
|
|
83
175
|
durationMs: number;
|
|
84
176
|
createdAt: string;
|
|
85
177
|
}
|
|
@@ -102,6 +194,7 @@ export interface SegmentReadingSummary {
|
|
|
102
194
|
sectionId: string;
|
|
103
195
|
bookIdx: number;
|
|
104
196
|
sectionGIMax: number;
|
|
197
|
+
sectionWordCount: number;
|
|
105
198
|
startedAt: string;
|
|
106
199
|
endedAt: string;
|
|
107
200
|
durationMs: number;
|
|
@@ -109,15 +202,27 @@ export interface SegmentReadingSummary {
|
|
|
109
202
|
scrolledTo: number;
|
|
110
203
|
scrolledGICount: number;
|
|
111
204
|
gazeReadGICount: number;
|
|
205
|
+
rawScrolledGICount: number;
|
|
206
|
+
rawGazeReadGICount: number;
|
|
207
|
+
/** gazeReadGICount / (durationMs / 60000) — WPM 근사 */
|
|
208
|
+
readingSpeedWPM: number | null;
|
|
209
|
+
/** scrolledGICount / (durationMs / 60000) */
|
|
210
|
+
unverifiedReadingSpeedWPM: number;
|
|
112
211
|
/** gazeReadGICount / scrolledGICount (시선 없으면 null) */
|
|
113
212
|
focusRatio: number | null;
|
|
114
|
-
/** 평균 읽기 속도 (GI per minute) */
|
|
115
|
-
readingSpeedGPM: number;
|
|
116
213
|
/** 평균 dwell time (ms per GI) */
|
|
117
214
|
avgDwellMs: number | null;
|
|
215
|
+
/** 집중도: gazeOnTextMs / durationMs * 100 */
|
|
216
|
+
concentrationPercent: number | null;
|
|
217
|
+
/** 시선일치도 (0~1, 오디오+시선추적 시에만) */
|
|
218
|
+
gazeAlignmentScore: number | null;
|
|
118
219
|
totalBlinks: number;
|
|
119
220
|
/** 분당 깜빡임 횟수 */
|
|
120
221
|
blinksPerMinute: number;
|
|
222
|
+
lookedUpWordCount: number;
|
|
223
|
+
quizScore: number;
|
|
224
|
+
quizMaxScore: number;
|
|
225
|
+
quizAccuracy: number | null;
|
|
121
226
|
calibrations: CalibrationPeriod[];
|
|
122
227
|
}
|
|
123
228
|
export interface ChildReadingSessionSummary {
|
|
@@ -130,9 +235,67 @@ export interface ChildReadingSessionSummary {
|
|
|
130
235
|
totalScrolledGICount: number;
|
|
131
236
|
totalGazeReadGICount: number;
|
|
132
237
|
overallFocusRatio: number | null;
|
|
133
|
-
|
|
238
|
+
avgReadingSpeedWPM: number | null;
|
|
239
|
+
avgUnverifiedReadingSpeedWPM: number;
|
|
240
|
+
overallConcentrationPercent: number | null;
|
|
241
|
+
overallGazeAlignmentScore: number | null;
|
|
134
242
|
totalBlinks: number;
|
|
135
243
|
avgBlinksPerMinute: number;
|
|
244
|
+
totalLookedUpWords: number;
|
|
245
|
+
totalQuizScore: number;
|
|
246
|
+
totalQuizMaxScore: number;
|
|
247
|
+
overallQuizAccuracy: number | null;
|
|
248
|
+
/** 이 세션에서 획득한 읽기 점수 */
|
|
249
|
+
sessionReadingScore: number;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* 읽기 점수 산출 입력값
|
|
253
|
+
* - 각 메트릭 가중치를 적용하여 종합 점수 산출
|
|
254
|
+
* - 점수는 누적 (읽을수록 올라감)
|
|
255
|
+
*/
|
|
256
|
+
export interface ReadingScoreInput {
|
|
257
|
+
/** 진도 (중첩비허용 coverage, 0~1) */
|
|
258
|
+
scrolledCoverage: number;
|
|
259
|
+
/** 검증된 진도 (gazeRead coverage, 0~1) */
|
|
260
|
+
gazeReadCoverage: number;
|
|
261
|
+
/** 집중도 (%, 0~100) */
|
|
262
|
+
concentrationPercent: number | null;
|
|
263
|
+
/** 시선일치도 (0~1) */
|
|
264
|
+
gazeAlignmentScore: number | null;
|
|
265
|
+
/** 퀴즈 정답률 (0~1) */
|
|
266
|
+
quizAccuracy: number | null;
|
|
267
|
+
/** 읽기 시간 (ms) */
|
|
268
|
+
durationMs: number;
|
|
269
|
+
/** 찾아본 단어 수 */
|
|
270
|
+
lookedUpWordCount: number;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* 현재 섹션 읽기 중 클라이언트에서 실시간 누적하는 상태
|
|
274
|
+
* - readingProgress.service.ts 모듈 스코프에서 관리
|
|
275
|
+
* - 5초 flush마다 서버에 delta 전송 + 로컬 누적 갱신
|
|
276
|
+
* - 섹션 변경/세션 종료 시 리셋
|
|
277
|
+
*/
|
|
278
|
+
export interface ReadingAccumulatedState {
|
|
279
|
+
bookIdx: number;
|
|
280
|
+
sectionId: string;
|
|
281
|
+
sectionGIMax: number;
|
|
282
|
+
sectionWordCount: number;
|
|
283
|
+
scrolledRanges: ReadRange[];
|
|
284
|
+
scrolledCoverage: number;
|
|
285
|
+
totalRawScrolledGIs: number;
|
|
286
|
+
totalReadMs: number;
|
|
287
|
+
lastGlobalIndex: number;
|
|
288
|
+
totalGazeOnTextMs: number;
|
|
289
|
+
totalGazeAlignedMs: number;
|
|
290
|
+
totalAudioGazeActiveMs: number;
|
|
291
|
+
concentrationPercent: number | null;
|
|
292
|
+
gazeAlignmentScore: number | null;
|
|
293
|
+
totalBlinks: number;
|
|
294
|
+
lookedUpWords: LookedUpWord[];
|
|
295
|
+
quizResults: QuizAttemptResult[];
|
|
296
|
+
quizScore: number;
|
|
297
|
+
quizMaxScore: number;
|
|
298
|
+
estimatedReadingScore: number;
|
|
136
299
|
}
|
|
137
300
|
/** GET /api/reading-progress/:bookIdx */
|
|
138
301
|
export interface ChildBookProgressResponse {
|
|
@@ -144,6 +307,8 @@ export interface DailyReadingStat {
|
|
|
144
307
|
date: string;
|
|
145
308
|
totalMs: number;
|
|
146
309
|
books: number[];
|
|
310
|
+
/** 그 날 획득한 읽기 점수 */
|
|
311
|
+
readingScore: number;
|
|
147
312
|
}
|
|
148
313
|
/** GET /api/reading-progress/daily-stats */
|
|
149
314
|
export interface DailyReadingStatsResponse {
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// src/book/child-reading-progress.type.ts
|
|
3
3
|
// 자녀 읽기 진행 데이터 — MongoDB 5컬렉션 + WS/REST 타입
|
|
4
|
+
//
|
|
5
|
+
// 메트릭 개요:
|
|
6
|
+
// 누가(childIdx) 언제(timestamps) 무엇을(bookIdx+sectionId)
|
|
7
|
+
// 진도(coverage, 중첩비허용) 읽은양(raw, 중첩허용) 읽기속도(WPM)
|
|
8
|
+
// 시선일치도(gazeAlignment, 오디오재생+시선추적시)
|
|
9
|
+
// 집중도(concentration, gazeOnText/total %)
|
|
10
|
+
// 찾아본단어(lookedUpWords) 퀴즈점수/정답률
|
|
11
|
+
// 미검증 읽은양/속도(scrollOnly) 종합 읽기점수(readingScore, 누적)
|
|
4
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|