@machinespirits/eval 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) 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/providers.yaml +60 -0
  9. package/config/suggestion-scenarios.yaml +1399 -0
  10. package/config/tutor-agents.yaml +716 -0
  11. package/docs/EVALUATION-VARIABLES.md +589 -0
  12. package/docs/REPLICATION-PLAN.md +577 -0
  13. package/docs/research/build.sh +74 -0
  14. package/docs/research/figures/figure1.png +0 -0
  15. package/docs/research/figures/figure2.png +0 -0
  16. package/docs/research/figures/figure3.png +0 -0
  17. package/docs/research/figures/figure4.png +0 -0
  18. package/docs/research/figures/figure5.png +0 -0
  19. package/docs/research/figures/figure6.png +0 -0
  20. package/docs/research/header.tex +4 -0
  21. package/docs/research/paper-full.md +1909 -0
  22. package/docs/research/paper-short.md +805 -0
  23. package/docs/research/references.bib +1011 -0
  24. package/index.js +15 -6
  25. package/package.json +14 -21
  26. package/routes/evalRoutes.js +88 -36
  27. package/scripts/analyze-judge-reliability.js +401 -0
  28. package/scripts/analyze-run.js +97 -0
  29. package/scripts/analyze-run.mjs +282 -0
  30. package/scripts/analyze-validation-failures.js +141 -0
  31. package/scripts/check-run.mjs +17 -0
  32. package/scripts/code-impasse-strategies.js +1132 -0
  33. package/scripts/compare-runs.js +44 -0
  34. package/scripts/compare-suggestions.js +80 -0
  35. package/scripts/compare-transformation.js +116 -0
  36. package/scripts/dig-into-run.js +158 -0
  37. package/scripts/eval-cli.js +2626 -0
  38. package/scripts/generate-paper-figures.py +452 -0
  39. package/scripts/qualitative-analysis-ai.js +1313 -0
  40. package/scripts/qualitative-analysis.js +688 -0
  41. package/scripts/seed-db.js +87 -0
  42. package/scripts/show-failed-suggestions.js +64 -0
  43. package/scripts/validate-content.js +192 -0
  44. package/server.js +3 -2
  45. package/services/__tests__/evalConfigLoader.test.js +338 -0
  46. package/services/anovaStats.js +499 -0
  47. package/services/contentResolver.js +407 -0
  48. package/services/dialogueTraceAnalyzer.js +454 -0
  49. package/services/evalConfigLoader.js +625 -0
  50. package/services/evaluationRunner.js +2171 -270
  51. package/services/evaluationStore.js +564 -29
  52. package/services/learnerConfigLoader.js +75 -5
  53. package/services/learnerRubricEvaluator.js +284 -0
  54. package/services/learnerTutorInteractionEngine.js +375 -0
  55. package/services/processUtils.js +18 -0
  56. package/services/progressLogger.js +98 -0
  57. package/services/promptRecommendationService.js +31 -26
  58. package/services/promptRewriter.js +427 -0
  59. package/services/rubricEvaluator.js +543 -70
  60. package/services/streamingReporter.js +104 -0
  61. package/services/turnComparisonAnalyzer.js +494 -0
  62. package/components/MobileEvalDashboard.tsx +0 -267
  63. package/components/comparison/DeltaAnalysisTable.tsx +0 -137
  64. package/components/comparison/ProfileComparisonCard.tsx +0 -176
  65. package/components/comparison/RecognitionABMode.tsx +0 -385
  66. package/components/comparison/RecognitionMetricsPanel.tsx +0 -135
  67. package/components/comparison/WinnerIndicator.tsx +0 -64
  68. package/components/comparison/index.ts +0 -5
  69. package/components/mobile/BottomSheet.tsx +0 -233
  70. package/components/mobile/DimensionBreakdown.tsx +0 -210
  71. package/components/mobile/DocsView.tsx +0 -363
  72. package/components/mobile/LogsView.tsx +0 -481
  73. package/components/mobile/PsychodynamicQuadrant.tsx +0 -261
  74. package/components/mobile/QuickTestView.tsx +0 -1098
  75. package/components/mobile/RecognitionTypeChart.tsx +0 -124
  76. package/components/mobile/RecognitionView.tsx +0 -809
  77. package/components/mobile/RunDetailView.tsx +0 -261
  78. package/components/mobile/RunHistoryView.tsx +0 -367
  79. package/components/mobile/ScoreRadial.tsx +0 -211
  80. package/components/mobile/StreamingLogPanel.tsx +0 -230
  81. package/components/mobile/SynthesisStrategyChart.tsx +0 -140
  82. package/docs/research/ABLATION-DIALOGUE-ROUNDS.md +0 -52
  83. package/docs/research/ABLATION-MODEL-SELECTION.md +0 -53
  84. package/docs/research/ADVANCED-EVAL-ANALYSIS.md +0 -60
  85. package/docs/research/ANOVA-RESULTS-2026-01-14.md +0 -257
  86. package/docs/research/COMPREHENSIVE-EVALUATION-PLAN.md +0 -586
  87. package/docs/research/COST-ANALYSIS.md +0 -56
  88. package/docs/research/CRITICAL-REVIEW-RECOGNITION-TUTORING.md +0 -340
  89. package/docs/research/DYNAMIC-VS-SCRIPTED-ANALYSIS.md +0 -291
  90. package/docs/research/EVAL-SYSTEM-ANALYSIS.md +0 -306
  91. package/docs/research/FACTORIAL-RESULTS-2026-01-14.md +0 -301
  92. package/docs/research/IMPLEMENTATION-PLAN-CRITIQUE-RESPONSE.md +0 -1988
  93. package/docs/research/LONGITUDINAL-DYADIC-EVALUATION.md +0 -282
  94. package/docs/research/MULTI-JUDGE-VALIDATION-2026-01-14.md +0 -147
  95. package/docs/research/PAPER-EXTENSION-DYADIC.md +0 -204
  96. package/docs/research/PAPER-UNIFIED.md +0 -659
  97. package/docs/research/PAPER-UNIFIED.pdf +0 -0
  98. package/docs/research/PROMPT-IMPROVEMENTS-2026-01-14.md +0 -356
  99. package/docs/research/SESSION-NOTES-2026-01-11-RECOGNITION-EVAL.md +0 -419
  100. package/docs/research/archive/PAPER-DRAFT-RECOGNITION-TUTORING.md +0 -1637
  101. package/docs/research/archive/paper-multiagent-tutor.tex +0 -978
  102. package/docs/research/paper-draft/full-paper.md +0 -136
  103. package/docs/research/paper-draft/images/pasted-image-2026-01-24T03-47-47-846Z-d76a7ae2.png +0 -0
  104. package/docs/research/paper-draft/references.bib +0 -515
  105. package/docs/research/transcript-baseline.md +0 -139
  106. package/docs/research/transcript-recognition-multiagent.md +0 -187
  107. package/hooks/useEvalData.ts +0 -625
  108. package/server-init.js +0 -45
  109. package/services/benchmarkService.js +0 -1892
  110. package/types.ts +0 -165
  111. 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
+ };