@machinespirits/eval 0.1.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.
Files changed (68) hide show
  1. package/components/MobileEvalDashboard.tsx +267 -0
  2. package/components/comparison/DeltaAnalysisTable.tsx +137 -0
  3. package/components/comparison/ProfileComparisonCard.tsx +176 -0
  4. package/components/comparison/RecognitionABMode.tsx +385 -0
  5. package/components/comparison/RecognitionMetricsPanel.tsx +135 -0
  6. package/components/comparison/WinnerIndicator.tsx +64 -0
  7. package/components/comparison/index.ts +5 -0
  8. package/components/mobile/BottomSheet.tsx +233 -0
  9. package/components/mobile/DimensionBreakdown.tsx +210 -0
  10. package/components/mobile/DocsView.tsx +363 -0
  11. package/components/mobile/LogsView.tsx +481 -0
  12. package/components/mobile/PsychodynamicQuadrant.tsx +261 -0
  13. package/components/mobile/QuickTestView.tsx +1098 -0
  14. package/components/mobile/RecognitionTypeChart.tsx +124 -0
  15. package/components/mobile/RecognitionView.tsx +809 -0
  16. package/components/mobile/RunDetailView.tsx +261 -0
  17. package/components/mobile/RunHistoryView.tsx +367 -0
  18. package/components/mobile/ScoreRadial.tsx +211 -0
  19. package/components/mobile/StreamingLogPanel.tsx +230 -0
  20. package/components/mobile/SynthesisStrategyChart.tsx +140 -0
  21. package/config/interaction-eval-scenarios.yaml +832 -0
  22. package/config/learner-agents.yaml +248 -0
  23. package/docs/research/ABLATION-DIALOGUE-ROUNDS.md +52 -0
  24. package/docs/research/ABLATION-MODEL-SELECTION.md +53 -0
  25. package/docs/research/ADVANCED-EVAL-ANALYSIS.md +60 -0
  26. package/docs/research/ANOVA-RESULTS-2026-01-14.md +257 -0
  27. package/docs/research/COMPREHENSIVE-EVALUATION-PLAN.md +586 -0
  28. package/docs/research/COST-ANALYSIS.md +56 -0
  29. package/docs/research/CRITICAL-REVIEW-RECOGNITION-TUTORING.md +340 -0
  30. package/docs/research/DYNAMIC-VS-SCRIPTED-ANALYSIS.md +291 -0
  31. package/docs/research/EVAL-SYSTEM-ANALYSIS.md +306 -0
  32. package/docs/research/FACTORIAL-RESULTS-2026-01-14.md +301 -0
  33. package/docs/research/IMPLEMENTATION-PLAN-CRITIQUE-RESPONSE.md +1988 -0
  34. package/docs/research/LONGITUDINAL-DYADIC-EVALUATION.md +282 -0
  35. package/docs/research/MULTI-JUDGE-VALIDATION-2026-01-14.md +147 -0
  36. package/docs/research/PAPER-EXTENSION-DYADIC.md +204 -0
  37. package/docs/research/PAPER-UNIFIED.md +659 -0
  38. package/docs/research/PAPER-UNIFIED.pdf +0 -0
  39. package/docs/research/PROMPT-IMPROVEMENTS-2026-01-14.md +356 -0
  40. package/docs/research/SESSION-NOTES-2026-01-11-RECOGNITION-EVAL.md +419 -0
  41. package/docs/research/apa.csl +2133 -0
  42. package/docs/research/archive/PAPER-DRAFT-RECOGNITION-TUTORING.md +1637 -0
  43. package/docs/research/archive/paper-multiagent-tutor.tex +978 -0
  44. package/docs/research/paper-draft/full-paper.md +136 -0
  45. package/docs/research/paper-draft/images/pasted-image-2026-01-24T03-47-47-846Z-d76a7ae2.png +0 -0
  46. package/docs/research/paper-draft/references.bib +515 -0
  47. package/docs/research/transcript-baseline.md +139 -0
  48. package/docs/research/transcript-recognition-multiagent.md +187 -0
  49. package/hooks/useEvalData.ts +625 -0
  50. package/index.js +27 -0
  51. package/package.json +73 -0
  52. package/routes/evalRoutes.js +3002 -0
  53. package/scripts/advanced-eval-analysis.js +351 -0
  54. package/scripts/analyze-eval-costs.js +378 -0
  55. package/scripts/analyze-eval-results.js +513 -0
  56. package/scripts/analyze-interaction-evals.js +368 -0
  57. package/server-init.js +45 -0
  58. package/server.js +162 -0
  59. package/services/benchmarkService.js +1892 -0
  60. package/services/evaluationRunner.js +739 -0
  61. package/services/evaluationStore.js +1121 -0
  62. package/services/learnerConfigLoader.js +385 -0
  63. package/services/learnerTutorInteractionEngine.js +857 -0
  64. package/services/memory/learnerMemoryService.js +1227 -0
  65. package/services/memory/learnerWritingPad.js +577 -0
  66. package/services/memory/tutorWritingPad.js +674 -0
  67. package/services/promptRecommendationService.js +493 -0
  68. package/services/rubricEvaluator.js +826 -0
@@ -0,0 +1,674 @@
1
+ /**
2
+ * Tutor Writing Pad Service
3
+ *
4
+ * Implements Freud's "Mystic Writing Pad" metaphor for tutor memory:
5
+ * - Conscious: Current pedagogical state, immediate goals
6
+ * - Preconscious: Accumulated strategies, what works for this learner
7
+ * - Unconscious: Deep insights about learner, relationship dynamics
8
+ *
9
+ * Tracks what the tutor has learned about teaching THIS specific learner.
10
+ */
11
+
12
+ import Database from 'better-sqlite3';
13
+ import path from 'path';
14
+ import { fileURLToPath } from 'url';
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+ const DATA_DIR = path.join(__dirname, '..', '..', 'data');
19
+
20
+ // Initialize database
21
+ let db;
22
+ try {
23
+ db = new Database(path.join(DATA_DIR, 'tutor-writing-pad.db'));
24
+
25
+ db.exec(`
26
+ CREATE TABLE IF NOT EXISTS conscious_state (
27
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
28
+ learner_id TEXT NOT NULL,
29
+ session_id TEXT NOT NULL,
30
+ current_strategy TEXT, -- 'scaffolding', 'socratic', 'direct', 'emotional_support'
31
+ learner_perceived_state TEXT, -- 'confused', 'engaged', 'frustrated', etc.
32
+ immediate_goal TEXT,
33
+ current_topic TEXT,
34
+ scaffolding_level INTEGER DEFAULT 2, -- 1-5, where 5 is most support
35
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
36
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
37
+ UNIQUE(learner_id, session_id)
38
+ );
39
+
40
+ CREATE TABLE IF NOT EXISTS strategy_effectiveness (
41
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
42
+ learner_id TEXT NOT NULL,
43
+ strategy_type TEXT NOT NULL, -- 'concrete_examples', 'socratic', 'analogies', etc.
44
+ success_count INTEGER DEFAULT 0,
45
+ failure_count INTEGER DEFAULT 0,
46
+ last_used TEXT,
47
+ context_notes TEXT, -- When does this strategy work best?
48
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
49
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
50
+ UNIQUE(learner_id, strategy_type)
51
+ );
52
+
53
+ CREATE TABLE IF NOT EXISTS learner_triggers (
54
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
55
+ learner_id TEXT NOT NULL,
56
+ trigger_type TEXT NOT NULL, -- 'frustration', 'engagement', 'breakthrough', 'shutdown'
57
+ trigger_description TEXT,
58
+ confidence REAL DEFAULT 0.5,
59
+ observation_count INTEGER DEFAULT 1,
60
+ examples TEXT, -- JSON array of observed examples
61
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
62
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
63
+ UNIQUE(learner_id, trigger_type, trigger_description)
64
+ );
65
+
66
+ CREATE TABLE IF NOT EXISTS pedagogical_insights (
67
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
68
+ learner_id TEXT NOT NULL,
69
+ insight TEXT NOT NULL,
70
+ insight_type TEXT, -- 'learning_style', 'emotional_need', 'content_preference', etc.
71
+ confidence REAL DEFAULT 0.5,
72
+ supporting_evidence TEXT, -- JSON array
73
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
74
+ );
75
+
76
+ CREATE TABLE IF NOT EXISTS relationship_dynamics (
77
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
78
+ learner_id TEXT NOT NULL,
79
+ trust_level REAL DEFAULT 0.5, -- 0-1
80
+ mutual_recognition_score REAL DEFAULT 0.0, -- 0-1
81
+ vulnerability_shown INTEGER DEFAULT 0, -- Has learner been vulnerable?
82
+ transformation_potential TEXT, -- 'low', 'medium', 'high'
83
+ relationship_stage TEXT, -- 'stranger', 'acquaintance', 'trusted', 'recognized'
84
+ notes TEXT,
85
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
86
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
87
+ UNIQUE(learner_id)
88
+ );
89
+
90
+ CREATE TABLE IF NOT EXISTS intervention_history (
91
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
92
+ learner_id TEXT NOT NULL,
93
+ session_id TEXT,
94
+ intervention_type TEXT NOT NULL, -- 'scaffolding', 'challenge', 'validation', 'redirection'
95
+ intervention_description TEXT,
96
+ learner_response TEXT, -- 'positive', 'neutral', 'negative', 'breakthrough'
97
+ context TEXT,
98
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
99
+ );
100
+
101
+ CREATE TABLE IF NOT EXISTS tutor_snapshots (
102
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
103
+ learner_id TEXT NOT NULL,
104
+ eval_run_id TEXT,
105
+ snapshot_data TEXT, -- JSON of full writing pad state
106
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
107
+ );
108
+
109
+ CREATE INDEX IF NOT EXISTS idx_conscious_learner ON conscious_state(learner_id);
110
+ CREATE INDEX IF NOT EXISTS idx_strategy_learner ON strategy_effectiveness(learner_id);
111
+ CREATE INDEX IF NOT EXISTS idx_triggers_learner ON learner_triggers(learner_id);
112
+ CREATE INDEX IF NOT EXISTS idx_insights_learner ON pedagogical_insights(learner_id);
113
+ CREATE INDEX IF NOT EXISTS idx_relationship_learner ON relationship_dynamics(learner_id);
114
+ CREATE INDEX IF NOT EXISTS idx_interventions_learner ON intervention_history(learner_id);
115
+ `);
116
+ } catch (e) {
117
+ console.warn('[TutorWritingPad] Could not initialize database:', e.message);
118
+ }
119
+
120
+ // ============================================================================
121
+ // Conscious Layer Operations
122
+ // ============================================================================
123
+
124
+ /**
125
+ * Update the conscious state (current pedagogical situation)
126
+ */
127
+ export function updateConsciousState(learnerId, sessionId, data) {
128
+ if (!db) return null;
129
+
130
+ const stmt = db.prepare(`
131
+ INSERT INTO conscious_state (learner_id, session_id, current_strategy, learner_perceived_state, immediate_goal, current_topic, scaffolding_level, updated_at)
132
+ VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
133
+ ON CONFLICT(learner_id, session_id) DO UPDATE SET
134
+ current_strategy = COALESCE(excluded.current_strategy, current_strategy),
135
+ learner_perceived_state = COALESCE(excluded.learner_perceived_state, learner_perceived_state),
136
+ immediate_goal = COALESCE(excluded.immediate_goal, immediate_goal),
137
+ current_topic = COALESCE(excluded.current_topic, current_topic),
138
+ scaffolding_level = COALESCE(excluded.scaffolding_level, scaffolding_level),
139
+ updated_at = CURRENT_TIMESTAMP
140
+ `);
141
+
142
+ return stmt.run(
143
+ learnerId,
144
+ sessionId,
145
+ data.currentStrategy || null,
146
+ data.learnerPerceivedState || null,
147
+ data.immediateGoal || null,
148
+ data.currentTopic || null,
149
+ data.scaffoldingLevel || null
150
+ );
151
+ }
152
+
153
+ /**
154
+ * Get current conscious state
155
+ */
156
+ export function getConsciousState(learnerId, sessionId) {
157
+ if (!db) return null;
158
+
159
+ const row = db.prepare(`
160
+ SELECT * FROM conscious_state WHERE learner_id = ? AND session_id = ?
161
+ `).get(learnerId, sessionId);
162
+
163
+ if (!row) return null;
164
+
165
+ return {
166
+ currentStrategy: row.current_strategy,
167
+ learnerPerceivedState: row.learner_perceived_state,
168
+ immediateGoal: row.immediate_goal,
169
+ currentTopic: row.current_topic,
170
+ scaffoldingLevel: row.scaffolding_level,
171
+ updatedAt: row.updated_at,
172
+ };
173
+ }
174
+
175
+ // ============================================================================
176
+ // Strategy Effectiveness Operations
177
+ // ============================================================================
178
+
179
+ /**
180
+ * Record a strategy use and its outcome
181
+ */
182
+ export function recordStrategyUse(learnerId, strategyType, success, contextNotes = null) {
183
+ if (!db) return null;
184
+
185
+ const existing = db.prepare(`
186
+ SELECT * FROM strategy_effectiveness WHERE learner_id = ? AND strategy_type = ?
187
+ `).get(learnerId, strategyType);
188
+
189
+ if (existing) {
190
+ const stmt = db.prepare(`
191
+ UPDATE strategy_effectiveness
192
+ SET success_count = success_count + ?,
193
+ failure_count = failure_count + ?,
194
+ last_used = CURRENT_TIMESTAMP,
195
+ context_notes = COALESCE(?, context_notes),
196
+ updated_at = CURRENT_TIMESTAMP
197
+ WHERE learner_id = ? AND strategy_type = ?
198
+ `);
199
+ return stmt.run(
200
+ success ? 1 : 0,
201
+ success ? 0 : 1,
202
+ contextNotes,
203
+ learnerId,
204
+ strategyType
205
+ );
206
+ } else {
207
+ const stmt = db.prepare(`
208
+ INSERT INTO strategy_effectiveness (learner_id, strategy_type, success_count, failure_count, last_used, context_notes)
209
+ VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, ?)
210
+ `);
211
+ return stmt.run(learnerId, strategyType, success ? 1 : 0, success ? 0 : 1, contextNotes);
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Get strategy effectiveness for a learner
217
+ */
218
+ export function getStrategyEffectiveness(learnerId) {
219
+ if (!db) return [];
220
+
221
+ const rows = db.prepare(`
222
+ SELECT *,
223
+ CASE WHEN success_count + failure_count > 0
224
+ THEN CAST(success_count AS REAL) / (success_count + failure_count)
225
+ ELSE 0.5
226
+ END as success_rate
227
+ FROM strategy_effectiveness
228
+ WHERE learner_id = ?
229
+ ORDER BY success_rate DESC
230
+ `).all(learnerId);
231
+
232
+ return rows.map(row => ({
233
+ strategyType: row.strategy_type,
234
+ successCount: row.success_count,
235
+ failureCount: row.failure_count,
236
+ successRate: row.success_rate,
237
+ lastUsed: row.last_used,
238
+ contextNotes: row.context_notes,
239
+ }));
240
+ }
241
+
242
+ /**
243
+ * Get the best strategy for this learner
244
+ */
245
+ export function getBestStrategy(learnerId, excludeTypes = []) {
246
+ if (!db) return null;
247
+
248
+ const strategies = getStrategyEffectiveness(learnerId);
249
+ return strategies.find(s => !excludeTypes.includes(s.strategyType) && s.successRate > 0.5);
250
+ }
251
+
252
+ // ============================================================================
253
+ // Learner Triggers Operations
254
+ // ============================================================================
255
+
256
+ /**
257
+ * Record an observed trigger
258
+ */
259
+ export function recordTrigger(learnerId, triggerType, description, example = null) {
260
+ if (!db) return null;
261
+
262
+ const existing = db.prepare(`
263
+ SELECT * FROM learner_triggers WHERE learner_id = ? AND trigger_type = ? AND trigger_description = ?
264
+ `).get(learnerId, triggerType, description);
265
+
266
+ if (existing) {
267
+ let examples = existing.examples ? JSON.parse(existing.examples) : [];
268
+ if (example) examples.push(example);
269
+ examples = examples.slice(-5); // Keep last 5 examples
270
+
271
+ const stmt = db.prepare(`
272
+ UPDATE learner_triggers
273
+ SET confidence = MIN(1.0, confidence + 0.1),
274
+ observation_count = observation_count + 1,
275
+ examples = ?,
276
+ updated_at = CURRENT_TIMESTAMP
277
+ WHERE id = ?
278
+ `);
279
+ return stmt.run(JSON.stringify(examples), existing.id);
280
+ } else {
281
+ const stmt = db.prepare(`
282
+ INSERT INTO learner_triggers (learner_id, trigger_type, trigger_description, examples)
283
+ VALUES (?, ?, ?, ?)
284
+ `);
285
+ return stmt.run(
286
+ learnerId,
287
+ triggerType,
288
+ description,
289
+ example ? JSON.stringify([example]) : null
290
+ );
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Get triggers for a learner
296
+ */
297
+ export function getTriggers(learnerId, triggerType = null) {
298
+ if (!db) return [];
299
+
300
+ let query = 'SELECT * FROM learner_triggers WHERE learner_id = ?';
301
+ const params = [learnerId];
302
+
303
+ if (triggerType) {
304
+ query += ' AND trigger_type = ?';
305
+ params.push(triggerType);
306
+ }
307
+
308
+ query += ' ORDER BY confidence DESC';
309
+
310
+ const rows = db.prepare(query).all(...params);
311
+
312
+ return rows.map(row => ({
313
+ triggerType: row.trigger_type,
314
+ description: row.trigger_description,
315
+ confidence: row.confidence,
316
+ observationCount: row.observation_count,
317
+ examples: row.examples ? JSON.parse(row.examples) : [],
318
+ }));
319
+ }
320
+
321
+ // ============================================================================
322
+ // Pedagogical Insights Operations
323
+ // ============================================================================
324
+
325
+ /**
326
+ * Record a pedagogical insight about this learner
327
+ */
328
+ export function recordInsight(learnerId, insight, insightType, confidence = 0.5, evidence = []) {
329
+ if (!db) return null;
330
+
331
+ const stmt = db.prepare(`
332
+ INSERT INTO pedagogical_insights (learner_id, insight, insight_type, confidence, supporting_evidence)
333
+ VALUES (?, ?, ?, ?, ?)
334
+ `);
335
+
336
+ return stmt.run(
337
+ learnerId,
338
+ insight,
339
+ insightType,
340
+ confidence,
341
+ JSON.stringify(evidence)
342
+ );
343
+ }
344
+
345
+ /**
346
+ * Get pedagogical insights for a learner
347
+ */
348
+ export function getInsights(learnerId, minConfidence = 0) {
349
+ if (!db) return [];
350
+
351
+ const rows = db.prepare(`
352
+ SELECT * FROM pedagogical_insights
353
+ WHERE learner_id = ? AND confidence >= ?
354
+ ORDER BY confidence DESC
355
+ `).all(learnerId, minConfidence);
356
+
357
+ return rows.map(row => ({
358
+ id: row.id,
359
+ insight: row.insight,
360
+ insightType: row.insight_type,
361
+ confidence: row.confidence,
362
+ supportingEvidence: row.supporting_evidence ? JSON.parse(row.supporting_evidence) : [],
363
+ createdAt: row.created_at,
364
+ }));
365
+ }
366
+
367
+ // ============================================================================
368
+ // Relationship Dynamics Operations
369
+ // ============================================================================
370
+
371
+ /**
372
+ * Update relationship dynamics with a learner
373
+ */
374
+ export function updateRelationshipDynamics(learnerId, data) {
375
+ if (!db) return null;
376
+
377
+ const existing = db.prepare(`
378
+ SELECT * FROM relationship_dynamics WHERE learner_id = ?
379
+ `).get(learnerId);
380
+
381
+ if (existing) {
382
+ const stmt = db.prepare(`
383
+ UPDATE relationship_dynamics
384
+ SET trust_level = COALESCE(?, trust_level),
385
+ mutual_recognition_score = COALESCE(?, mutual_recognition_score),
386
+ vulnerability_shown = COALESCE(?, vulnerability_shown),
387
+ transformation_potential = COALESCE(?, transformation_potential),
388
+ relationship_stage = COALESCE(?, relationship_stage),
389
+ notes = COALESCE(?, notes),
390
+ updated_at = CURRENT_TIMESTAMP
391
+ WHERE learner_id = ?
392
+ `);
393
+ return stmt.run(
394
+ data.trustLevel ?? null,
395
+ data.mutualRecognitionScore ?? null,
396
+ data.vulnerabilityShown ?? null,
397
+ data.transformationPotential ?? null,
398
+ data.relationshipStage ?? null,
399
+ data.notes ?? null,
400
+ learnerId
401
+ );
402
+ } else {
403
+ const stmt = db.prepare(`
404
+ INSERT INTO relationship_dynamics (learner_id, trust_level, mutual_recognition_score, vulnerability_shown, transformation_potential, relationship_stage, notes)
405
+ VALUES (?, ?, ?, ?, ?, ?, ?)
406
+ `);
407
+ return stmt.run(
408
+ learnerId,
409
+ data.trustLevel ?? 0.5,
410
+ data.mutualRecognitionScore ?? 0.0,
411
+ data.vulnerabilityShown ?? 0,
412
+ data.transformationPotential ?? 'medium',
413
+ data.relationshipStage ?? 'stranger',
414
+ data.notes ?? null
415
+ );
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Get relationship dynamics for a learner
421
+ */
422
+ export function getRelationshipDynamics(learnerId) {
423
+ if (!db) return null;
424
+
425
+ const row = db.prepare(`
426
+ SELECT * FROM relationship_dynamics WHERE learner_id = ?
427
+ `).get(learnerId);
428
+
429
+ if (!row) return null;
430
+
431
+ return {
432
+ trustLevel: row.trust_level,
433
+ mutualRecognitionScore: row.mutual_recognition_score,
434
+ vulnerabilityShown: row.vulnerability_shown === 1,
435
+ transformationPotential: row.transformation_potential,
436
+ relationshipStage: row.relationship_stage,
437
+ notes: row.notes,
438
+ updatedAt: row.updated_at,
439
+ };
440
+ }
441
+
442
+ // ============================================================================
443
+ // Intervention History Operations
444
+ // ============================================================================
445
+
446
+ /**
447
+ * Record an intervention and its outcome
448
+ */
449
+ export function recordIntervention(learnerId, sessionId, data) {
450
+ if (!db) return null;
451
+
452
+ const stmt = db.prepare(`
453
+ INSERT INTO intervention_history (learner_id, session_id, intervention_type, intervention_description, learner_response, context)
454
+ VALUES (?, ?, ?, ?, ?, ?)
455
+ `);
456
+
457
+ return stmt.run(
458
+ learnerId,
459
+ sessionId,
460
+ data.interventionType,
461
+ data.interventionDescription || null,
462
+ data.learnerResponse || null,
463
+ data.context || null
464
+ );
465
+ }
466
+
467
+ /**
468
+ * Get recent interventions for a learner
469
+ */
470
+ export function getRecentInterventions(learnerId, limit = 10) {
471
+ if (!db) return [];
472
+
473
+ const rows = db.prepare(`
474
+ SELECT * FROM intervention_history
475
+ WHERE learner_id = ?
476
+ ORDER BY created_at DESC
477
+ LIMIT ?
478
+ `).all(learnerId, limit);
479
+
480
+ return rows.map(row => ({
481
+ id: row.id,
482
+ sessionId: row.session_id,
483
+ interventionType: row.intervention_type,
484
+ interventionDescription: row.intervention_description,
485
+ learnerResponse: row.learner_response,
486
+ context: row.context,
487
+ createdAt: row.created_at,
488
+ }));
489
+ }
490
+
491
+ // ============================================================================
492
+ // Full Writing Pad Operations
493
+ // ============================================================================
494
+
495
+ /**
496
+ * Get the complete writing pad state for a tutor's knowledge of a learner
497
+ */
498
+ export function getFullWritingPad(learnerId, sessionId = null) {
499
+ const conscious = sessionId ? getConsciousState(learnerId, sessionId) : null;
500
+ const strategies = getStrategyEffectiveness(learnerId);
501
+ const triggers = getTriggers(learnerId);
502
+ const insights = getInsights(learnerId);
503
+ const relationship = getRelationshipDynamics(learnerId);
504
+ const recentInterventions = getRecentInterventions(learnerId, 5);
505
+
506
+ return {
507
+ conscious,
508
+ preconscious: {
509
+ effectiveStrategies: strategies.filter(s => s.successRate > 0.6),
510
+ ineffectiveStrategies: strategies.filter(s => s.successRate < 0.4),
511
+ triggers: {
512
+ frustration: triggers.filter(t => t.triggerType === 'frustration'),
513
+ engagement: triggers.filter(t => t.triggerType === 'engagement'),
514
+ breakthrough: triggers.filter(t => t.triggerType === 'breakthrough'),
515
+ shutdown: triggers.filter(t => t.triggerType === 'shutdown'),
516
+ },
517
+ },
518
+ unconscious: {
519
+ insights,
520
+ relationshipDynamics: relationship,
521
+ },
522
+ recentInterventions,
523
+ };
524
+ }
525
+
526
+ /**
527
+ * Create a snapshot of tutor writing pad state (for long-term tracking)
528
+ */
529
+ export function createSnapshot(learnerId, evalRunId = null) {
530
+ if (!db) return null;
531
+
532
+ const fullState = getFullWritingPad(learnerId);
533
+
534
+ const stmt = db.prepare(`
535
+ INSERT INTO tutor_snapshots (learner_id, eval_run_id, snapshot_data)
536
+ VALUES (?, ?, ?)
537
+ `);
538
+
539
+ const result = stmt.run(learnerId, evalRunId, JSON.stringify(fullState));
540
+ return {
541
+ id: result.lastInsertRowid,
542
+ ...fullState,
543
+ };
544
+ }
545
+
546
+ /**
547
+ * Get snapshots for comparing tutor learning across evals
548
+ */
549
+ export function getSnapshots(learnerId, limit = 10) {
550
+ if (!db) return [];
551
+
552
+ const rows = db.prepare(`
553
+ SELECT * FROM tutor_snapshots WHERE learner_id = ?
554
+ ORDER BY created_at DESC LIMIT ?
555
+ `).all(learnerId, limit);
556
+
557
+ return rows.map(row => ({
558
+ id: row.id,
559
+ evalRunId: row.eval_run_id,
560
+ snapshotData: JSON.parse(row.snapshot_data),
561
+ createdAt: row.created_at,
562
+ }));
563
+ }
564
+
565
+ /**
566
+ * Build narrative summary for injection into prompts
567
+ */
568
+ export function buildNarrativeSummary(learnerId, sessionId = null) {
569
+ const pad = getFullWritingPad(learnerId, sessionId);
570
+ const parts = [];
571
+
572
+ // Current state
573
+ if (pad.conscious) {
574
+ parts.push(`Current strategy: ${pad.conscious.currentStrategy || 'adaptive'}`);
575
+ if (pad.conscious.learnerPerceivedState) {
576
+ parts.push(`Learner appears: ${pad.conscious.learnerPerceivedState}`);
577
+ }
578
+ if (pad.conscious.immediateGoal) {
579
+ parts.push(`Working toward: ${pad.conscious.immediateGoal}`);
580
+ }
581
+ parts.push(`Scaffolding level: ${pad.conscious.scaffoldingLevel || 3}/5`);
582
+ }
583
+
584
+ // What works
585
+ if (pad.preconscious.effectiveStrategies.length > 0) {
586
+ parts.push(`\nEffective approaches for this learner:`);
587
+ for (const s of pad.preconscious.effectiveStrategies.slice(0, 3)) {
588
+ parts.push(` - ${s.strategyType} (${Math.round(s.successRate * 100)}% success)`);
589
+ }
590
+ }
591
+
592
+ // What to avoid
593
+ if (pad.preconscious.ineffectiveStrategies.length > 0) {
594
+ parts.push(`\nApproaches that haven't worked:`);
595
+ for (const s of pad.preconscious.ineffectiveStrategies.slice(0, 3)) {
596
+ parts.push(` - ${s.strategyType}`);
597
+ }
598
+ }
599
+
600
+ // Triggers
601
+ const triggers = pad.preconscious.triggers;
602
+ if (triggers.frustration.length > 0) {
603
+ const desc = triggers.frustration.map(t => t.description).join('; ');
604
+ parts.push(`\nFrustration triggers: ${desc}`);
605
+ }
606
+ if (triggers.engagement.length > 0) {
607
+ const desc = triggers.engagement.map(t => t.description).join('; ');
608
+ parts.push(`Engagement triggers: ${desc}`);
609
+ }
610
+
611
+ // Insights
612
+ const highConfidenceInsights = pad.unconscious.insights.filter(i => i.confidence > 0.7);
613
+ if (highConfidenceInsights.length > 0) {
614
+ parts.push(`\nKey insights about this learner:`);
615
+ for (const i of highConfidenceInsights.slice(0, 3)) {
616
+ parts.push(` - ${i.insight}`);
617
+ }
618
+ }
619
+
620
+ // Relationship
621
+ if (pad.unconscious.relationshipDynamics) {
622
+ const r = pad.unconscious.relationshipDynamics;
623
+ parts.push(`\nRelationship: ${r.relationshipStage || 'developing'}`);
624
+ parts.push(`Trust level: ${Math.round((r.trustLevel || 0.5) * 100)}%`);
625
+ if (r.transformationPotential) {
626
+ parts.push(`Transformation potential: ${r.transformationPotential}`);
627
+ }
628
+ }
629
+
630
+ return parts.join('\n');
631
+ }
632
+
633
+ /**
634
+ * Clear all data for a learner (for test isolation)
635
+ */
636
+ export function clearTutorData(learnerId) {
637
+ if (!db) return;
638
+
639
+ db.prepare('DELETE FROM conscious_state WHERE learner_id = ?').run(learnerId);
640
+ db.prepare('DELETE FROM strategy_effectiveness WHERE learner_id = ?').run(learnerId);
641
+ db.prepare('DELETE FROM learner_triggers WHERE learner_id = ?').run(learnerId);
642
+ db.prepare('DELETE FROM pedagogical_insights WHERE learner_id = ?').run(learnerId);
643
+ db.prepare('DELETE FROM relationship_dynamics WHERE learner_id = ?').run(learnerId);
644
+ db.prepare('DELETE FROM intervention_history WHERE learner_id = ?').run(learnerId);
645
+ db.prepare('DELETE FROM tutor_snapshots WHERE learner_id = ?').run(learnerId);
646
+ }
647
+
648
+ export default {
649
+ // Conscious
650
+ updateConsciousState,
651
+ getConsciousState,
652
+ // Strategies
653
+ recordStrategyUse,
654
+ getStrategyEffectiveness,
655
+ getBestStrategy,
656
+ // Triggers
657
+ recordTrigger,
658
+ getTriggers,
659
+ // Insights
660
+ recordInsight,
661
+ getInsights,
662
+ // Relationship
663
+ updateRelationshipDynamics,
664
+ getRelationshipDynamics,
665
+ // Interventions
666
+ recordIntervention,
667
+ getRecentInterventions,
668
+ // Full pad
669
+ getFullWritingPad,
670
+ createSnapshot,
671
+ getSnapshots,
672
+ buildNarrativeSummary,
673
+ clearTutorData,
674
+ };