@machinespirits/eval 0.1.2 → 0.2.1

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 (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/config/eval-settings.yaml +18 -0
  4. package/config/evaluation-rubric-learner.yaml +277 -0
  5. package/config/evaluation-rubric.yaml +613 -0
  6. package/config/interaction-eval-scenarios.yaml +93 -50
  7. package/config/learner-agents.yaml +124 -193
  8. package/config/machinespirits-eval.code-workspace +11 -0
  9. package/config/providers.yaml +60 -0
  10. package/config/suggestion-scenarios.yaml +1399 -0
  11. package/config/tutor-agents.yaml +716 -0
  12. package/docs/EVALUATION-VARIABLES.md +589 -0
  13. package/docs/REPLICATION-PLAN.md +577 -0
  14. package/index.js +15 -6
  15. package/package.json +16 -22
  16. package/routes/evalRoutes.js +88 -36
  17. package/scripts/analyze-judge-reliability.js +401 -0
  18. package/scripts/analyze-run.js +97 -0
  19. package/scripts/analyze-run.mjs +282 -0
  20. package/scripts/analyze-validation-failures.js +141 -0
  21. package/scripts/check-run.mjs +17 -0
  22. package/scripts/code-impasse-strategies.js +1132 -0
  23. package/scripts/compare-runs.js +44 -0
  24. package/scripts/compare-suggestions.js +80 -0
  25. package/scripts/compare-transformation.js +116 -0
  26. package/scripts/dig-into-run.js +158 -0
  27. package/scripts/eval-cli.js +2626 -0
  28. package/scripts/generate-paper-figures.py +452 -0
  29. package/scripts/qualitative-analysis-ai.js +1313 -0
  30. package/scripts/qualitative-analysis.js +688 -0
  31. package/scripts/seed-db.js +87 -0
  32. package/scripts/show-failed-suggestions.js +64 -0
  33. package/scripts/validate-content.js +192 -0
  34. package/server.js +3 -2
  35. package/services/__tests__/evalConfigLoader.test.js +338 -0
  36. package/services/anovaStats.js +499 -0
  37. package/services/contentResolver.js +407 -0
  38. package/services/dialogueTraceAnalyzer.js +454 -0
  39. package/services/evalConfigLoader.js +625 -0
  40. package/services/evaluationRunner.js +2171 -270
  41. package/services/evaluationStore.js +564 -29
  42. package/services/learnerConfigLoader.js +75 -5
  43. package/services/learnerRubricEvaluator.js +284 -0
  44. package/services/learnerTutorInteractionEngine.js +375 -0
  45. package/services/processUtils.js +18 -0
  46. package/services/progressLogger.js +98 -0
  47. package/services/promptRecommendationService.js +31 -26
  48. package/services/promptRewriter.js +427 -0
  49. package/services/rubricEvaluator.js +543 -70
  50. package/services/streamingReporter.js +104 -0
  51. package/services/turnComparisonAnalyzer.js +494 -0
  52. package/components/MobileEvalDashboard.tsx +0 -267
  53. package/components/comparison/DeltaAnalysisTable.tsx +0 -137
  54. package/components/comparison/ProfileComparisonCard.tsx +0 -176
  55. package/components/comparison/RecognitionABMode.tsx +0 -385
  56. package/components/comparison/RecognitionMetricsPanel.tsx +0 -135
  57. package/components/comparison/WinnerIndicator.tsx +0 -64
  58. package/components/comparison/index.ts +0 -5
  59. package/components/mobile/BottomSheet.tsx +0 -233
  60. package/components/mobile/DimensionBreakdown.tsx +0 -210
  61. package/components/mobile/DocsView.tsx +0 -363
  62. package/components/mobile/LogsView.tsx +0 -481
  63. package/components/mobile/PsychodynamicQuadrant.tsx +0 -261
  64. package/components/mobile/QuickTestView.tsx +0 -1098
  65. package/components/mobile/RecognitionTypeChart.tsx +0 -124
  66. package/components/mobile/RecognitionView.tsx +0 -809
  67. package/components/mobile/RunDetailView.tsx +0 -261
  68. package/components/mobile/RunHistoryView.tsx +0 -367
  69. package/components/mobile/ScoreRadial.tsx +0 -211
  70. package/components/mobile/StreamingLogPanel.tsx +0 -230
  71. package/components/mobile/SynthesisStrategyChart.tsx +0 -140
  72. package/docs/research/ABLATION-DIALOGUE-ROUNDS.md +0 -52
  73. package/docs/research/ABLATION-MODEL-SELECTION.md +0 -53
  74. package/docs/research/ADVANCED-EVAL-ANALYSIS.md +0 -60
  75. package/docs/research/ANOVA-RESULTS-2026-01-14.md +0 -257
  76. package/docs/research/COMPREHENSIVE-EVALUATION-PLAN.md +0 -586
  77. package/docs/research/COST-ANALYSIS.md +0 -56
  78. package/docs/research/CRITICAL-REVIEW-RECOGNITION-TUTORING.md +0 -340
  79. package/docs/research/DYNAMIC-VS-SCRIPTED-ANALYSIS.md +0 -291
  80. package/docs/research/EVAL-SYSTEM-ANALYSIS.md +0 -306
  81. package/docs/research/FACTORIAL-RESULTS-2026-01-14.md +0 -301
  82. package/docs/research/IMPLEMENTATION-PLAN-CRITIQUE-RESPONSE.md +0 -1988
  83. package/docs/research/LONGITUDINAL-DYADIC-EVALUATION.md +0 -282
  84. package/docs/research/MULTI-JUDGE-VALIDATION-2026-01-14.md +0 -147
  85. package/docs/research/PAPER-EXTENSION-DYADIC.md +0 -204
  86. package/docs/research/PAPER-UNIFIED.md +0 -659
  87. package/docs/research/PAPER-UNIFIED.pdf +0 -0
  88. package/docs/research/PROMPT-IMPROVEMENTS-2026-01-14.md +0 -356
  89. package/docs/research/SESSION-NOTES-2026-01-11-RECOGNITION-EVAL.md +0 -419
  90. package/docs/research/apa.csl +0 -2133
  91. package/docs/research/archive/PAPER-DRAFT-RECOGNITION-TUTORING.md +0 -1637
  92. package/docs/research/archive/paper-multiagent-tutor.tex +0 -978
  93. package/docs/research/paper-draft/full-paper.md +0 -136
  94. package/docs/research/paper-draft/images/pasted-image-2026-01-24T03-47-47-846Z-d76a7ae2.png +0 -0
  95. package/docs/research/paper-draft/references.bib +0 -515
  96. package/docs/research/transcript-baseline.md +0 -139
  97. package/docs/research/transcript-recognition-multiagent.md +0 -187
  98. package/hooks/useEvalData.ts +0 -625
  99. package/server-init.js +0 -45
  100. package/services/benchmarkService.js +0 -1892
  101. package/types.ts +0 -165
  102. package/utils/haptics.ts +0 -45
@@ -0,0 +1,454 @@
1
+ /**
2
+ * Dialogue Trace Analyzer Service
3
+ *
4
+ * Analyzes the internal dialogue traces from ego-superego interactions.
5
+ * Tracks how superego feedback influences ego revisions and identifies
6
+ * signals of bilateral transformation in the dialogue.
7
+ *
8
+ * Theoretical basis: The superego acts as the "external perspective" that
9
+ * enables genuine recognition. By tracking how feedback is incorporated,
10
+ * we can measure whether the dialogue achieves mutual transformation or
11
+ * remains one-directional instruction.
12
+ */
13
+
14
+ /**
15
+ * Analyze superego feedback incorporation patterns.
16
+ *
17
+ * @param {Array} dialogueTrace - Array of trace entries from tutor-core dialogue
18
+ * @returns {Object} Incorporation analysis metrics
19
+ */
20
+ export function analyzeSuperegoIncorporation(dialogueTrace) {
21
+ if (!dialogueTrace || !Array.isArray(dialogueTrace)) {
22
+ return {
23
+ incorporationRate: null,
24
+ feedbackPatterns: { enhance: 0, revise: 0, approve: 0, reject: 0 },
25
+ confidenceProgression: [],
26
+ totalFeedbackEvents: 0,
27
+ totalRevisions: 0,
28
+ avgConfidence: null,
29
+ transformationSignals: [],
30
+ };
31
+ }
32
+
33
+ const superegoFeedback = dialogueTrace.filter(e => e.agent === 'superego');
34
+ const egoRevisions = dialogueTrace.filter(e =>
35
+ e.agent === 'ego' && (e.action === 'revision' || e.action === 'revise')
36
+ );
37
+
38
+ // Count feedback patterns
39
+ const feedbackPatterns = { enhance: 0, revise: 0, approve: 0, reject: 0 };
40
+ const confidenceProgression = [];
41
+
42
+ for (const feedback of superegoFeedback) {
43
+ // Track intervention type
44
+ const interventionType = feedback.interventionType ||
45
+ feedback.verdict?.interventionType ||
46
+ feedback.action;
47
+
48
+ if (interventionType === 'enhance') feedbackPatterns.enhance++;
49
+ else if (interventionType === 'revise') feedbackPatterns.revise++;
50
+ else if (feedback.verdict?.approved === true) feedbackPatterns.approve++;
51
+ else if (feedback.verdict?.approved === false) feedbackPatterns.reject++;
52
+
53
+ // Track confidence
54
+ const confidence = feedback.confidence ||
55
+ feedback.verdict?.confidence ||
56
+ feedback.score;
57
+ if (typeof confidence === 'number') {
58
+ confidenceProgression.push({
59
+ turnIndex: feedback.turnIndex,
60
+ confidence,
61
+ timestamp: feedback.timestamp,
62
+ });
63
+ }
64
+ }
65
+
66
+ // Calculate incorporation rate
67
+ // How often does an ego revision follow superego feedback?
68
+ const incorporationRate = superegoFeedback.length > 0
69
+ ? egoRevisions.length / superegoFeedback.length
70
+ : null;
71
+
72
+ // Average confidence
73
+ const avgConfidence = confidenceProgression.length > 0
74
+ ? confidenceProgression.reduce((sum, c) => sum + c.confidence, 0) / confidenceProgression.length
75
+ : null;
76
+
77
+ // Extract transformation signals
78
+ const transformationSignals = extractTransformationSignals(dialogueTrace);
79
+
80
+ return {
81
+ incorporationRate,
82
+ feedbackPatterns,
83
+ confidenceProgression,
84
+ totalFeedbackEvents: superegoFeedback.length,
85
+ totalRevisions: egoRevisions.length,
86
+ avgConfidence,
87
+ transformationSignals,
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Extract transformation signals from dialogue trace.
93
+ * Identifies moments where tutor or learner explicitly transform their position.
94
+ *
95
+ * @param {Array} dialogueTrace - Array of trace entries
96
+ * @returns {Array} Array of transformation signal objects
97
+ */
98
+ export function extractTransformationSignals(dialogueTrace) {
99
+ if (!dialogueTrace || !Array.isArray(dialogueTrace)) {
100
+ return [];
101
+ }
102
+
103
+ const signals = [];
104
+
105
+ // Transformation language patterns
106
+ const tutorTransformationPatterns = [
107
+ /you'?ve? (helped|pushed|made) me (see|think|understand|reconsider)/i,
108
+ /that changes (how I|my)/i,
109
+ /(reconsidering|revising) (my|the) (approach|framing|understanding)/i,
110
+ /building on (your|that)/i,
111
+ /your (insight|point|question) (complicates|enriches|changes)/i,
112
+ /I hadn'?t (thought|considered)/i,
113
+ /let me (revise|adjust|rethink)/i,
114
+ ];
115
+
116
+ const learnerTransformationPatterns = [
117
+ /oh (wait|I see|that makes sense)/i,
118
+ /my (understanding|thinking|frame) (is|has) (changed|shifted|evolved)/i,
119
+ /(I was wrong|I see now|this is clicking)/i,
120
+ /that changes (how I|my)/i,
121
+ /so it'?s (not just|more like|actually)/i,
122
+ /the whole way I (think|thought|was thinking)/i,
123
+ ];
124
+
125
+ const superegoAcknowledgmentPatterns = [
126
+ /genuinely responsive/i,
127
+ /mutual (recognition|transformation)/i,
128
+ /adapted (to|based on)/i,
129
+ /evolved (through|during)/i,
130
+ /bilateral/i,
131
+ /both parties/i,
132
+ ];
133
+
134
+ for (const entry of dialogueTrace) {
135
+ // Check ego entries for tutor transformation
136
+ if (entry.agent === 'ego') {
137
+ const text = entry.reasoning || entry.detail || entry.contextSummary || '';
138
+
139
+ for (const pattern of tutorTransformationPatterns) {
140
+ if (pattern.test(text)) {
141
+ signals.push({
142
+ turn: entry.turnIndex,
143
+ type: 'tutor_transformation',
144
+ source: 'ego_reasoning',
145
+ pattern: pattern.source,
146
+ content: text.substring(0, 150),
147
+ timestamp: entry.timestamp,
148
+ });
149
+ break; // One signal per entry
150
+ }
151
+ }
152
+ }
153
+
154
+ // Check superego feedback for acknowledgment of adaptation
155
+ if (entry.agent === 'superego') {
156
+ const text = entry.verdict?.feedback ||
157
+ entry.verdict?.reasoning ||
158
+ entry.detail ||
159
+ entry.contextSummary ||
160
+ '';
161
+
162
+ for (const pattern of superegoAcknowledgmentPatterns) {
163
+ if (pattern.test(text)) {
164
+ signals.push({
165
+ turn: entry.turnIndex,
166
+ type: 'superego_noted_adaptation',
167
+ source: 'superego_feedback',
168
+ pattern: pattern.source,
169
+ content: text.substring(0, 150),
170
+ timestamp: entry.timestamp,
171
+ });
172
+ break;
173
+ }
174
+ }
175
+ }
176
+
177
+ // Check learner entries for growth signals
178
+ if (entry.agent === 'user' || entry.agent?.startsWith('learner')) {
179
+ const text = entry.detail || entry.contextSummary || '';
180
+
181
+ for (const pattern of learnerTransformationPatterns) {
182
+ if (pattern.test(text)) {
183
+ signals.push({
184
+ turn: entry.turnIndex,
185
+ type: 'learner_transformation',
186
+ source: entry.agent,
187
+ pattern: pattern.source,
188
+ content: text.substring(0, 150),
189
+ timestamp: entry.timestamp,
190
+ });
191
+ break;
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ return signals;
198
+ }
199
+
200
+ /**
201
+ * Analyze the bilateral transformation balance in a dialogue.
202
+ * Measures whether transformation is mutual or one-directional.
203
+ *
204
+ * @param {Array} dialogueTrace - Array of trace entries
205
+ * @returns {Object} Bilateral analysis
206
+ */
207
+ export function analyzeBilateralTransformation(dialogueTrace) {
208
+ const signals = extractTransformationSignals(dialogueTrace);
209
+
210
+ const tutorSignals = signals.filter(s => s.type === 'tutor_transformation');
211
+ const learnerSignals = signals.filter(s => s.type === 'learner_transformation');
212
+ const acknowledgmentSignals = signals.filter(s => s.type === 'superego_noted_adaptation');
213
+
214
+ const tutorCount = tutorSignals.length;
215
+ const learnerCount = learnerSignals.length;
216
+ const total = tutorCount + learnerCount;
217
+
218
+ // Calculate balance (1.0 = perfectly balanced)
219
+ let balance = null;
220
+ if (total > 0) {
221
+ const maxCount = Math.max(tutorCount, learnerCount);
222
+ const minCount = Math.min(tutorCount, learnerCount);
223
+ balance = maxCount > 0 ? minCount / maxCount : 0;
224
+ }
225
+
226
+ // Determine if transformation is genuine mutual recognition
227
+ const isMutual = tutorCount > 0 && learnerCount > 0;
228
+ const isAcknowledged = acknowledgmentSignals.length > 0;
229
+
230
+ return {
231
+ tutorTransformationCount: tutorCount,
232
+ learnerTransformationCount: learnerCount,
233
+ superegoAcknowledgmentCount: acknowledgmentSignals.length,
234
+ bilateralBalance: balance,
235
+ isMutualTransformation: isMutual,
236
+ isAcknowledgedBySystem: isAcknowledged,
237
+ transformationTimeline: signals.sort((a, b) => (a.turn || 0) - (b.turn || 0)),
238
+ summary: generateTransformationSummary(tutorCount, learnerCount, balance, isMutual),
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Generate a human-readable summary of transformation analysis.
244
+ *
245
+ * @param {number} tutorCount - Tutor transformation signals
246
+ * @param {number} learnerCount - Learner transformation signals
247
+ * @param {number|null} balance - Bilateral balance score
248
+ * @param {boolean} isMutual - Whether transformation is mutual
249
+ * @returns {string} Summary text
250
+ */
251
+ function generateTransformationSummary(tutorCount, learnerCount, balance, isMutual) {
252
+ if (tutorCount === 0 && learnerCount === 0) {
253
+ return 'No explicit transformation signals detected in dialogue.';
254
+ }
255
+
256
+ if (!isMutual) {
257
+ if (tutorCount > 0) {
258
+ return `One-directional: Tutor shows ${tutorCount} adaptation signal(s), but learner shows no growth.`;
259
+ } else {
260
+ return `One-directional: Learner shows ${learnerCount} growth signal(s), but tutor shows no adaptation.`;
261
+ }
262
+ }
263
+
264
+ if (balance !== null && balance >= 0.7) {
265
+ return `Mutual transformation achieved: ${tutorCount} tutor adaptation(s), ${learnerCount} learner growth(s), balance=${(balance * 100).toFixed(0)}%.`;
266
+ } else if (balance !== null && balance >= 0.3) {
267
+ return `Partial mutual transformation: ${tutorCount} tutor, ${learnerCount} learner signals, balance=${(balance * 100).toFixed(0)}% (asymmetric).`;
268
+ } else {
269
+ return `Imbalanced transformation: ${tutorCount} tutor, ${learnerCount} learner signals, balance=${(balance * 100).toFixed(0)}% (highly asymmetric).`;
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Analyze superego intervention effectiveness.
275
+ * Tracks whether superego interventions lead to improved outcomes.
276
+ *
277
+ * @param {Array} dialogueTrace - Array of trace entries
278
+ * @param {Array} turnResults - Array of turn result objects (for score comparison)
279
+ * @returns {Object} Intervention effectiveness analysis
280
+ */
281
+ export function analyzeInterventionEffectiveness(dialogueTrace, turnResults) {
282
+ if (!dialogueTrace || !turnResults) {
283
+ return {
284
+ interventionCount: 0,
285
+ scoreImprovementAfterIntervention: null,
286
+ mostEffectiveInterventionType: null,
287
+ interventionsByType: {},
288
+ };
289
+ }
290
+
291
+ const interventions = dialogueTrace.filter(e =>
292
+ e.agent === 'superego' && e.action === 'revise'
293
+ );
294
+
295
+ const interventionsByType = {};
296
+ let totalImprovement = 0;
297
+ let measuredInterventions = 0;
298
+
299
+ for (const intervention of interventions) {
300
+ const turnIndex = intervention.turnIndex;
301
+ const type = intervention.interventionType || 'revise';
302
+
303
+ // Track by type
304
+ if (!interventionsByType[type]) {
305
+ interventionsByType[type] = { count: 0, avgImprovement: 0, improvements: [] };
306
+ }
307
+ interventionsByType[type].count++;
308
+
309
+ // Find score before and after intervention
310
+ const turnBefore = turnResults.find(t => t.turnIndex === turnIndex - 1);
311
+ const turnAfter = turnResults.find(t => t.turnIndex === turnIndex);
312
+
313
+ if (turnBefore?.turnScore !== null && turnAfter?.turnScore !== null) {
314
+ const improvement = turnAfter.turnScore - turnBefore.turnScore;
315
+ interventionsByType[type].improvements.push(improvement);
316
+ totalImprovement += improvement;
317
+ measuredInterventions++;
318
+ }
319
+ }
320
+
321
+ // Calculate averages
322
+ for (const type of Object.keys(interventionsByType)) {
323
+ const imps = interventionsByType[type].improvements;
324
+ if (imps.length > 0) {
325
+ interventionsByType[type].avgImprovement = imps.reduce((a, b) => a + b, 0) / imps.length;
326
+ }
327
+ }
328
+
329
+ // Find most effective type
330
+ let mostEffectiveType = null;
331
+ let bestAvgImprovement = -Infinity;
332
+ for (const [type, data] of Object.entries(interventionsByType)) {
333
+ if (data.avgImprovement > bestAvgImprovement) {
334
+ bestAvgImprovement = data.avgImprovement;
335
+ mostEffectiveType = type;
336
+ }
337
+ }
338
+
339
+ return {
340
+ interventionCount: interventions.length,
341
+ scoreImprovementAfterIntervention: measuredInterventions > 0
342
+ ? totalImprovement / measuredInterventions
343
+ : null,
344
+ mostEffectiveInterventionType: mostEffectiveType,
345
+ interventionsByType,
346
+ };
347
+ }
348
+
349
+ /**
350
+ * Generate a comprehensive transformation report for a dialogue.
351
+ *
352
+ * @param {Array} dialogueTrace - Array of trace entries
353
+ * @param {Array} turnResults - Array of turn result objects
354
+ * @returns {Object} Comprehensive transformation report
355
+ */
356
+ export function generateTransformationReport(dialogueTrace, turnResults) {
357
+ const superegoAnalysis = analyzeSuperegoIncorporation(dialogueTrace);
358
+ const bilateralAnalysis = analyzeBilateralTransformation(dialogueTrace);
359
+ const interventionAnalysis = analyzeInterventionEffectiveness(dialogueTrace, turnResults);
360
+
361
+ return {
362
+ // Superego feedback patterns
363
+ superegoMetrics: {
364
+ incorporationRate: superegoAnalysis.incorporationRate,
365
+ feedbackPatterns: superegoAnalysis.feedbackPatterns,
366
+ avgConfidence: superegoAnalysis.avgConfidence,
367
+ totalFeedbackEvents: superegoAnalysis.totalFeedbackEvents,
368
+ },
369
+
370
+ // Bilateral transformation
371
+ bilateralMetrics: {
372
+ tutorTransformationCount: bilateralAnalysis.tutorTransformationCount,
373
+ learnerTransformationCount: bilateralAnalysis.learnerTransformationCount,
374
+ bilateralBalance: bilateralAnalysis.bilateralBalance,
375
+ isMutualTransformation: bilateralAnalysis.isMutualTransformation,
376
+ summary: bilateralAnalysis.summary,
377
+ },
378
+
379
+ // Intervention effectiveness
380
+ interventionMetrics: {
381
+ interventionCount: interventionAnalysis.interventionCount,
382
+ avgScoreImprovement: interventionAnalysis.scoreImprovementAfterIntervention,
383
+ mostEffectiveType: interventionAnalysis.mostEffectiveInterventionType,
384
+ },
385
+
386
+ // All transformation signals for timeline analysis
387
+ transformationSignals: superegoAnalysis.transformationSignals,
388
+ transformationTimeline: bilateralAnalysis.transformationTimeline,
389
+
390
+ // Summary assessment
391
+ overallAssessment: {
392
+ hasMutualTransformation: bilateralAnalysis.isMutualTransformation,
393
+ bilateralBalance: bilateralAnalysis.bilateralBalance,
394
+ superegoEffective: superegoAnalysis.incorporationRate !== null &&
395
+ superegoAnalysis.incorporationRate > 0.5,
396
+ transformationQuality: calculateTransformationQuality(
397
+ bilateralAnalysis,
398
+ superegoAnalysis,
399
+ interventionAnalysis
400
+ ),
401
+ },
402
+ };
403
+ }
404
+
405
+ /**
406
+ * Calculate an overall transformation quality score.
407
+ *
408
+ * @param {Object} bilateral - Bilateral transformation analysis
409
+ * @param {Object} superego - Superego analysis
410
+ * @param {Object} intervention - Intervention analysis
411
+ * @returns {number} Quality score (0-100)
412
+ */
413
+ function calculateTransformationQuality(bilateral, superego, intervention) {
414
+ let score = 0;
415
+ let factors = 0;
416
+
417
+ // Bilateral balance (weight: 40%)
418
+ if (bilateral.bilateralBalance !== null) {
419
+ score += bilateral.bilateralBalance * 40;
420
+ factors += 40;
421
+ }
422
+
423
+ // Mutual transformation (weight: 20%)
424
+ if (bilateral.isMutualTransformation) {
425
+ score += 20;
426
+ }
427
+ factors += 20;
428
+
429
+ // Superego incorporation (weight: 20%)
430
+ if (superego.incorporationRate !== null) {
431
+ score += Math.min(1, superego.incorporationRate) * 20;
432
+ factors += 20;
433
+ }
434
+
435
+ // Intervention effectiveness (weight: 20%)
436
+ if (intervention.scoreImprovementAfterIntervention !== null) {
437
+ // Normalize improvement (assume max reasonable improvement is 20 points)
438
+ const normalizedImprovement = Math.min(1, Math.max(0,
439
+ intervention.scoreImprovementAfterIntervention / 20
440
+ ));
441
+ score += normalizedImprovement * 20;
442
+ factors += 20;
443
+ }
444
+
445
+ return factors > 0 ? (score / factors) * 100 : 0;
446
+ }
447
+
448
+ export default {
449
+ analyzeSuperegoIncorporation,
450
+ extractTransformationSignals,
451
+ analyzeBilateralTransformation,
452
+ analyzeInterventionEffectiveness,
453
+ generateTransformationReport,
454
+ };