@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.
- package/components/MobileEvalDashboard.tsx +267 -0
- package/components/comparison/DeltaAnalysisTable.tsx +137 -0
- package/components/comparison/ProfileComparisonCard.tsx +176 -0
- package/components/comparison/RecognitionABMode.tsx +385 -0
- package/components/comparison/RecognitionMetricsPanel.tsx +135 -0
- package/components/comparison/WinnerIndicator.tsx +64 -0
- package/components/comparison/index.ts +5 -0
- package/components/mobile/BottomSheet.tsx +233 -0
- package/components/mobile/DimensionBreakdown.tsx +210 -0
- package/components/mobile/DocsView.tsx +363 -0
- package/components/mobile/LogsView.tsx +481 -0
- package/components/mobile/PsychodynamicQuadrant.tsx +261 -0
- package/components/mobile/QuickTestView.tsx +1098 -0
- package/components/mobile/RecognitionTypeChart.tsx +124 -0
- package/components/mobile/RecognitionView.tsx +809 -0
- package/components/mobile/RunDetailView.tsx +261 -0
- package/components/mobile/RunHistoryView.tsx +367 -0
- package/components/mobile/ScoreRadial.tsx +211 -0
- package/components/mobile/StreamingLogPanel.tsx +230 -0
- package/components/mobile/SynthesisStrategyChart.tsx +140 -0
- package/config/interaction-eval-scenarios.yaml +832 -0
- package/config/learner-agents.yaml +248 -0
- package/docs/research/ABLATION-DIALOGUE-ROUNDS.md +52 -0
- package/docs/research/ABLATION-MODEL-SELECTION.md +53 -0
- package/docs/research/ADVANCED-EVAL-ANALYSIS.md +60 -0
- package/docs/research/ANOVA-RESULTS-2026-01-14.md +257 -0
- package/docs/research/COMPREHENSIVE-EVALUATION-PLAN.md +586 -0
- package/docs/research/COST-ANALYSIS.md +56 -0
- package/docs/research/CRITICAL-REVIEW-RECOGNITION-TUTORING.md +340 -0
- package/docs/research/DYNAMIC-VS-SCRIPTED-ANALYSIS.md +291 -0
- package/docs/research/EVAL-SYSTEM-ANALYSIS.md +306 -0
- package/docs/research/FACTORIAL-RESULTS-2026-01-14.md +301 -0
- package/docs/research/IMPLEMENTATION-PLAN-CRITIQUE-RESPONSE.md +1988 -0
- package/docs/research/LONGITUDINAL-DYADIC-EVALUATION.md +282 -0
- package/docs/research/MULTI-JUDGE-VALIDATION-2026-01-14.md +147 -0
- package/docs/research/PAPER-EXTENSION-DYADIC.md +204 -0
- package/docs/research/PAPER-UNIFIED.md +659 -0
- package/docs/research/PAPER-UNIFIED.pdf +0 -0
- package/docs/research/PROMPT-IMPROVEMENTS-2026-01-14.md +356 -0
- package/docs/research/SESSION-NOTES-2026-01-11-RECOGNITION-EVAL.md +419 -0
- package/docs/research/apa.csl +2133 -0
- package/docs/research/archive/PAPER-DRAFT-RECOGNITION-TUTORING.md +1637 -0
- package/docs/research/archive/paper-multiagent-tutor.tex +978 -0
- package/docs/research/paper-draft/full-paper.md +136 -0
- package/docs/research/paper-draft/images/pasted-image-2026-01-24T03-47-47-846Z-d76a7ae2.png +0 -0
- package/docs/research/paper-draft/references.bib +515 -0
- package/docs/research/transcript-baseline.md +139 -0
- package/docs/research/transcript-recognition-multiagent.md +187 -0
- package/hooks/useEvalData.ts +625 -0
- package/index.js +27 -0
- package/package.json +73 -0
- package/routes/evalRoutes.js +3002 -0
- package/scripts/advanced-eval-analysis.js +351 -0
- package/scripts/analyze-eval-costs.js +378 -0
- package/scripts/analyze-eval-results.js +513 -0
- package/scripts/analyze-interaction-evals.js +368 -0
- package/server-init.js +45 -0
- package/server.js +162 -0
- package/services/benchmarkService.js +1892 -0
- package/services/evaluationRunner.js +739 -0
- package/services/evaluationStore.js +1121 -0
- package/services/learnerConfigLoader.js +385 -0
- package/services/learnerTutorInteractionEngine.js +857 -0
- package/services/memory/learnerMemoryService.js +1227 -0
- package/services/memory/learnerWritingPad.js +577 -0
- package/services/memory/tutorWritingPad.js +674 -0
- package/services/promptRecommendationService.js +493 -0
- 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
|
+
};
|