@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,577 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learner Writing Pad Service
|
|
3
|
+
*
|
|
4
|
+
* Implements Freud's "Mystic Writing Pad" metaphor for learner memory:
|
|
5
|
+
* - Conscious: Current interaction, immediate understanding
|
|
6
|
+
* - Preconscious: Recent lessons, accessible with attention
|
|
7
|
+
* - Unconscious: Permanent traces of breakthroughs and traumas
|
|
8
|
+
*
|
|
9
|
+
* Tracks what the learner remembers, misremembers, and has forgotten.
|
|
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, 'learner-writing-pad.db'));
|
|
24
|
+
|
|
25
|
+
db.exec(`
|
|
26
|
+
CREATE TABLE IF NOT EXISTS conscious_layer (
|
|
27
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
28
|
+
learner_id TEXT NOT NULL,
|
|
29
|
+
session_id TEXT NOT NULL,
|
|
30
|
+
current_topic TEXT,
|
|
31
|
+
current_understanding TEXT, -- 'none', 'partial', 'solid', 'transforming'
|
|
32
|
+
active_questions TEXT, -- JSON array
|
|
33
|
+
emotional_state TEXT, -- 'curious', 'frustrated', 'engaged', 'confused', etc.
|
|
34
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
35
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
36
|
+
UNIQUE(learner_id, session_id)
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
CREATE TABLE IF NOT EXISTS preconscious_lessons (
|
|
40
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
41
|
+
learner_id TEXT NOT NULL,
|
|
42
|
+
concept TEXT NOT NULL,
|
|
43
|
+
initial_understanding TEXT,
|
|
44
|
+
current_understanding TEXT,
|
|
45
|
+
retention_score REAL DEFAULT 1.0, -- Decays over time
|
|
46
|
+
last_accessed TEXT,
|
|
47
|
+
access_count INTEGER DEFAULT 1,
|
|
48
|
+
misunderstandings TEXT, -- JSON array
|
|
49
|
+
connections TEXT, -- JSON array of related concepts
|
|
50
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
51
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
52
|
+
UNIQUE(learner_id, concept)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
CREATE TABLE IF NOT EXISTS unconscious_breakthroughs (
|
|
56
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
57
|
+
learner_id TEXT NOT NULL,
|
|
58
|
+
moment_description TEXT NOT NULL,
|
|
59
|
+
concept TEXT,
|
|
60
|
+
impact_score REAL DEFAULT 0.5, -- How significant was this
|
|
61
|
+
session_number INTEGER,
|
|
62
|
+
context TEXT, -- What led to this breakthrough
|
|
63
|
+
emotional_valence TEXT, -- 'positive', 'mixed', 'neutral'
|
|
64
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
CREATE TABLE IF NOT EXISTS unconscious_traumas (
|
|
68
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
69
|
+
learner_id TEXT NOT NULL,
|
|
70
|
+
moment_description TEXT NOT NULL,
|
|
71
|
+
concept TEXT,
|
|
72
|
+
impact_score REAL DEFAULT 0.5, -- How significant was this
|
|
73
|
+
session_number INTEGER,
|
|
74
|
+
trigger TEXT, -- What triggered this experience
|
|
75
|
+
resolved INTEGER DEFAULT 0, -- Has it been worked through?
|
|
76
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
CREATE TABLE IF NOT EXISTS core_patterns (
|
|
80
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
81
|
+
learner_id TEXT NOT NULL,
|
|
82
|
+
pattern_type TEXT NOT NULL, -- 'learning_style', 'struggle_pattern', 'strength', etc.
|
|
83
|
+
pattern_key TEXT NOT NULL,
|
|
84
|
+
pattern_value TEXT,
|
|
85
|
+
confidence REAL DEFAULT 0.5,
|
|
86
|
+
observation_count INTEGER DEFAULT 1,
|
|
87
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
88
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
89
|
+
UNIQUE(learner_id, pattern_type, pattern_key)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
CREATE TABLE IF NOT EXISTS memory_snapshots (
|
|
93
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
94
|
+
learner_id TEXT NOT NULL,
|
|
95
|
+
eval_run_id TEXT,
|
|
96
|
+
snapshot_data TEXT, -- JSON of full writing pad state
|
|
97
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
CREATE INDEX IF NOT EXISTS idx_conscious_learner ON conscious_layer(learner_id);
|
|
101
|
+
CREATE INDEX IF NOT EXISTS idx_preconscious_learner ON preconscious_lessons(learner_id);
|
|
102
|
+
CREATE INDEX IF NOT EXISTS idx_breakthroughs_learner ON unconscious_breakthroughs(learner_id);
|
|
103
|
+
CREATE INDEX IF NOT EXISTS idx_traumas_learner ON unconscious_traumas(learner_id);
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_patterns_learner ON core_patterns(learner_id);
|
|
105
|
+
`);
|
|
106
|
+
} catch (e) {
|
|
107
|
+
console.warn('[LearnerWritingPad] Could not initialize database:', e.message);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ============================================================================
|
|
111
|
+
// Conscious Layer Operations
|
|
112
|
+
// ============================================================================
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Update the conscious layer (current interaction state)
|
|
116
|
+
*/
|
|
117
|
+
export function updateConsciousLayer(learnerId, sessionId, data) {
|
|
118
|
+
if (!db) return null;
|
|
119
|
+
|
|
120
|
+
const stmt = db.prepare(`
|
|
121
|
+
INSERT INTO conscious_layer (learner_id, session_id, current_topic, current_understanding, active_questions, emotional_state, updated_at)
|
|
122
|
+
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
123
|
+
ON CONFLICT(learner_id, session_id) DO UPDATE SET
|
|
124
|
+
current_topic = COALESCE(excluded.current_topic, current_topic),
|
|
125
|
+
current_understanding = COALESCE(excluded.current_understanding, current_understanding),
|
|
126
|
+
active_questions = COALESCE(excluded.active_questions, active_questions),
|
|
127
|
+
emotional_state = COALESCE(excluded.emotional_state, emotional_state),
|
|
128
|
+
updated_at = CURRENT_TIMESTAMP
|
|
129
|
+
`);
|
|
130
|
+
|
|
131
|
+
return stmt.run(
|
|
132
|
+
learnerId,
|
|
133
|
+
sessionId,
|
|
134
|
+
data.currentTopic || null,
|
|
135
|
+
data.currentUnderstanding || null,
|
|
136
|
+
data.activeQuestions ? JSON.stringify(data.activeQuestions) : null,
|
|
137
|
+
data.emotionalState || null
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get current conscious state
|
|
143
|
+
*/
|
|
144
|
+
export function getConsciousLayer(learnerId, sessionId) {
|
|
145
|
+
if (!db) return null;
|
|
146
|
+
|
|
147
|
+
const row = db.prepare(`
|
|
148
|
+
SELECT * FROM conscious_layer WHERE learner_id = ? AND session_id = ?
|
|
149
|
+
`).get(learnerId, sessionId);
|
|
150
|
+
|
|
151
|
+
if (!row) return null;
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
currentTopic: row.current_topic,
|
|
155
|
+
currentUnderstanding: row.current_understanding,
|
|
156
|
+
activeQuestions: row.active_questions ? JSON.parse(row.active_questions) : [],
|
|
157
|
+
emotionalState: row.emotional_state,
|
|
158
|
+
updatedAt: row.updated_at,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ============================================================================
|
|
163
|
+
// Preconscious Layer Operations
|
|
164
|
+
// ============================================================================
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Record or update a learned concept
|
|
168
|
+
*/
|
|
169
|
+
export function recordLesson(learnerId, concept, data) {
|
|
170
|
+
if (!db) return null;
|
|
171
|
+
|
|
172
|
+
const stmt = db.prepare(`
|
|
173
|
+
INSERT INTO preconscious_lessons (learner_id, concept, initial_understanding, current_understanding, retention_score, last_accessed, misunderstandings, connections)
|
|
174
|
+
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP, ?, ?)
|
|
175
|
+
ON CONFLICT(learner_id, concept) DO UPDATE SET
|
|
176
|
+
current_understanding = COALESCE(excluded.current_understanding, current_understanding),
|
|
177
|
+
retention_score = CASE
|
|
178
|
+
WHEN excluded.retention_score IS NOT NULL THEN excluded.retention_score
|
|
179
|
+
ELSE MIN(1.0, retention_score + 0.1)
|
|
180
|
+
END,
|
|
181
|
+
last_accessed = CURRENT_TIMESTAMP,
|
|
182
|
+
access_count = access_count + 1,
|
|
183
|
+
misunderstandings = COALESCE(excluded.misunderstandings, misunderstandings),
|
|
184
|
+
connections = COALESCE(excluded.connections, connections),
|
|
185
|
+
updated_at = CURRENT_TIMESTAMP
|
|
186
|
+
`);
|
|
187
|
+
|
|
188
|
+
return stmt.run(
|
|
189
|
+
learnerId,
|
|
190
|
+
concept,
|
|
191
|
+
data.initialUnderstanding || null,
|
|
192
|
+
data.currentUnderstanding || null,
|
|
193
|
+
data.retentionScore || 1.0,
|
|
194
|
+
data.misunderstandings ? JSON.stringify(data.misunderstandings) : null,
|
|
195
|
+
data.connections ? JSON.stringify(data.connections) : null
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Apply memory decay to all preconscious lessons
|
|
201
|
+
* Called at session start to simulate forgetting
|
|
202
|
+
*/
|
|
203
|
+
export function applyMemoryDecay(learnerId, decayRate = 0.05) {
|
|
204
|
+
if (!db) return null;
|
|
205
|
+
|
|
206
|
+
return db.prepare(`
|
|
207
|
+
UPDATE preconscious_lessons
|
|
208
|
+
SET retention_score = MAX(0.1, retention_score - ?),
|
|
209
|
+
updated_at = CURRENT_TIMESTAMP
|
|
210
|
+
WHERE learner_id = ?
|
|
211
|
+
`).run(decayRate, learnerId);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get all preconscious lessons for a learner
|
|
216
|
+
*/
|
|
217
|
+
export function getPreconsciousLessons(learnerId, minRetention = 0) {
|
|
218
|
+
if (!db) return [];
|
|
219
|
+
|
|
220
|
+
const rows = db.prepare(`
|
|
221
|
+
SELECT * FROM preconscious_lessons
|
|
222
|
+
WHERE learner_id = ? AND retention_score >= ?
|
|
223
|
+
ORDER BY retention_score DESC
|
|
224
|
+
`).all(learnerId, minRetention);
|
|
225
|
+
|
|
226
|
+
return rows.map(row => ({
|
|
227
|
+
concept: row.concept,
|
|
228
|
+
initialUnderstanding: row.initial_understanding,
|
|
229
|
+
currentUnderstanding: row.current_understanding,
|
|
230
|
+
retentionScore: row.retention_score,
|
|
231
|
+
lastAccessed: row.last_accessed,
|
|
232
|
+
accessCount: row.access_count,
|
|
233
|
+
misunderstandings: row.misunderstandings ? JSON.parse(row.misunderstandings) : [],
|
|
234
|
+
connections: row.connections ? JSON.parse(row.connections) : [],
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get lessons that are at risk of being forgotten
|
|
240
|
+
*/
|
|
241
|
+
export function getLessonsAtRisk(learnerId, threshold = 0.4) {
|
|
242
|
+
if (!db) return [];
|
|
243
|
+
|
|
244
|
+
const rows = db.prepare(`
|
|
245
|
+
SELECT * FROM preconscious_lessons
|
|
246
|
+
WHERE learner_id = ? AND retention_score < ?
|
|
247
|
+
ORDER BY retention_score ASC
|
|
248
|
+
`).all(learnerId, threshold);
|
|
249
|
+
|
|
250
|
+
return rows.map(row => ({
|
|
251
|
+
concept: row.concept,
|
|
252
|
+
retentionScore: row.retention_score,
|
|
253
|
+
lastAccessed: row.last_accessed,
|
|
254
|
+
misunderstandings: row.misunderstandings ? JSON.parse(row.misunderstandings) : [],
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ============================================================================
|
|
259
|
+
// Unconscious Layer Operations
|
|
260
|
+
// ============================================================================
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Record a breakthrough moment
|
|
264
|
+
*/
|
|
265
|
+
export function recordBreakthrough(learnerId, data) {
|
|
266
|
+
if (!db) return null;
|
|
267
|
+
|
|
268
|
+
const stmt = db.prepare(`
|
|
269
|
+
INSERT INTO unconscious_breakthroughs (learner_id, moment_description, concept, impact_score, session_number, context, emotional_valence)
|
|
270
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
271
|
+
`);
|
|
272
|
+
|
|
273
|
+
return stmt.run(
|
|
274
|
+
learnerId,
|
|
275
|
+
data.momentDescription,
|
|
276
|
+
data.concept || null,
|
|
277
|
+
data.impactScore || 0.5,
|
|
278
|
+
data.sessionNumber || null,
|
|
279
|
+
data.context || null,
|
|
280
|
+
data.emotionalValence || 'positive'
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Record a learning trauma (frustration, confusion, shame)
|
|
286
|
+
*/
|
|
287
|
+
export function recordTrauma(learnerId, data) {
|
|
288
|
+
if (!db) return null;
|
|
289
|
+
|
|
290
|
+
const stmt = db.prepare(`
|
|
291
|
+
INSERT INTO unconscious_traumas (learner_id, moment_description, concept, impact_score, session_number, trigger)
|
|
292
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
293
|
+
`);
|
|
294
|
+
|
|
295
|
+
return stmt.run(
|
|
296
|
+
learnerId,
|
|
297
|
+
data.momentDescription,
|
|
298
|
+
data.concept || null,
|
|
299
|
+
data.impactScore || 0.5,
|
|
300
|
+
data.sessionNumber || null,
|
|
301
|
+
data.trigger || null
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Mark a trauma as resolved (worked through)
|
|
307
|
+
*/
|
|
308
|
+
export function resolveTrauma(learnerId, traumaId) {
|
|
309
|
+
if (!db) return null;
|
|
310
|
+
|
|
311
|
+
return db.prepare(`
|
|
312
|
+
UPDATE unconscious_traumas SET resolved = 1 WHERE id = ? AND learner_id = ?
|
|
313
|
+
`).run(traumaId, learnerId);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Get breakthroughs for a learner
|
|
318
|
+
*/
|
|
319
|
+
export function getBreakthroughs(learnerId) {
|
|
320
|
+
if (!db) return [];
|
|
321
|
+
|
|
322
|
+
const rows = db.prepare(`
|
|
323
|
+
SELECT * FROM unconscious_breakthroughs WHERE learner_id = ? ORDER BY impact_score DESC
|
|
324
|
+
`).all(learnerId);
|
|
325
|
+
|
|
326
|
+
return rows.map(row => ({
|
|
327
|
+
id: row.id,
|
|
328
|
+
momentDescription: row.moment_description,
|
|
329
|
+
concept: row.concept,
|
|
330
|
+
impactScore: row.impact_score,
|
|
331
|
+
sessionNumber: row.session_number,
|
|
332
|
+
context: row.context,
|
|
333
|
+
emotionalValence: row.emotional_valence,
|
|
334
|
+
createdAt: row.created_at,
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get unresolved traumas for a learner
|
|
340
|
+
*/
|
|
341
|
+
export function getUnresolvedTraumas(learnerId) {
|
|
342
|
+
if (!db) return [];
|
|
343
|
+
|
|
344
|
+
const rows = db.prepare(`
|
|
345
|
+
SELECT * FROM unconscious_traumas WHERE learner_id = ? AND resolved = 0 ORDER BY impact_score DESC
|
|
346
|
+
`).all(learnerId);
|
|
347
|
+
|
|
348
|
+
return rows.map(row => ({
|
|
349
|
+
id: row.id,
|
|
350
|
+
momentDescription: row.moment_description,
|
|
351
|
+
concept: row.concept,
|
|
352
|
+
impactScore: row.impact_score,
|
|
353
|
+
sessionNumber: row.session_number,
|
|
354
|
+
trigger: row.trigger,
|
|
355
|
+
createdAt: row.created_at,
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ============================================================================
|
|
360
|
+
// Core Patterns Operations
|
|
361
|
+
// ============================================================================
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Record or update a core pattern observation
|
|
365
|
+
*/
|
|
366
|
+
export function recordPattern(learnerId, patternType, patternKey, patternValue, confidence = 0.5) {
|
|
367
|
+
if (!db) return null;
|
|
368
|
+
|
|
369
|
+
const stmt = db.prepare(`
|
|
370
|
+
INSERT INTO core_patterns (learner_id, pattern_type, pattern_key, pattern_value, confidence)
|
|
371
|
+
VALUES (?, ?, ?, ?, ?)
|
|
372
|
+
ON CONFLICT(learner_id, pattern_type, pattern_key) DO UPDATE SET
|
|
373
|
+
pattern_value = excluded.pattern_value,
|
|
374
|
+
confidence = MIN(1.0, confidence + 0.1),
|
|
375
|
+
observation_count = observation_count + 1,
|
|
376
|
+
updated_at = CURRENT_TIMESTAMP
|
|
377
|
+
`);
|
|
378
|
+
|
|
379
|
+
return stmt.run(learnerId, patternType, patternKey, patternValue, confidence);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Get core patterns for a learner
|
|
384
|
+
*/
|
|
385
|
+
export function getCorePatterns(learnerId) {
|
|
386
|
+
if (!db) return {};
|
|
387
|
+
|
|
388
|
+
const rows = db.prepare(`
|
|
389
|
+
SELECT * FROM core_patterns WHERE learner_id = ? ORDER BY confidence DESC
|
|
390
|
+
`).all(learnerId);
|
|
391
|
+
|
|
392
|
+
const patterns = {};
|
|
393
|
+
for (const row of rows) {
|
|
394
|
+
if (!patterns[row.pattern_type]) {
|
|
395
|
+
patterns[row.pattern_type] = {};
|
|
396
|
+
}
|
|
397
|
+
patterns[row.pattern_type][row.pattern_key] = {
|
|
398
|
+
value: row.pattern_value,
|
|
399
|
+
confidence: row.confidence,
|
|
400
|
+
observationCount: row.observation_count,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
return patterns;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ============================================================================
|
|
407
|
+
// Full Writing Pad Operations
|
|
408
|
+
// ============================================================================
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Get the complete writing pad state for a learner
|
|
412
|
+
*/
|
|
413
|
+
export function getFullWritingPad(learnerId, sessionId = null) {
|
|
414
|
+
const conscious = sessionId ? getConsciousLayer(learnerId, sessionId) : null;
|
|
415
|
+
const preconscious = getPreconsciousLessons(learnerId);
|
|
416
|
+
const breakthroughs = getBreakthroughs(learnerId);
|
|
417
|
+
const traumas = getUnresolvedTraumas(learnerId);
|
|
418
|
+
const patterns = getCorePatterns(learnerId);
|
|
419
|
+
const atRisk = getLessonsAtRisk(learnerId);
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
conscious,
|
|
423
|
+
preconscious: {
|
|
424
|
+
lessons: preconscious,
|
|
425
|
+
atRisk,
|
|
426
|
+
},
|
|
427
|
+
unconscious: {
|
|
428
|
+
breakthroughs,
|
|
429
|
+
unresolvedTraumas: traumas,
|
|
430
|
+
},
|
|
431
|
+
corePatterns: patterns,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Create a snapshot of writing pad state (for long-term tracking)
|
|
437
|
+
*/
|
|
438
|
+
export function createSnapshot(learnerId, evalRunId = null) {
|
|
439
|
+
if (!db) return null;
|
|
440
|
+
|
|
441
|
+
const fullState = getFullWritingPad(learnerId);
|
|
442
|
+
|
|
443
|
+
const stmt = db.prepare(`
|
|
444
|
+
INSERT INTO memory_snapshots (learner_id, eval_run_id, snapshot_data)
|
|
445
|
+
VALUES (?, ?, ?)
|
|
446
|
+
`);
|
|
447
|
+
|
|
448
|
+
const result = stmt.run(learnerId, evalRunId, JSON.stringify(fullState));
|
|
449
|
+
return {
|
|
450
|
+
id: result.lastInsertRowid,
|
|
451
|
+
...fullState,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Get snapshots for a learner (for comparing across evals)
|
|
457
|
+
*/
|
|
458
|
+
export function getSnapshots(learnerId, limit = 10) {
|
|
459
|
+
if (!db) return [];
|
|
460
|
+
|
|
461
|
+
const rows = db.prepare(`
|
|
462
|
+
SELECT * FROM memory_snapshots WHERE learner_id = ?
|
|
463
|
+
ORDER BY created_at DESC LIMIT ?
|
|
464
|
+
`).all(learnerId, limit);
|
|
465
|
+
|
|
466
|
+
return rows.map(row => ({
|
|
467
|
+
id: row.id,
|
|
468
|
+
evalRunId: row.eval_run_id,
|
|
469
|
+
snapshotData: JSON.parse(row.snapshot_data),
|
|
470
|
+
createdAt: row.created_at,
|
|
471
|
+
}));
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Build narrative summary of writing pad for injection into prompts
|
|
476
|
+
*/
|
|
477
|
+
export function buildNarrativeSummary(learnerId, sessionId = null) {
|
|
478
|
+
const pad = getFullWritingPad(learnerId, sessionId);
|
|
479
|
+
const parts = [];
|
|
480
|
+
|
|
481
|
+
// Conscious layer
|
|
482
|
+
if (pad.conscious) {
|
|
483
|
+
parts.push(`Currently focused on: ${pad.conscious.currentTopic || 'nothing specific'}`);
|
|
484
|
+
if (pad.conscious.currentUnderstanding) {
|
|
485
|
+
parts.push(`Understanding level: ${pad.conscious.currentUnderstanding}`);
|
|
486
|
+
}
|
|
487
|
+
if (pad.conscious.activeQuestions?.length > 0) {
|
|
488
|
+
parts.push(`Active questions: ${pad.conscious.activeQuestions.join('; ')}`);
|
|
489
|
+
}
|
|
490
|
+
if (pad.conscious.emotionalState) {
|
|
491
|
+
parts.push(`Emotional state: ${pad.conscious.emotionalState}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Preconscious - strong memories
|
|
496
|
+
const strongLessons = pad.preconscious.lessons.filter(l => l.retentionScore > 0.7);
|
|
497
|
+
if (strongLessons.length > 0) {
|
|
498
|
+
parts.push(`\nStrong understanding of: ${strongLessons.map(l => l.concept).join(', ')}`);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Preconscious - at risk
|
|
502
|
+
if (pad.preconscious.atRisk.length > 0) {
|
|
503
|
+
parts.push(`Fading memories of: ${pad.preconscious.atRisk.map(l => l.concept).join(', ')}`);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Unconscious - breakthroughs
|
|
507
|
+
const recentBreakthroughs = pad.unconscious.breakthroughs.slice(0, 3);
|
|
508
|
+
if (recentBreakthroughs.length > 0) {
|
|
509
|
+
parts.push(`\nPast breakthroughs:`);
|
|
510
|
+
for (const b of recentBreakthroughs) {
|
|
511
|
+
parts.push(` - ${b.momentDescription}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Unconscious - traumas
|
|
516
|
+
if (pad.unconscious.unresolvedTraumas.length > 0) {
|
|
517
|
+
parts.push(`\nUnresolved difficulties:`);
|
|
518
|
+
for (const t of pad.unconscious.unresolvedTraumas) {
|
|
519
|
+
parts.push(` - ${t.momentDescription}${t.concept ? ` (related to ${t.concept})` : ''}`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Core patterns
|
|
524
|
+
if (Object.keys(pad.corePatterns).length > 0) {
|
|
525
|
+
parts.push(`\nCore patterns:`);
|
|
526
|
+
for (const [type, patterns] of Object.entries(pad.corePatterns)) {
|
|
527
|
+
const patternStrs = Object.entries(patterns)
|
|
528
|
+
.filter(([_, p]) => p.confidence > 0.5)
|
|
529
|
+
.map(([key, p]) => `${key}: ${p.value}`);
|
|
530
|
+
if (patternStrs.length > 0) {
|
|
531
|
+
parts.push(` ${type}: ${patternStrs.join(', ')}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return parts.join('\n');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Clear all data for a learner (for test isolation)
|
|
541
|
+
*/
|
|
542
|
+
export function clearLearnerData(learnerId) {
|
|
543
|
+
if (!db) return;
|
|
544
|
+
|
|
545
|
+
db.prepare('DELETE FROM conscious_layer WHERE learner_id = ?').run(learnerId);
|
|
546
|
+
db.prepare('DELETE FROM preconscious_lessons WHERE learner_id = ?').run(learnerId);
|
|
547
|
+
db.prepare('DELETE FROM unconscious_breakthroughs WHERE learner_id = ?').run(learnerId);
|
|
548
|
+
db.prepare('DELETE FROM unconscious_traumas WHERE learner_id = ?').run(learnerId);
|
|
549
|
+
db.prepare('DELETE FROM core_patterns WHERE learner_id = ?').run(learnerId);
|
|
550
|
+
db.prepare('DELETE FROM memory_snapshots WHERE learner_id = ?').run(learnerId);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export default {
|
|
554
|
+
// Conscious
|
|
555
|
+
updateConsciousLayer,
|
|
556
|
+
getConsciousLayer,
|
|
557
|
+
// Preconscious
|
|
558
|
+
recordLesson,
|
|
559
|
+
applyMemoryDecay,
|
|
560
|
+
getPreconsciousLessons,
|
|
561
|
+
getLessonsAtRisk,
|
|
562
|
+
// Unconscious
|
|
563
|
+
recordBreakthrough,
|
|
564
|
+
recordTrauma,
|
|
565
|
+
resolveTrauma,
|
|
566
|
+
getBreakthroughs,
|
|
567
|
+
getUnresolvedTraumas,
|
|
568
|
+
// Patterns
|
|
569
|
+
recordPattern,
|
|
570
|
+
getCorePatterns,
|
|
571
|
+
// Full pad
|
|
572
|
+
getFullWritingPad,
|
|
573
|
+
createSnapshot,
|
|
574
|
+
getSnapshots,
|
|
575
|
+
buildNarrativeSummary,
|
|
576
|
+
clearLearnerData,
|
|
577
|
+
};
|