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,532 @@
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>What-If 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
+ .scenario-builder {
24
+ margin-top: 15px;
25
+ }
26
+
27
+ .scenario-item {
28
+ background: var(--hover-bg);
29
+ padding: 15px;
30
+ border-radius: 8px;
31
+ margin-bottom: 10px;
32
+ }
33
+
34
+ .scenario-item-header {
35
+ display: flex;
36
+ justify-content: space-between;
37
+ align-items: center;
38
+ margin-bottom: 10px;
39
+ }
40
+
41
+ .scenario-fields {
42
+ display: grid;
43
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
44
+ gap: 10px;
45
+ }
46
+
47
+ .field-input {
48
+ display: flex;
49
+ flex-direction: column;
50
+ }
51
+
52
+ .field-input label {
53
+ font-size: 0.85rem;
54
+ color: var(--text-secondary);
55
+ margin-bottom: 5px;
56
+ }
57
+
58
+ .metrics-grid {
59
+ display: grid;
60
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
61
+ gap: 15px;
62
+ margin-top: 15px;
63
+ }
64
+
65
+ .metric-card {
66
+ background: var(--hover-bg);
67
+ padding: 15px;
68
+ border-radius: 8px;
69
+ text-align: center;
70
+ }
71
+
72
+ .metric-value {
73
+ font-size: 2rem;
74
+ font-weight: bold;
75
+ color: var(--primary-color);
76
+ }
77
+
78
+ .metric-label {
79
+ font-size: 0.9rem;
80
+ color: var(--text-secondary);
81
+ margin-top: 5px;
82
+ }
83
+
84
+ .hidden {
85
+ display: none;
86
+ }
87
+
88
+ .error-message {
89
+ background: #fee2e2;
90
+ color: #991b1b;
91
+ padding: 12px;
92
+ border-radius: 6px;
93
+ margin-top: 10px;
94
+ }
95
+
96
+ .success-message {
97
+ background: #d1fae5;
98
+ color: #065f46;
99
+ padding: 12px;
100
+ border-radius: 6px;
101
+ margin-top: 10px;
102
+ }
103
+
104
+ .header-links {
105
+ margin-top: 20px;
106
+ }
107
+
108
+ .header-links a {
109
+ margin-right: 15px;
110
+ text-decoration: none;
111
+ color: var(--primary-color);
112
+ }
113
+
114
+ .results-table {
115
+ width: 100%;
116
+ border-collapse: collapse;
117
+ margin-top: 15px;
118
+ }
119
+
120
+ .results-table th,
121
+ .results-table td {
122
+ padding: 10px;
123
+ text-align: left;
124
+ border-bottom: 1px solid var(--border-color);
125
+ }
126
+
127
+ .results-table th {
128
+ background: var(--hover-bg);
129
+ font-weight: 600;
130
+ }
131
+ </style>
132
+ </head>
133
+ <body>
134
+ <div class="simulation-container">
135
+ <header>
136
+ <h1>❓ What-If Analysis</h1>
137
+ <p class="subtitle">Simulate different scenarios and analyze how decisions change</p>
138
+ <div class="header-links">
139
+ <a href="/simulation">← Back to Simulation Dashboard</a>
140
+ </div>
141
+ </header>
142
+
143
+ <!-- Step 1: Configure Rules -->
144
+ <div class="step-section">
145
+ <h2>Configure Rules</h2>
146
+ <p>Paste your rules JSON or select a version to use</p>
147
+
148
+ <div class="form-group">
149
+ <label for="rulesJson">Rules JSON:</label>
150
+ <textarea id="rulesJson" class="input" rows="10" placeholder='{"version": "1.0", "ruleset": "my_ruleset", "rules": [...]}'></textarea>
151
+ </div>
152
+
153
+ <div class="form-group">
154
+ <label for="ruleVersion">Or select version:</label>
155
+ <select id="ruleVersion" class="input">
156
+ <option value="">-- Select Version --</option>
157
+ </select>
158
+ </div>
159
+
160
+ <div class="actions">
161
+ <label for="importRulesFile" class="btn btn-secondary">📁 Import Rules JSON
162
+ <input type="file" id="importRulesFile" accept=".json" style="display: none;">
163
+ </label>
164
+ <button class="btn btn-secondary" id="validateRulesBtn">✓ Validate Rules</button>
165
+ </div>
166
+ </div>
167
+
168
+ <!-- Step 2: Define Scenarios -->
169
+ <div class="step-section">
170
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
171
+ <h2>Define Scenarios</h2>
172
+ <button class="btn btn-primary" id="addScenarioBtn">+ Add Scenario</button>
173
+ </div>
174
+
175
+ <div id="scenariosContainer" class="scenario-builder">
176
+ <div class="scenario-item">
177
+ <div class="scenario-item-header">
178
+ <strong>Scenario 1</strong>
179
+ <button class="btn btn-secondary" onclick="removeScenario(this)" style="padding: 5px 10px;">Remove</button>
180
+ </div>
181
+ <div class="scenario-fields" id="scenarioFields1">
182
+ <div class="field-input">
183
+ <label>Field Name</label>
184
+ <input type="text" class="input scenario-field-name" placeholder="e.g., credit_score">
185
+ </div>
186
+ <div class="field-input">
187
+ <label>Value</label>
188
+ <input type="text" class="input scenario-field-value" placeholder="e.g., 650">
189
+ </div>
190
+ </div>
191
+ <button class="btn btn-secondary" onclick="addField(this)" style="margin-top: 10px; padding: 5px 10px;">+ Add Field</button>
192
+ </div>
193
+ </div>
194
+ </div>
195
+
196
+ <!-- Step 3: Run Analysis -->
197
+ <div class="step-section">
198
+ <h2>Run What-If Analysis</h2>
199
+
200
+ <div class="form-group">
201
+ <label>
202
+ <input type="checkbox" id="parallelExecution" checked> Parallel execution
203
+ </label>
204
+ </div>
205
+ <div class="form-group">
206
+ <label>
207
+ <input type="checkbox" id="sensitivityAnalysis"> Perform sensitivity analysis
208
+ </label>
209
+ </div>
210
+ <div class="form-group">
211
+ <label for="threadCount">Thread count:</label>
212
+ <input type="number" id="threadCount" value="4" min="1" max="16" class="input" style="width: 100px;">
213
+ </div>
214
+
215
+ <button class="btn btn-primary" id="runAnalysisBtn">▶ Run Analysis</button>
216
+
217
+ <div id="runStatus" class="hidden"></div>
218
+ </div>
219
+
220
+ <!-- Step 4: Results -->
221
+ <div class="step-section" id="resultsSection" style="display: none;">
222
+ <h2>Analysis Results</h2>
223
+
224
+ <!-- Statistics -->
225
+ <div id="statisticsSection" class="hidden">
226
+ <h3>Statistics</h3>
227
+ <div class="metrics-grid" id="statisticsGrid"></div>
228
+ </div>
229
+
230
+ <!-- Decision Distribution -->
231
+ <div id="distributionSection" class="hidden">
232
+ <h3>Decision Distribution</h3>
233
+ <div id="distributionContent"></div>
234
+ </div>
235
+
236
+ <!-- Scenario Results -->
237
+ <div id="scenarioResultsSection" class="hidden">
238
+ <h3>Scenario Results</h3>
239
+ <div id="scenarioResultsContent"></div>
240
+ </div>
241
+
242
+ <!-- Sensitivity Analysis -->
243
+ <div id="sensitivitySection" class="hidden">
244
+ <h3>Sensitivity Analysis</h3>
245
+ <div id="sensitivityContent"></div>
246
+ </div>
247
+ </div>
248
+ </div>
249
+
250
+ <script>
251
+ function getBasePath() {
252
+ const baseTag = document.querySelector('base');
253
+ if (baseTag && baseTag.href) {
254
+ try {
255
+ const baseUrl = new URL(baseTag.href, window.location.href);
256
+ let path = baseUrl.pathname;
257
+ if (path && !path.endsWith('/')) path += '/';
258
+ return path;
259
+ } catch (e) {
260
+ if (baseTag.href.startsWith('/')) {
261
+ const match = baseTag.href.match(/^(https?:\/\/[^\/]+)?(\/.*?)\/?$/);
262
+ if (match && match[2]) {
263
+ return match[2].endsWith('/') ? match[2] : match[2] + '/';
264
+ }
265
+ }
266
+ }
267
+ }
268
+ const pathname = window.location.pathname;
269
+ if (pathname.includes('/decision_agent')) {
270
+ const match = pathname.match(/^(\/.*?\/decision_agent)\/?/);
271
+ if (match) {
272
+ return match[1].endsWith('/') ? match[1] : match[1] + '/';
273
+ }
274
+ }
275
+ return pathname.substring(0, pathname.lastIndexOf('/') + 1) || '/';
276
+ }
277
+
278
+ const basePath = getBasePath();
279
+ let scenarioCount = 1;
280
+
281
+ // Load versions
282
+ function loadVersions() {
283
+ fetch(`${basePath}api/versions`)
284
+ .then(response => response.json())
285
+ .then(data => {
286
+ const select = document.getElementById('ruleVersion');
287
+ select.innerHTML = '<option value="">-- Select Version --</option>';
288
+ if (data.versions) {
289
+ data.versions.forEach(v => {
290
+ const option = document.createElement('option');
291
+ option.value = v.id;
292
+ option.textContent = `${v.rule_id} v${v.version_number} (${v.status})`;
293
+ select.appendChild(option);
294
+ });
295
+ }
296
+ })
297
+ .catch(error => console.error('Error loading versions:', error));
298
+ }
299
+
300
+ loadVersions();
301
+
302
+ // Add scenario
303
+ document.getElementById('addScenarioBtn').addEventListener('click', () => {
304
+ scenarioCount++;
305
+ const container = document.getElementById('scenariosContainer');
306
+ const scenario = document.createElement('div');
307
+ scenario.className = 'scenario-item';
308
+ scenario.innerHTML = `
309
+ <div class="scenario-item-header">
310
+ <strong>Scenario ${scenarioCount}</strong>
311
+ <button class="btn btn-secondary" onclick="removeScenario(this)" style="padding: 5px 10px;">Remove</button>
312
+ </div>
313
+ <div class="scenario-fields" id="scenarioFields${scenarioCount}">
314
+ <div class="field-input">
315
+ <label>Field Name</label>
316
+ <input type="text" class="input scenario-field-name" placeholder="e.g., credit_score">
317
+ </div>
318
+ <div class="field-input">
319
+ <label>Value</label>
320
+ <input type="text" class="input scenario-field-value" placeholder="e.g., 650">
321
+ </div>
322
+ </div>
323
+ <button class="btn btn-secondary" onclick="addField(this)" style="margin-top: 10px; padding: 5px 10px;">+ Add Field</button>
324
+ `;
325
+ container.appendChild(scenario);
326
+ });
327
+
328
+ function removeScenario(btn) {
329
+ btn.closest('.scenario-item').remove();
330
+ }
331
+
332
+ function addField(btn) {
333
+ const fieldsContainer = btn.previousElementSibling;
334
+ const fieldDiv = document.createElement('div');
335
+ fieldDiv.className = 'field-input';
336
+ fieldDiv.innerHTML = `
337
+ <label>Field Name</label>
338
+ <input type="text" class="input scenario-field-name" placeholder="e.g., amount">
339
+ <label style="margin-top: 5px;">Value</label>
340
+ <input type="text" class="input scenario-field-value" placeholder="e.g., 100000">
341
+ `;
342
+ fieldsContainer.appendChild(fieldDiv);
343
+ }
344
+
345
+ // Import rules
346
+ document.getElementById('importRulesFile').addEventListener('change', (e) => {
347
+ const file = e.target.files[0];
348
+ if (file) {
349
+ const reader = new FileReader();
350
+ reader.onload = (e) => {
351
+ try {
352
+ const json = JSON.parse(e.target.result);
353
+ document.getElementById('rulesJson').value = JSON.stringify(json, null, 2);
354
+ } catch (error) {
355
+ alert('Invalid JSON file: ' + error.message);
356
+ }
357
+ };
358
+ reader.readAsText(file);
359
+ }
360
+ });
361
+
362
+ // Validate rules
363
+ document.getElementById('validateRulesBtn').addEventListener('click', () => {
364
+ const rulesJson = document.getElementById('rulesJson').value;
365
+ try {
366
+ JSON.parse(rulesJson);
367
+ fetch(`${basePath}api/validate`, {
368
+ method: 'POST',
369
+ headers: { 'Content-Type': 'application/json' },
370
+ body: JSON.stringify(JSON.parse(rulesJson))
371
+ })
372
+ .then(response => response.json())
373
+ .then(data => {
374
+ if (data.valid) {
375
+ alert('Rules are valid!');
376
+ } else {
377
+ alert('Validation errors: ' + data.errors.join(', '));
378
+ }
379
+ });
380
+ } catch (error) {
381
+ alert('Invalid JSON: ' + error.message);
382
+ }
383
+ });
384
+
385
+ // Run analysis
386
+ document.getElementById('runAnalysisBtn').addEventListener('click', () => {
387
+ // Collect scenarios
388
+ const scenarios = [];
389
+ document.querySelectorAll('.scenario-item').forEach(item => {
390
+ const scenario = {};
391
+ const fields = item.querySelectorAll('.scenario-fields');
392
+ fields.forEach(fieldContainer => {
393
+ const names = fieldContainer.querySelectorAll('.scenario-field-name');
394
+ const values = fieldContainer.querySelectorAll('.scenario-field-value');
395
+ names.forEach((name, i) => {
396
+ if (name.value && values[i] && values[i].value) {
397
+ const value = values[i].value;
398
+ // Try to parse as number
399
+ scenario[name.value] = isNaN(value) ? value : parseFloat(value);
400
+ }
401
+ });
402
+ });
403
+ if (Object.keys(scenario).length > 0) {
404
+ scenarios.push(scenario);
405
+ }
406
+ });
407
+
408
+ if (scenarios.length === 0) {
409
+ alert('Please define at least one scenario');
410
+ return;
411
+ }
412
+
413
+ const rulesJson = document.getElementById('rulesJson').value;
414
+ const ruleVersion = document.getElementById('ruleVersion').value;
415
+
416
+ if (!rulesJson.trim() && !ruleVersion) {
417
+ alert('Please provide rules JSON or select a version');
418
+ return;
419
+ }
420
+
421
+ try {
422
+ const rules = rulesJson.trim() ? JSON.parse(rulesJson) : null;
423
+
424
+ const runStatus = document.getElementById('runStatus');
425
+ runStatus.className = 'hidden';
426
+
427
+ const requestData = {
428
+ scenarios: scenarios,
429
+ options: {
430
+ parallel: document.getElementById('parallelExecution').checked,
431
+ thread_count: parseInt(document.getElementById('threadCount').value) || 4,
432
+ sensitivity_analysis: document.getElementById('sensitivityAnalysis').checked
433
+ }
434
+ };
435
+
436
+ if (rules) {
437
+ requestData.rules = rules;
438
+ }
439
+ if (ruleVersion) {
440
+ requestData.rule_version = ruleVersion;
441
+ }
442
+
443
+ fetch(`${basePath}api/simulation/whatif`, {
444
+ method: 'POST',
445
+ headers: { 'Content-Type': 'application/json' },
446
+ body: JSON.stringify(requestData)
447
+ })
448
+ .then(response => response.json())
449
+ .then(data => {
450
+ if (data.error) {
451
+ runStatus.className = 'error-message';
452
+ runStatus.textContent = `Error: ${data.error}`;
453
+ runStatus.classList.remove('hidden');
454
+ } else {
455
+ runStatus.className = 'success-message';
456
+ runStatus.textContent = 'Analysis completed successfully!';
457
+ runStatus.classList.remove('hidden');
458
+ displayResults(data);
459
+ }
460
+ })
461
+ .catch(error => {
462
+ runStatus.className = 'error-message';
463
+ runStatus.textContent = `Error: ${error.message}`;
464
+ runStatus.classList.remove('hidden');
465
+ });
466
+ } catch (error) {
467
+ alert('Invalid JSON: ' + error.message);
468
+ }
469
+ });
470
+
471
+ function displayResults(data) {
472
+ document.getElementById('resultsSection').style.display = 'block';
473
+ const results = data.results;
474
+
475
+ // Statistics
476
+ if (results.total_scenarios !== undefined) {
477
+ const statsGrid = document.getElementById('statisticsGrid');
478
+ statsGrid.innerHTML = `
479
+ <div class="metric-card">
480
+ <div class="metric-value">${results.total_scenarios}</div>
481
+ <div class="metric-label">Total Scenarios</div>
482
+ </div>
483
+ ${results.average_confidence !== undefined ? `
484
+ <div class="metric-card">
485
+ <div class="metric-value">${results.average_confidence.toFixed(3)}</div>
486
+ <div class="metric-label">Avg Confidence</div>
487
+ </div>
488
+ ` : ''}
489
+ `;
490
+ document.getElementById('statisticsSection').classList.remove('hidden');
491
+ }
492
+
493
+ // Decision distribution
494
+ if (results.decision_distribution) {
495
+ const distContent = document.getElementById('distributionContent');
496
+ let html = '<table class="results-table"><thead><tr><th>Decision</th><th>Count</th></tr></thead><tbody>';
497
+ Object.entries(results.decision_distribution).forEach(([decision, count]) => {
498
+ html += `<tr><td>${decision}</td><td>${count}</td></tr>`;
499
+ });
500
+ html += '</tbody></table>';
501
+ distContent.innerHTML = html;
502
+ document.getElementById('distributionSection').classList.remove('hidden');
503
+ }
504
+
505
+ // Scenario results
506
+ if (results.scenarios) {
507
+ const scenarioContent = document.getElementById('scenarioResultsContent');
508
+ let html = '<table class="results-table"><thead><tr><th>Scenario</th><th>Decision</th><th>Confidence</th></tr></thead><tbody>';
509
+ results.scenarios.forEach((s, i) => {
510
+ html += `<tr>
511
+ <td>${JSON.stringify(s.scenario)}</td>
512
+ <td>${s.decision}</td>
513
+ <td>${s.confidence.toFixed(3)}</td>
514
+ </tr>`;
515
+ });
516
+ html += '</tbody></table>';
517
+ scenarioContent.innerHTML = html;
518
+ document.getElementById('scenarioResultsSection').classList.remove('hidden');
519
+ }
520
+
521
+ // Sensitivity analysis
522
+ if (results.sensitivity) {
523
+ const sensContent = document.getElementById('sensitivityContent');
524
+ let html = '<p>Sensitivity analysis results:</p><pre>' + JSON.stringify(results.sensitivity, null, 2) + '</pre>';
525
+ sensContent.innerHTML = html;
526
+ document.getElementById('sensitivitySection').classList.remove('hidden');
527
+ }
528
+ }
529
+ </script>
530
+ </body>
531
+ </html>
532
+
@@ -796,3 +796,68 @@ textarea.input {
796
796
  text-align: center;
797
797
  padding: 20px;
798
798
  }
799
+
800
+ /* Test Results Styles */
801
+ .test-results {
802
+ border: 1px solid var(--border-color);
803
+ border-radius: 6px;
804
+ padding: 20px;
805
+ background: var(--panel-bg);
806
+ margin-top: 20px;
807
+ }
808
+
809
+ .test-results h3 {
810
+ margin-top: 0;
811
+ color: var(--primary-color);
812
+ }
813
+
814
+ .test-result-item {
815
+ margin: 10px 0;
816
+ padding: 8px 0;
817
+ border-bottom: 1px solid var(--border-color);
818
+ }
819
+
820
+ .test-result-item:last-child {
821
+ border-bottom: none;
822
+ }
823
+
824
+ .test-explainability {
825
+ margin-top: 20px;
826
+ padding-top: 20px;
827
+ border-top: 2px solid var(--border-color);
828
+ }
829
+
830
+ .test-explainability h4 {
831
+ margin-top: 0;
832
+ color: var(--primary-color);
833
+ }
834
+
835
+ .test-because ul,
836
+ .test-failed ul {
837
+ list-style: none;
838
+ padding-left: 0;
839
+ margin: 10px 0;
840
+ }
841
+
842
+ .test-because li,
843
+ .test-failed li {
844
+ padding: 6px 12px;
845
+ margin: 4px 0;
846
+ border-radius: 4px;
847
+ background: var(--hover-bg);
848
+ }
849
+
850
+ .test-because li {
851
+ border-left: 3px solid #28a745;
852
+ }
853
+
854
+ .test-failed li {
855
+ border-left: 3px solid #dc3545;
856
+ }
857
+
858
+ .form-hint {
859
+ display: block;
860
+ margin-top: 4px;
861
+ font-size: 0.85em;
862
+ color: var(--text-secondary);
863
+ }