decision_agent 0.3.0 → 1.0.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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +272 -7
  3. data/lib/decision_agent/agent.rb +72 -1
  4. data/lib/decision_agent/context.rb +1 -0
  5. data/lib/decision_agent/data_enrichment/cache/memory_adapter.rb +86 -0
  6. data/lib/decision_agent/data_enrichment/cache_adapter.rb +49 -0
  7. data/lib/decision_agent/data_enrichment/circuit_breaker.rb +135 -0
  8. data/lib/decision_agent/data_enrichment/client.rb +220 -0
  9. data/lib/decision_agent/data_enrichment/config.rb +78 -0
  10. data/lib/decision_agent/data_enrichment/errors.rb +36 -0
  11. data/lib/decision_agent/decision.rb +102 -2
  12. data/lib/decision_agent/dmn/feel/evaluator.rb +28 -6
  13. data/lib/decision_agent/dsl/condition_evaluator.rb +982 -839
  14. data/lib/decision_agent/dsl/schema_validator.rb +51 -13
  15. data/lib/decision_agent/evaluators/dmn_evaluator.rb +106 -19
  16. data/lib/decision_agent/evaluators/json_rule_evaluator.rb +69 -9
  17. data/lib/decision_agent/explainability/condition_trace.rb +83 -0
  18. data/lib/decision_agent/explainability/explainability_result.rb +52 -0
  19. data/lib/decision_agent/explainability/rule_trace.rb +39 -0
  20. data/lib/decision_agent/explainability/trace_collector.rb +24 -0
  21. data/lib/decision_agent/monitoring/alert_manager.rb +5 -1
  22. data/lib/decision_agent/simulation/errors.rb +18 -0
  23. data/lib/decision_agent/simulation/impact_analyzer.rb +498 -0
  24. data/lib/decision_agent/simulation/monte_carlo_simulator.rb +635 -0
  25. data/lib/decision_agent/simulation/replay_engine.rb +486 -0
  26. data/lib/decision_agent/simulation/scenario_engine.rb +318 -0
  27. data/lib/decision_agent/simulation/scenario_library.rb +163 -0
  28. data/lib/decision_agent/simulation/shadow_test_engine.rb +287 -0
  29. data/lib/decision_agent/simulation/what_if_analyzer.rb +1002 -0
  30. data/lib/decision_agent/simulation.rb +17 -0
  31. data/lib/decision_agent/version.rb +1 -1
  32. data/lib/decision_agent/versioning/activerecord_adapter.rb +23 -8
  33. data/lib/decision_agent/web/public/app.js +119 -0
  34. data/lib/decision_agent/web/public/index.html +49 -0
  35. data/lib/decision_agent/web/public/simulation.html +130 -0
  36. data/lib/decision_agent/web/public/simulation_impact.html +478 -0
  37. data/lib/decision_agent/web/public/simulation_replay.html +551 -0
  38. data/lib/decision_agent/web/public/simulation_shadow.html +546 -0
  39. data/lib/decision_agent/web/public/simulation_whatif.html +532 -0
  40. data/lib/decision_agent/web/public/styles.css +65 -0
  41. data/lib/decision_agent/web/server.rb +594 -23
  42. data/lib/decision_agent.rb +60 -2
  43. metadata +53 -73
  44. data/spec/ab_testing/ab_test_assignment_spec.rb +0 -253
  45. data/spec/ab_testing/ab_test_manager_spec.rb +0 -612
  46. data/spec/ab_testing/ab_test_spec.rb +0 -270
  47. data/spec/ab_testing/ab_testing_agent_spec.rb +0 -655
  48. data/spec/ab_testing/storage/adapter_spec.rb +0 -64
  49. data/spec/ab_testing/storage/memory_adapter_spec.rb +0 -485
  50. data/spec/activerecord_thread_safety_spec.rb +0 -553
  51. data/spec/advanced_operators_spec.rb +0 -3150
  52. data/spec/agent_spec.rb +0 -289
  53. data/spec/api_contract_spec.rb +0 -430
  54. data/spec/audit_adapters_spec.rb +0 -92
  55. data/spec/auth/access_audit_logger_spec.rb +0 -394
  56. data/spec/auth/authenticator_spec.rb +0 -112
  57. data/spec/auth/password_reset_spec.rb +0 -294
  58. data/spec/auth/permission_checker_spec.rb +0 -207
  59. data/spec/auth/permission_spec.rb +0 -73
  60. data/spec/auth/rbac_adapter_spec.rb +0 -778
  61. data/spec/auth/rbac_config_spec.rb +0 -82
  62. data/spec/auth/role_spec.rb +0 -51
  63. data/spec/auth/session_manager_spec.rb +0 -172
  64. data/spec/auth/session_spec.rb +0 -112
  65. data/spec/auth/user_spec.rb +0 -130
  66. data/spec/comprehensive_edge_cases_spec.rb +0 -1777
  67. data/spec/context_spec.rb +0 -127
  68. data/spec/decision_agent_spec.rb +0 -96
  69. data/spec/decision_spec.rb +0 -423
  70. data/spec/dmn/decision_graph_spec.rb +0 -282
  71. data/spec/dmn/decision_tree_spec.rb +0 -203
  72. data/spec/dmn/feel/errors_spec.rb +0 -18
  73. data/spec/dmn/feel/functions_spec.rb +0 -400
  74. data/spec/dmn/feel/simple_parser_spec.rb +0 -274
  75. data/spec/dmn/feel/types_spec.rb +0 -176
  76. data/spec/dmn/feel_parser_spec.rb +0 -489
  77. data/spec/dmn/hit_policy_spec.rb +0 -202
  78. data/spec/dmn/integration_spec.rb +0 -226
  79. data/spec/dsl/condition_evaluator_spec.rb +0 -774
  80. data/spec/dsl_validation_spec.rb +0 -648
  81. data/spec/edge_cases_spec.rb +0 -353
  82. data/spec/evaluation_spec.rb +0 -364
  83. data/spec/evaluation_validator_spec.rb +0 -165
  84. data/spec/examples/feedback_aware_evaluator_spec.rb +0 -460
  85. data/spec/examples.txt +0 -1909
  86. data/spec/fixtures/dmn/complex_decision.dmn +0 -81
  87. data/spec/fixtures/dmn/invalid_structure.dmn +0 -31
  88. data/spec/fixtures/dmn/simple_decision.dmn +0 -40
  89. data/spec/issue_verification_spec.rb +0 -759
  90. data/spec/json_rule_evaluator_spec.rb +0 -587
  91. data/spec/monitoring/alert_manager_spec.rb +0 -378
  92. data/spec/monitoring/metrics_collector_spec.rb +0 -501
  93. data/spec/monitoring/monitored_agent_spec.rb +0 -225
  94. data/spec/monitoring/prometheus_exporter_spec.rb +0 -242
  95. data/spec/monitoring/storage/activerecord_adapter_spec.rb +0 -498
  96. data/spec/monitoring/storage/base_adapter_spec.rb +0 -61
  97. data/spec/monitoring/storage/memory_adapter_spec.rb +0 -247
  98. data/spec/performance_optimizations_spec.rb +0 -493
  99. data/spec/replay_edge_cases_spec.rb +0 -699
  100. data/spec/replay_spec.rb +0 -210
  101. data/spec/rfc8785_canonicalization_spec.rb +0 -215
  102. data/spec/scoring_spec.rb +0 -225
  103. data/spec/spec_helper.rb +0 -60
  104. data/spec/testing/batch_test_importer_spec.rb +0 -693
  105. data/spec/testing/batch_test_runner_spec.rb +0 -307
  106. data/spec/testing/test_coverage_analyzer_spec.rb +0 -292
  107. data/spec/testing/test_result_comparator_spec.rb +0 -392
  108. data/spec/testing/test_scenario_spec.rb +0 -113
  109. data/spec/thread_safety_spec.rb +0 -490
  110. data/spec/thread_safety_spec.rb.broken +0 -878
  111. data/spec/versioning/adapter_spec.rb +0 -156
  112. data/spec/versioning_spec.rb +0 -1030
  113. data/spec/web/middleware/auth_middleware_spec.rb +0 -133
  114. data/spec/web/middleware/permission_middleware_spec.rb +0 -247
  115. data/spec/web_ui_rack_spec.rb +0 -2134
@@ -0,0 +1,478 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Impact Analysis</title>
7
+ <link rel="stylesheet" href="styles.css">
8
+ <style>
9
+ .simulation-container {
10
+ max-width: 1400px;
11
+ margin: 0 auto;
12
+ padding: 20px;
13
+ }
14
+
15
+ .step-section {
16
+ background: var(--panel-bg);
17
+ border-radius: 10px;
18
+ padding: 20px;
19
+ margin-bottom: 20px;
20
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
21
+ }
22
+
23
+ .metrics-grid {
24
+ display: grid;
25
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
26
+ gap: 15px;
27
+ margin-top: 15px;
28
+ }
29
+
30
+ .metric-card {
31
+ background: var(--hover-bg);
32
+ padding: 15px;
33
+ border-radius: 8px;
34
+ text-align: center;
35
+ }
36
+
37
+ .metric-value {
38
+ font-size: 2rem;
39
+ font-weight: bold;
40
+ color: var(--primary-color);
41
+ }
42
+
43
+ .metric-label {
44
+ font-size: 0.9rem;
45
+ color: var(--text-secondary);
46
+ margin-top: 5px;
47
+ }
48
+
49
+ .risk-badge {
50
+ display: inline-block;
51
+ padding: 8px 16px;
52
+ border-radius: 20px;
53
+ font-weight: 600;
54
+ font-size: 1.1rem;
55
+ }
56
+
57
+ .risk-low {
58
+ background: #d1fae5;
59
+ color: #065f46;
60
+ }
61
+
62
+ .risk-medium {
63
+ background: #fef3c7;
64
+ color: #92400e;
65
+ }
66
+
67
+ .risk-high {
68
+ background: #fed7aa;
69
+ color: #9a3412;
70
+ }
71
+
72
+ .risk-critical {
73
+ background: #fee2e2;
74
+ color: #991b1b;
75
+ }
76
+
77
+ .hidden {
78
+ display: none;
79
+ }
80
+
81
+ .error-message {
82
+ background: #fee2e2;
83
+ color: #991b1b;
84
+ padding: 12px;
85
+ border-radius: 6px;
86
+ margin-top: 10px;
87
+ }
88
+
89
+ .success-message {
90
+ background: #d1fae5;
91
+ color: #065f46;
92
+ padding: 12px;
93
+ border-radius: 6px;
94
+ margin-top: 10px;
95
+ }
96
+
97
+ .header-links {
98
+ margin-top: 20px;
99
+ }
100
+
101
+ .header-links a {
102
+ margin-right: 15px;
103
+ text-decoration: none;
104
+ color: var(--primary-color);
105
+ }
106
+
107
+ .file-upload-area {
108
+ border: 2px dashed var(--border-color);
109
+ border-radius: 8px;
110
+ padding: 40px;
111
+ text-align: center;
112
+ cursor: pointer;
113
+ transition: all 0.3s;
114
+ }
115
+
116
+ .file-upload-area:hover {
117
+ border-color: var(--primary-color);
118
+ background: var(--hover-bg);
119
+ }
120
+ </style>
121
+ </head>
122
+ <body>
123
+ <div class="simulation-container">
124
+ <header>
125
+ <h1>📊 Impact Analysis</h1>
126
+ <p class="subtitle">Quantify the impact of rule changes before deploying to production</p>
127
+ <div class="header-links">
128
+ <a href="/simulation">← Back to Simulation Dashboard</a>
129
+ </div>
130
+ </header>
131
+
132
+ <!-- Step 1: Select Versions -->
133
+ <div class="step-section">
134
+ <h2>Select Versions to Compare</h2>
135
+
136
+ <div class="form-group">
137
+ <label for="baselineVersion">Baseline Version:</label>
138
+ <select id="baselineVersion" class="input">
139
+ <option value="">-- Select Baseline Version --</option>
140
+ </select>
141
+ </div>
142
+
143
+ <div class="form-group">
144
+ <label for="proposedVersion">Proposed Version:</label>
145
+ <select id="proposedVersion" class="input">
146
+ <option value="">-- Select Proposed Version --</option>
147
+ </select>
148
+ </div>
149
+ </div>
150
+
151
+ <!-- Step 2: Upload Test Data -->
152
+ <div class="step-section">
153
+ <h2>Upload Test Data</h2>
154
+ <p>Upload a CSV or JSON file with test contexts, or paste JSON directly</p>
155
+
156
+ <div class="file-upload-area" id="uploadArea">
157
+ <p>📁 Drag and drop your CSV/JSON file here, or click to browse</p>
158
+ <input type="file" id="fileInput" accept=".csv,.json" style="display: none;">
159
+ </div>
160
+
161
+ <div class="form-group" style="margin-top: 20px;">
162
+ <label for="testDataJson">Or paste test data JSON:</label>
163
+ <textarea id="testDataJson" class="input" rows="5" placeholder='[{"credit_score": 650, "amount": 100000}, ...]'></textarea>
164
+ </div>
165
+
166
+ <div id="uploadStatus" class="hidden"></div>
167
+ </div>
168
+
169
+ <!-- Step 3: Run Analysis -->
170
+ <div class="step-section">
171
+ <h2>Run Impact Analysis</h2>
172
+
173
+ <div class="form-group">
174
+ <label>
175
+ <input type="checkbox" id="calculateRisk" checked> Calculate risk score
176
+ </label>
177
+ </div>
178
+ <div class="form-group">
179
+ <label>
180
+ <input type="checkbox" id="parallelExecution" checked> Parallel execution
181
+ </label>
182
+ </div>
183
+ <div class="form-group">
184
+ <label for="threadCount">Thread count:</label>
185
+ <input type="number" id="threadCount" value="4" min="1" max="16" class="input" style="width: 100px;">
186
+ </div>
187
+
188
+ <button class="btn btn-primary" id="runAnalysisBtn" disabled>▶ Run Impact Analysis</button>
189
+
190
+ <div id="runStatus" class="hidden"></div>
191
+ </div>
192
+
193
+ <!-- Step 4: Results -->
194
+ <div class="step-section" id="resultsSection" style="display: none;">
195
+ <h2>Impact Analysis Results</h2>
196
+
197
+ <!-- Risk Score -->
198
+ <div id="riskSection" class="hidden">
199
+ <h3>Risk Assessment</h3>
200
+ <div style="margin: 20px 0;">
201
+ <div style="font-size: 1.5rem; margin-bottom: 10px;">Risk Level: <span id="riskLevel" class="risk-badge"></span></div>
202
+ <div style="font-size: 1.2rem;">Risk Score: <span id="riskScore"></span></div>
203
+ </div>
204
+ </div>
205
+
206
+ <!-- Statistics -->
207
+ <div id="statisticsSection" class="hidden">
208
+ <h3>Impact Statistics</h3>
209
+ <div class="metrics-grid" id="statisticsGrid"></div>
210
+ </div>
211
+
212
+ <!-- Decision Distribution -->
213
+ <div id="distributionSection" class="hidden">
214
+ <h3>Decision Distribution Changes</h3>
215
+ <div id="distributionContent"></div>
216
+ </div>
217
+
218
+ <!-- Confidence Impact -->
219
+ <div id="confidenceSection" class="hidden">
220
+ <h3>Confidence Impact</h3>
221
+ <div id="confidenceContent"></div>
222
+ </div>
223
+ </div>
224
+ </div>
225
+
226
+ <script>
227
+ function getBasePath() {
228
+ const baseTag = document.querySelector('base');
229
+ if (baseTag && baseTag.href) {
230
+ try {
231
+ const baseUrl = new URL(baseTag.href, window.location.href);
232
+ let path = baseUrl.pathname;
233
+ if (path && !path.endsWith('/')) path += '/';
234
+ return path;
235
+ } catch (e) {
236
+ if (baseTag.href.startsWith('/')) {
237
+ const match = baseTag.href.match(/^(https?:\/\/[^\/]+)?(\/.*?)\/?$/);
238
+ if (match && match[2]) {
239
+ return match[2].endsWith('/') ? match[2] : match[2] + '/';
240
+ }
241
+ }
242
+ }
243
+ }
244
+ const pathname = window.location.pathname;
245
+ if (pathname.includes('/decision_agent')) {
246
+ const match = pathname.match(/^(\/.*?\/decision_agent)\/?/);
247
+ if (match) {
248
+ return match[1].endsWith('/') ? match[1] : match[1] + '/';
249
+ }
250
+ }
251
+ return pathname.substring(0, pathname.lastIndexOf('/') + 1) || '/';
252
+ }
253
+
254
+ const basePath = getBasePath();
255
+ let testData = null;
256
+
257
+ // Load versions
258
+ function loadVersions() {
259
+ fetch(`${basePath}api/versions`)
260
+ .then(response => response.json())
261
+ .then(data => {
262
+ const baselineSelect = document.getElementById('baselineVersion');
263
+ const proposedSelect = document.getElementById('proposedVersion');
264
+
265
+ baselineSelect.innerHTML = '<option value="">-- Select Baseline Version --</option>';
266
+ proposedSelect.innerHTML = '<option value="">-- Select Proposed Version --</option>';
267
+
268
+ if (data.versions) {
269
+ data.versions.forEach(v => {
270
+ const option1 = document.createElement('option');
271
+ option1.value = v.id;
272
+ option1.textContent = `${v.rule_id} v${v.version_number} (${v.status})`;
273
+ baselineSelect.appendChild(option1);
274
+
275
+ const option2 = document.createElement('option');
276
+ option2.value = v.id;
277
+ option2.textContent = `${v.rule_id} v${v.version_number} (${v.status})`;
278
+ proposedSelect.appendChild(option2);
279
+ });
280
+ }
281
+ })
282
+ .catch(error => console.error('Error loading versions:', error));
283
+ }
284
+
285
+ loadVersions();
286
+
287
+ // File upload
288
+ const uploadArea = document.getElementById('uploadArea');
289
+ const fileInput = document.getElementById('fileInput');
290
+ const uploadStatus = document.getElementById('uploadStatus');
291
+
292
+ uploadArea.addEventListener('click', () => fileInput.click());
293
+ uploadArea.addEventListener('dragover', (e) => {
294
+ e.preventDefault();
295
+ uploadArea.classList.add('dragover');
296
+ });
297
+ uploadArea.addEventListener('dragleave', () => {
298
+ uploadArea.classList.remove('dragover');
299
+ });
300
+ uploadArea.addEventListener('drop', (e) => {
301
+ e.preventDefault();
302
+ uploadArea.classList.remove('dragover');
303
+ const files = e.dataTransfer.files;
304
+ if (files.length > 0) {
305
+ handleFileUpload(files[0]);
306
+ }
307
+ });
308
+ fileInput.addEventListener('change', (e) => {
309
+ if (e.target.files.length > 0) {
310
+ handleFileUpload(e.target.files[0]);
311
+ }
312
+ });
313
+
314
+ function handleFileUpload(file) {
315
+ const reader = new FileReader();
316
+ reader.onload = (e) => {
317
+ try {
318
+ if (file.name.endsWith('.json')) {
319
+ testData = JSON.parse(e.target.result);
320
+ } else if (file.name.endsWith('.csv')) {
321
+ const lines = e.target.result.split('\n');
322
+ const headers = lines[0].split(',');
323
+ testData = lines.slice(1).filter(l => l.trim()).map(line => {
324
+ const values = line.split(',');
325
+ const obj = {};
326
+ headers.forEach((h, i) => {
327
+ obj[h.trim()] = values[i]?.trim() || '';
328
+ });
329
+ return obj;
330
+ });
331
+ }
332
+
333
+ uploadStatus.className = 'success-message';
334
+ uploadStatus.textContent = `Successfully loaded ${testData.length} test contexts`;
335
+ uploadStatus.classList.remove('hidden');
336
+ checkReady();
337
+ } catch (error) {
338
+ uploadStatus.className = 'error-message';
339
+ uploadStatus.textContent = `Error: ${error.message}`;
340
+ uploadStatus.classList.remove('hidden');
341
+ }
342
+ };
343
+ reader.readAsText(file);
344
+ }
345
+
346
+ // Test data JSON input
347
+ document.getElementById('testDataJson').addEventListener('input', (e) => {
348
+ try {
349
+ if (e.target.value.trim()) {
350
+ testData = JSON.parse(e.target.value);
351
+ uploadStatus.className = 'success-message';
352
+ uploadStatus.textContent = `Loaded ${testData.length} test contexts from JSON`;
353
+ uploadStatus.classList.remove('hidden');
354
+ } else {
355
+ testData = null;
356
+ uploadStatus.classList.add('hidden');
357
+ }
358
+ checkReady();
359
+ } catch (error) {
360
+ testData = null;
361
+ uploadStatus.className = 'error-message';
362
+ uploadStatus.textContent = `Invalid JSON: ${error.message}`;
363
+ uploadStatus.classList.remove('hidden');
364
+ }
365
+ });
366
+
367
+ function checkReady() {
368
+ const baseline = document.getElementById('baselineVersion').value;
369
+ const proposed = document.getElementById('proposedVersion').value;
370
+ document.getElementById('runAnalysisBtn').disabled = !(baseline && proposed && testData);
371
+ }
372
+
373
+ document.getElementById('baselineVersion').addEventListener('change', checkReady);
374
+ document.getElementById('proposedVersion').addEventListener('change', checkReady);
375
+
376
+ // Run analysis
377
+ document.getElementById('runAnalysisBtn').addEventListener('click', () => {
378
+ const baselineVersion = document.getElementById('baselineVersion').value;
379
+ const proposedVersion = document.getElementById('proposedVersion').value;
380
+
381
+ if (!baselineVersion || !proposedVersion || !testData) {
382
+ alert('Please select both versions and provide test data');
383
+ return;
384
+ }
385
+
386
+ const runStatus = document.getElementById('runStatus');
387
+ runStatus.className = 'hidden';
388
+
389
+ const requestData = {
390
+ baseline_version: baselineVersion,
391
+ proposed_version: proposedVersion,
392
+ test_data: testData,
393
+ options: {
394
+ calculate_risk: document.getElementById('calculateRisk').checked,
395
+ parallel: document.getElementById('parallelExecution').checked,
396
+ thread_count: parseInt(document.getElementById('threadCount').value) || 4
397
+ }
398
+ };
399
+
400
+ fetch(`${basePath}api/simulation/impact`, {
401
+ method: 'POST',
402
+ headers: { 'Content-Type': 'application/json' },
403
+ body: JSON.stringify(requestData)
404
+ })
405
+ .then(response => response.json())
406
+ .then(data => {
407
+ if (data.error) {
408
+ runStatus.className = 'error-message';
409
+ runStatus.textContent = `Error: ${data.error}`;
410
+ runStatus.classList.remove('hidden');
411
+ } else {
412
+ runStatus.className = 'success-message';
413
+ runStatus.textContent = 'Impact analysis completed successfully!';
414
+ runStatus.classList.remove('hidden');
415
+ displayResults(data);
416
+ }
417
+ })
418
+ .catch(error => {
419
+ runStatus.className = 'error-message';
420
+ runStatus.textContent = `Error: ${error.message}`;
421
+ runStatus.classList.remove('hidden');
422
+ });
423
+ });
424
+
425
+ function displayResults(data) {
426
+ document.getElementById('resultsSection').style.display = 'block';
427
+ const results = data.results;
428
+
429
+ // Risk assessment
430
+ if (results.risk_score !== undefined) {
431
+ const riskLevel = document.getElementById('riskLevel');
432
+ const riskScore = document.getElementById('riskScore');
433
+ riskScore.textContent = results.risk_score.toFixed(3);
434
+
435
+ const level = results.risk_level || 'low';
436
+ riskLevel.textContent = level.toUpperCase();
437
+ riskLevel.className = 'risk-badge risk-' + level;
438
+
439
+ document.getElementById('riskSection').classList.remove('hidden');
440
+ }
441
+
442
+ // Statistics
443
+ if (results.decision_changes !== undefined) {
444
+ const statsGrid = document.getElementById('statisticsGrid');
445
+ statsGrid.innerHTML = `
446
+ <div class="metric-card">
447
+ <div class="metric-value">${results.decision_changes}</div>
448
+ <div class="metric-label">Decision Changes</div>
449
+ </div>
450
+ <div class="metric-card">
451
+ <div class="metric-value">${(results.change_rate * 100).toFixed(2)}%</div>
452
+ <div class="metric-label">Change Rate</div>
453
+ </div>
454
+ `;
455
+ document.getElementById('statisticsSection').classList.remove('hidden');
456
+ }
457
+
458
+ // Decision distribution
459
+ if (results.decision_distribution) {
460
+ const distContent = document.getElementById('distributionContent');
461
+ let html = '<h4>Baseline:</h4><pre>' + JSON.stringify(results.decision_distribution.baseline, null, 2) + '</pre>';
462
+ html += '<h4>Proposed:</h4><pre>' + JSON.stringify(results.decision_distribution.proposed, null, 2) + '</pre>';
463
+ distContent.innerHTML = html;
464
+ document.getElementById('distributionSection').classList.remove('hidden');
465
+ }
466
+
467
+ // Confidence impact
468
+ if (results.confidence_impact) {
469
+ const confContent = document.getElementById('confidenceContent');
470
+ let html = '<pre>' + JSON.stringify(results.confidence_impact, null, 2) + '</pre>';
471
+ confContent.innerHTML = html;
472
+ document.getElementById('confidenceSection').classList.remove('hidden');
473
+ }
474
+ }
475
+ </script>
476
+ </body>
477
+ </html>
478
+