@more-ink/irt-edge 2.1.3 → 2.2.0
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/CHANGELOG.md +131 -108
- package/README.md +530 -526
- package/examples/sdk-usage.ts +131 -130
- package/package.json +53 -53
- package/sdk/client.d.ts +4 -0
- package/sdk/client.d.ts.map +1 -1
- package/sdk/client.js +9 -0
- package/sdk/client.js.map +1 -1
- package/sdk/index.d.ts +2 -0
- package/sdk/index.d.ts.map +1 -1
- package/sdk/index.js +2 -0
- package/sdk/index.js.map +1 -1
- package/sdk/types.d.ts +5 -0
- package/sdk/types.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -1,526 +1,530 @@
|
|
|
1
|
-
# @more-ink/irt-edge
|
|
2
|
-
|
|
3
|
-
**Two packages in one:**
|
|
4
|
-
1. **SDK** - Lightweight API client for JavaScript/TypeScript frontends (published to npm)
|
|
5
|
-
2. **Backend Server** - Production HTTP API server built with Hono for Aliyun Function Compute (not published)
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Table of Contents
|
|
10
|
-
|
|
11
|
-
- [SDK Usage (Frontend)](#sdk-usage-frontend)
|
|
12
|
-
- [Installation](#installation)
|
|
13
|
-
- [Quick Start](#quick-start)
|
|
14
|
-
- [Configuration](#configuration)
|
|
15
|
-
- [API Methods](#api-methods)
|
|
16
|
-
- [Error Handling](#error-handling)
|
|
17
|
-
- [Best Practices](#best-practices)
|
|
18
|
-
- [Backend Server Setup](#backend-server-setup)
|
|
19
|
-
- [Backend Deployment](#backend-deployment-aliyun-fc)
|
|
20
|
-
- [SDK Publishing](#sdk-publishing-npm)
|
|
21
|
-
- [Package Scripts](#package-scripts)
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## SDK Usage (Frontend)
|
|
26
|
-
|
|
27
|
-
### Installation
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npm install @more-ink/irt-edge @more-ink/irt-core
|
|
31
|
-
# or
|
|
32
|
-
yarn add @more-ink/irt-edge @more-ink/irt-core
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
**Note:** `@more-ink/irt-core` is a peer dependency that provides type definitions.
|
|
36
|
-
|
|
37
|
-
### Quick Start
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
import { IrtClient } from '@more-ink/irt-edge'
|
|
41
|
-
|
|
42
|
-
const client = new IrtClient({
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
console.log('
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
```typescript
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
#
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
#
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
#
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
#
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
- `POST /api/irt/
|
|
353
|
-
- `POST /api/irt/
|
|
354
|
-
- `POST /api/irt/
|
|
355
|
-
- `POST /api/irt/
|
|
356
|
-
- `
|
|
357
|
-
- `
|
|
358
|
-
- `
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
- `
|
|
362
|
-
- `GET /
|
|
363
|
-
|
|
364
|
-
**
|
|
365
|
-
- `
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
npm
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
npm run
|
|
424
|
-
|
|
425
|
-
# Or
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
-
|
|
439
|
-
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
-
|
|
455
|
-
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
-
|
|
460
|
-
- Check
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
- `npm run --workspace @more-ink/irt-edge
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
- `npm run --workspace @more-ink/irt-edge
|
|
493
|
-
- `npm run --workspace @more-ink/irt-edge
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
- `npm run --workspace @more-ink/irt-edge
|
|
498
|
-
- `npm run --workspace @more-ink/irt-edge
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
│ ├──
|
|
515
|
-
│ ├──
|
|
516
|
-
│
|
|
517
|
-
|
|
518
|
-
├──
|
|
519
|
-
├──
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
1
|
+
# @more-ink/irt-edge
|
|
2
|
+
|
|
3
|
+
**Two packages in one:**
|
|
4
|
+
1. **SDK** - Lightweight API client for JavaScript/TypeScript frontends (published to npm)
|
|
5
|
+
2. **Backend Server** - Production HTTP API server built with Hono for Aliyun Function Compute (not published)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [SDK Usage (Frontend)](#sdk-usage-frontend)
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Quick Start](#quick-start)
|
|
14
|
+
- [Configuration](#configuration)
|
|
15
|
+
- [API Methods](#api-methods)
|
|
16
|
+
- [Error Handling](#error-handling)
|
|
17
|
+
- [Best Practices](#best-practices)
|
|
18
|
+
- [Backend Server Setup](#backend-server-setup)
|
|
19
|
+
- [Backend Deployment](#backend-deployment-aliyun-fc)
|
|
20
|
+
- [SDK Publishing](#sdk-publishing-npm)
|
|
21
|
+
- [Package Scripts](#package-scripts)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## SDK Usage (Frontend)
|
|
26
|
+
|
|
27
|
+
### Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @more-ink/irt-edge @more-ink/irt-core
|
|
31
|
+
# or
|
|
32
|
+
yarn add @more-ink/irt-edge @more-ink/irt-core
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Note:** `@more-ink/irt-core` is a peer dependency that provides type definitions.
|
|
36
|
+
|
|
37
|
+
### Quick Start
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { IrtClient } from '@more-ink/irt-edge'
|
|
41
|
+
|
|
42
|
+
const client = new IrtClient({
|
|
43
|
+
namespace: 'exam-a',
|
|
44
|
+
baseUrl: 'https://your-api.example.com',
|
|
45
|
+
headers: {
|
|
46
|
+
'Authorization': 'Bearer YOUR_TOKEN' // Optional
|
|
47
|
+
},
|
|
48
|
+
timeout: 10000 // Optional (default: 10000ms)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Record an answer and get the next item
|
|
52
|
+
const result = await client.recordAnswer({
|
|
53
|
+
userId: 'user123',
|
|
54
|
+
skillId: 'math',
|
|
55
|
+
itemId: 'item456',
|
|
56
|
+
score: 0.8,
|
|
57
|
+
timestamp: Date.now()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
console.log('Updated ability:', result.theta)
|
|
61
|
+
console.log('Next item:', result.nextItem)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Configuration
|
|
65
|
+
|
|
66
|
+
#### IrtClientConfig
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
interface IrtClientConfig {
|
|
70
|
+
namespace: string // Required namespace (exam type)
|
|
71
|
+
baseUrl: string // API server base URL (required)
|
|
72
|
+
headers?: Record<string, string> // Custom headers (e.g., auth tokens)
|
|
73
|
+
timeout?: number // Request timeout in ms (default: 10000)
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The SDK sends the namespace as `X-IRT-Namespace` on every request; the server rejects calls without it.
|
|
78
|
+
|
|
79
|
+
### API Methods
|
|
80
|
+
|
|
81
|
+
#### recordAnswer()
|
|
82
|
+
|
|
83
|
+
Record a user's response and get the next recommended item.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
async recordAnswer(params: RecordAnswerRequest): Promise<RecordAnswerResponse>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Parameters:**
|
|
90
|
+
```typescript
|
|
91
|
+
interface RecordAnswerRequest {
|
|
92
|
+
userId: string // User identifier
|
|
93
|
+
skillId: string // Skill identifier
|
|
94
|
+
itemId: string // Item identifier
|
|
95
|
+
score: number // Response score [0,1] (0=wrong, 1=correct)
|
|
96
|
+
timestamp: number // When response occurred (ms)
|
|
97
|
+
updateOptions?: Partial<UpdateOptions> // Override learning rates
|
|
98
|
+
selectionOptions?: Partial<NextItemOptions> // Override selection behavior
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Returns:** Updated ability (theta), standard error (se), next item, and updated states.
|
|
103
|
+
|
|
104
|
+
**Example:**
|
|
105
|
+
```typescript
|
|
106
|
+
const result = await client.recordAnswer({
|
|
107
|
+
userId: 'alice',
|
|
108
|
+
skillId: 'geometry',
|
|
109
|
+
itemId: 'q100',
|
|
110
|
+
score: 1.0,
|
|
111
|
+
timestamp: Date.now()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
console.log(`Ability: ${result.theta.toFixed(2)} ± ${result.se.toFixed(2)}`)
|
|
115
|
+
if (result.nextItem) {
|
|
116
|
+
console.log(`Next question: ${result.nextItem.id}`)
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### recordMultiSkillAnswers()
|
|
121
|
+
|
|
122
|
+
Record multiple skill scores that came from a single multi-skill item. This is useful when one interaction yields separate sub-skill scores (e.g., integrated tasks that grade both reading and writing).
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
async recordMultiSkillAnswers(
|
|
126
|
+
params: RecordMultiSkillAnswersRequest,
|
|
127
|
+
): Promise<RecordMultiSkillAnswersResponse>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Parameters:**
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
interface RecordMultiSkillAnswersRequest {
|
|
134
|
+
userId: string
|
|
135
|
+
itemId: string
|
|
136
|
+
timestamp?: number
|
|
137
|
+
updateOptions?: Partial<UpdateOptions>
|
|
138
|
+
skillScores: Array<{
|
|
139
|
+
skillId: string
|
|
140
|
+
score: number // [0,1]
|
|
141
|
+
updateOptions?: Partial<UpdateOptions>
|
|
142
|
+
}>
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Returns:** Array of per-skill updates with theta, SE, updated user/item snapshots.
|
|
147
|
+
|
|
148
|
+
**Example:**
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
const multi = await client.recordMultiSkillAnswers({
|
|
152
|
+
userId: 'alice',
|
|
153
|
+
itemId: 'essay-22',
|
|
154
|
+
skillScores: [
|
|
155
|
+
{ skillId: 'reading', score: 0.9 },
|
|
156
|
+
{ skillId: 'writing', score: 0.6 },
|
|
157
|
+
],
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
multi.results.forEach((entry) => {
|
|
161
|
+
console.log(entry.skillId, entry.theta, entry.se)
|
|
162
|
+
})
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### recordAnswerOnly()
|
|
166
|
+
|
|
167
|
+
Record a user's response without requesting the next recommended item (useful when you already control sequencing).
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
async recordAnswerOnly(params: RecordAnswerRequest): Promise<RecordAnswerOnlyResponse>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
await client.recordAnswerOnly({
|
|
175
|
+
userId: 'alice',
|
|
176
|
+
skillId: 'geometry',
|
|
177
|
+
itemId: 'q100',
|
|
178
|
+
score: 1.0,
|
|
179
|
+
timestamp: Date.now()
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### selectNextItem()
|
|
184
|
+
|
|
185
|
+
Get the next recommended item without recording a response.
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
async selectNextItem(params: SelectNextItemRequest): Promise<SelectNextItemResponse>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### resetUser() / resetItem()
|
|
192
|
+
|
|
193
|
+
Reset all Redis-backed skill state for a user or item.
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
async resetUser(userId: string): Promise<ResetUserResponse>
|
|
197
|
+
async resetItem(itemId: string): Promise<ResetItemResponse>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### bulkUpdateUser() / bulkUpdateItem()
|
|
201
|
+
|
|
202
|
+
Bulk cold-start/backfill helpers that update **only** user skills or **only** item parameters.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
async bulkUpdateUser(
|
|
206
|
+
userId: string,
|
|
207
|
+
events: BulkUpdateEvent[],
|
|
208
|
+
updateOptions?: Partial<UpdateOptions>
|
|
209
|
+
): Promise<BulkUpdateResponse>
|
|
210
|
+
|
|
211
|
+
async bulkUpdateItem(
|
|
212
|
+
itemId: string,
|
|
213
|
+
events: BulkUpdateEvent[],
|
|
214
|
+
updateOptions?: Partial<UpdateOptions>
|
|
215
|
+
): Promise<BulkUpdateResponse>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### getUserState() / getUserStates()
|
|
219
|
+
|
|
220
|
+
Get current user ability estimates.
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
async getUserState(userId: string, skillId: string): Promise<UserStateResponse>
|
|
224
|
+
async getUserStates(userId: string): Promise<UserStatesResponse>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### getItemState() / getItemStates()
|
|
228
|
+
|
|
229
|
+
Get item parameters and metadata.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
async getItemState(itemId: string, skillId: string): Promise<ItemStateResponse>
|
|
233
|
+
async getItemStates(itemId: string): Promise<ItemStatesResponse>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
#### deleteUser() / deleteItem()
|
|
237
|
+
|
|
238
|
+
Delete a user (and their skill states) or an item (and all per-skill calibrations). These are mainly for integration tests and fixture cleanup.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
async deleteUser(userId: string): Promise<DeleteUserResponse>
|
|
242
|
+
async deleteItem(itemId: string): Promise<DeleteItemResponse>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### health()
|
|
246
|
+
|
|
247
|
+
Check API server health status.
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
async health(): Promise<HealthResponse>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
See `examples/sdk-usage.ts` for complete working examples.
|
|
254
|
+
|
|
255
|
+
### Error Handling
|
|
256
|
+
|
|
257
|
+
All methods throw errors for network failures, HTTP errors, timeouts, and invalid responses.
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
try {
|
|
261
|
+
const result = await client.recordAnswer({...})
|
|
262
|
+
// Handle success
|
|
263
|
+
} catch (error) {
|
|
264
|
+
if (error instanceof Error) {
|
|
265
|
+
console.error('API error:', error.message)
|
|
266
|
+
if (error.message.includes('timeout')) {
|
|
267
|
+
// Retry logic
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Best Practices
|
|
274
|
+
|
|
275
|
+
1. **Reuse client instances** - Create one client and reuse it
|
|
276
|
+
2. **Use timestamps** - Always provide client-side timestamps for accurate analytics
|
|
277
|
+
3. **Handle errors gracefully** - Show user-friendly messages on errors
|
|
278
|
+
4. **Check for null items** - The API may return `null` if no suitable item is available
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
const result = await client.selectNextItem({...})
|
|
282
|
+
if (result.nextItem) {
|
|
283
|
+
// Show item to user
|
|
284
|
+
} else {
|
|
285
|
+
// No more items available
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Backend Server Setup
|
|
292
|
+
|
|
293
|
+
The backend server is **not published** to npm. It runs as a service on Aliyun Function Compute.
|
|
294
|
+
|
|
295
|
+
### Features
|
|
296
|
+
|
|
297
|
+
- RESTful IRT API backed by **Redis (online state)** and **Postgres via Prisma** (durable via `/api/irt/sync`)
|
|
298
|
+
- Aliyun FC lifecycle hooks (`/initialize`, `/pre-stop`)
|
|
299
|
+
- Graceful shutdown and health checks
|
|
300
|
+
|
|
301
|
+
### Development Setup
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
# From repo root
|
|
305
|
+
npm install
|
|
306
|
+
|
|
307
|
+
# Create .env file with:
|
|
308
|
+
# REDIS_URL=redis://... (or rediss://...)
|
|
309
|
+
# REDIS_DB=2
|
|
310
|
+
# DATABASE_URL=postgres://user:pass@host:port/db
|
|
311
|
+
|
|
312
|
+
# Run dev server
|
|
313
|
+
npm run --workspace @more-ink/irt-edge dev
|
|
314
|
+
|
|
315
|
+
# Seed data
|
|
316
|
+
npm run --workspace @more-ink/irt-edge seed
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Backend Environment Variables
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
# Aliyun Function Compute
|
|
323
|
+
ALIYUN_ACCESS_KEY="YOUR_ALIYUN_ACCESS_KEY"
|
|
324
|
+
ALIYUN_SECRET_ACCESS_KEY="YOUR_ALIYUN_SECRET_KEY"
|
|
325
|
+
|
|
326
|
+
# Database (PostgreSQL via Prisma)
|
|
327
|
+
DATABASE_URL="postgres://user:pass@host:port/db?schema=public"
|
|
328
|
+
# If using PgBouncer / pooled connections, also set DIRECT_URL for Prisma CLI commands
|
|
329
|
+
# DIRECT_URL="postgres://user:pass@host:port/db?schema=public"
|
|
330
|
+
|
|
331
|
+
# Feishu (Lark) Integration
|
|
332
|
+
FEISHU_APP_ID="YOUR_FEISHU_APP_ID"
|
|
333
|
+
FEISHU_APP_SECRET="YOUR_FEISHU_APP_SECRET"
|
|
334
|
+
FEISHU_CHAT_ID="YOUR_FEISHU_CHAT_ID"
|
|
335
|
+
|
|
336
|
+
# Redis (primary online store)
|
|
337
|
+
REDIS_URL="redis://localhost:6379"
|
|
338
|
+
REDIS_DB=2
|
|
339
|
+
|
|
340
|
+
# Debug mode for Function Compute
|
|
341
|
+
DEBUG_FC="true"
|
|
342
|
+
|
|
343
|
+
# Server port (optional, default: 9000)
|
|
344
|
+
PORT=9000
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
See `src/index.ts` for default IRT engine parameters (learning rates, selection options).
|
|
348
|
+
|
|
349
|
+
### API Endpoints
|
|
350
|
+
|
|
351
|
+
**IRT Operations:**
|
|
352
|
+
- `POST /api/irt/answer` - Record response and get next item
|
|
353
|
+
- `POST /api/irt/answer-multi` - Record multiple skill responses for one item
|
|
354
|
+
- `POST /api/irt/sync` - Persist dirty Redis state to Postgres (cron-triggered)
|
|
355
|
+
- `POST /api/irt/next-item` - Get next item without recording response
|
|
356
|
+
- `POST /api/irt/users/:userId/reset` - Reset user skills in Redis
|
|
357
|
+
- `POST /api/irt/items/:itemId/reset` - Reset item skills in Redis
|
|
358
|
+
- `POST /api/irt/bulk-update-user/:userId` - Bulk update user skills (user-only)
|
|
359
|
+
- `POST /api/irt/bulk-update-item/:itemId` - Bulk update item parameters (item-only)
|
|
360
|
+
- `DELETE /api/irt/users/:userId` - Delete a user and all skill states
|
|
361
|
+
- `DELETE /api/irt/items/:itemId` - Delete an item and its per-skill calibrations
|
|
362
|
+
- `GET /api/irt/health` - Health check
|
|
363
|
+
|
|
364
|
+
**Aliyun FC Lifecycle:**
|
|
365
|
+
- `POST /initialize` - Instance startup (verifies Prisma DB and Redis connection)
|
|
366
|
+
- `GET /pre-stop` - Instance shutdown (closes Prisma DB and Redis)
|
|
367
|
+
|
|
368
|
+
**System:**
|
|
369
|
+
- `GET /` - Service info
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Cold-start Guidance
|
|
374
|
+
|
|
375
|
+
When you have **no prior data** (no user θ, no item a/b), the recommended order for scale is:
|
|
376
|
+
|
|
377
|
+
1) **Item cold-start** using a default θ (e.g., θ=0).
|
|
378
|
+
2) **User cold-start** using the calibrated item a/b.
|
|
379
|
+
3) **Optional**: run item cold-start again to refine a/b.
|
|
380
|
+
|
|
381
|
+
Why this order: item sets are typically small and fixed, while user sets are large and segmented. A quick first-pass item calibration (with θ=0) reduces downstream work before user backfill.
|
|
382
|
+
|
|
383
|
+
Reset and cold-start operations clear **Redis** only. Postgres is treated as a backup store and may retain stale rows until fresh updates overwrite them.
|
|
384
|
+
|
|
385
|
+
### Bulk Update Constraints
|
|
386
|
+
|
|
387
|
+
- Maximum **10k events** per request (over limit returns 400).
|
|
388
|
+
- `timestamp` is **required** and events are processed in ascending time order.
|
|
389
|
+
- `score` must be in `[0, 1]` (invalid events are skipped with errors).
|
|
390
|
+
- Errors are reported per event; valid events still proceed.
|
|
391
|
+
- Only a **single global** `updateOptions` is supported per bulk request.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Backend Deployment (Aliyun FC)
|
|
396
|
+
|
|
397
|
+
Deploy the backend server to Aliyun FC:
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
# From root (recommended)
|
|
401
|
+
npm run dp
|
|
402
|
+
|
|
403
|
+
# Or from package dir
|
|
404
|
+
npm run --workspace @more-ink/irt-edge deploy
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### How Deployment Works
|
|
408
|
+
|
|
409
|
+
Deployment now runs directly from the package root with npm-installed `node_modules` (no flattening step needed):
|
|
410
|
+
1. Run `npm run --workspace @more-ink/irt-edge deploy` (runs `prisma generate`, then `build`, then `fc-deploy` from the package root)
|
|
411
|
+
2. Upload from the package root; `node_modules` already contains real files.
|
|
412
|
+
|
|
413
|
+
Ensure `DATABASE_URL` (and `DIRECT_URL` if pooling) is set in the function env.
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## SDK Publishing (npm)
|
|
418
|
+
|
|
419
|
+
Publish the SDK to npm (backend code stays private):
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
# From repo root (recommended)
|
|
423
|
+
npm run pub
|
|
424
|
+
|
|
425
|
+
# Or from package directory
|
|
426
|
+
cd packages/irt-edge
|
|
427
|
+
npm run publish:sdk
|
|
428
|
+
|
|
429
|
+
# Or using npm directly
|
|
430
|
+
npm publish --access public
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### What Gets Published
|
|
434
|
+
|
|
435
|
+
**Published to npm:**
|
|
436
|
+
- ✅ `sdk/` - Compiled SDK code (~40KB unpacked)
|
|
437
|
+
- ✅ `README.md`, `CHANGELOG.md` - Documentation
|
|
438
|
+
- ✅ `examples/` - Usage examples
|
|
439
|
+
- ✅ `package.json` - Package metadata
|
|
440
|
+
|
|
441
|
+
**NOT published (stays private):**
|
|
442
|
+
- ❌ Backend server code (`src/index.ts`, `routes.ts`, `storage/`)
|
|
443
|
+
- ❌ Scripts and tests (`scripts/`, `tests/`)
|
|
444
|
+
- ❌ Config files (`.env`, `tsconfig.json`, etc.)
|
|
445
|
+
- ❌ Backend build output (`dist/`)
|
|
446
|
+
|
|
447
|
+
The `.npmignore` file ensures only SDK files are published.
|
|
448
|
+
|
|
449
|
+
### Publishing Checklist
|
|
450
|
+
|
|
451
|
+
Before publishing:
|
|
452
|
+
|
|
453
|
+
1. **Version Management**
|
|
454
|
+
- Update version in `package.json` (follow [semver](https://semver.org/))
|
|
455
|
+
- Update `CHANGELOG.md` with changes
|
|
456
|
+
|
|
457
|
+
2. **Code Quality**
|
|
458
|
+
- Build SDK: `npm run --workspace @more-ink/irt-edge build:sdk`
|
|
459
|
+
- Run tests: `npm run --workspace @more-ink/irt-edge test`
|
|
460
|
+
- Check TypeScript: `tsc -p tsconfig.sdk.json --noEmit`
|
|
461
|
+
|
|
462
|
+
3. **Verify Package**
|
|
463
|
+
- Dry-run: `npm pack --dry-run`
|
|
464
|
+
- Check package size (<50KB)
|
|
465
|
+
- Verify only SDK files included
|
|
466
|
+
|
|
467
|
+
4. **Publish**
|
|
468
|
+
- `npm run pub` (auto-builds before publishing)
|
|
469
|
+
|
|
470
|
+
5. **Post-Publishing**
|
|
471
|
+
- Verify on npm: https://www.npmjs.com/package/@more-ink/irt-edge
|
|
472
|
+
- Tag release: `git tag v1.0.0 && git push origin v1.0.0`
|
|
473
|
+
- Create GitHub release
|
|
474
|
+
|
|
475
|
+
### Test Installation
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
mkdir /tmp/test-irt-sdk && cd /tmp/test-irt-sdk
|
|
479
|
+
npm init -y
|
|
480
|
+
npm install @more-ink/irt-edge @more-ink/irt-core
|
|
481
|
+
node -e "const { IrtClient } = require('@more-ink/irt-edge'); console.log('OK')"
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
---
|
|
485
|
+
|
|
486
|
+
## Package Scripts
|
|
487
|
+
|
|
488
|
+
### Development & Backend
|
|
489
|
+
- `npm run --workspace @more-ink/irt-edge dev` - Run dev server with hot reload
|
|
490
|
+
- `npm run --workspace @more-ink/irt-edge build` - Build backend server to `dist/`
|
|
491
|
+
- `npm run --workspace @more-ink/irt-edge start` - Start production backend server
|
|
492
|
+
- `npm run --workspace @more-ink/irt-edge seed` - Seed Redis with test data
|
|
493
|
+
- `npm run --workspace @more-ink/irt-edge deploy` - Deploy backend to Aliyun FC
|
|
494
|
+
|
|
495
|
+
### SDK Publishing
|
|
496
|
+
- `npm run --workspace @more-ink/irt-edge build:sdk` - Build SDK to `sdk/`
|
|
497
|
+
- `npm run --workspace @more-ink/irt-edge publish:sdk` - Build and publish SDK to npm
|
|
498
|
+
- `npm run pub` - Shortcut for `npm run --workspace @more-ink/irt-edge publish:sdk` (from root)
|
|
499
|
+
|
|
500
|
+
### Testing
|
|
501
|
+
- `npm run --workspace @more-ink/irt-edge test` - Run test suite
|
|
502
|
+
- `npm run --workspace @more-ink/irt-edge test:watch` - Run tests in watch mode
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## Architecture
|
|
507
|
+
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
irt-edge/
|
|
513
|
+
├── src/
|
|
514
|
+
│ ├── sdk/ # ✅ SDK source (published)
|
|
515
|
+
│ │ ├── types.ts # Type definitions
|
|
516
|
+
│ │ ├── client.ts # IrtClient class
|
|
517
|
+
│ │ └── index.ts # Public exports
|
|
518
|
+
│ ├── index.ts # ❌ Backend server (not published)
|
|
519
|
+
│ ├── routes.ts # ❌ Backend routes
|
|
520
|
+
│ └── storage/ # ❌ Backend storage layer
|
|
521
|
+
├── sdk/ # ✅ Compiled SDK (published)
|
|
522
|
+
├── dist/ # ❌ Backend build (not published)
|
|
523
|
+
├── examples/ # ✅ SDK examples (published)
|
|
524
|
+
├── tsconfig.json # Backend build config
|
|
525
|
+
└── tsconfig.sdk.json # SDK build config
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
## License
|
|
529
|
+
|
|
530
|
+
Proprietary - All Rights Reserved
|