decision_agent 0.2.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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +313 -8
  3. data/bin/decision_agent +104 -0
  4. data/lib/decision_agent/agent.rb +72 -1
  5. data/lib/decision_agent/context.rb +1 -0
  6. data/lib/decision_agent/data_enrichment/cache/memory_adapter.rb +86 -0
  7. data/lib/decision_agent/data_enrichment/cache_adapter.rb +49 -0
  8. data/lib/decision_agent/data_enrichment/circuit_breaker.rb +135 -0
  9. data/lib/decision_agent/data_enrichment/client.rb +220 -0
  10. data/lib/decision_agent/data_enrichment/config.rb +78 -0
  11. data/lib/decision_agent/data_enrichment/errors.rb +36 -0
  12. data/lib/decision_agent/decision.rb +102 -2
  13. data/lib/decision_agent/dmn/adapter.rb +135 -0
  14. data/lib/decision_agent/dmn/cache.rb +306 -0
  15. data/lib/decision_agent/dmn/decision_graph.rb +327 -0
  16. data/lib/decision_agent/dmn/decision_tree.rb +192 -0
  17. data/lib/decision_agent/dmn/errors.rb +30 -0
  18. data/lib/decision_agent/dmn/exporter.rb +217 -0
  19. data/lib/decision_agent/dmn/feel/evaluator.rb +819 -0
  20. data/lib/decision_agent/dmn/feel/functions.rb +420 -0
  21. data/lib/decision_agent/dmn/feel/parser.rb +349 -0
  22. data/lib/decision_agent/dmn/feel/simple_parser.rb +276 -0
  23. data/lib/decision_agent/dmn/feel/transformer.rb +372 -0
  24. data/lib/decision_agent/dmn/feel/types.rb +276 -0
  25. data/lib/decision_agent/dmn/importer.rb +77 -0
  26. data/lib/decision_agent/dmn/model.rb +197 -0
  27. data/lib/decision_agent/dmn/parser.rb +191 -0
  28. data/lib/decision_agent/dmn/testing.rb +333 -0
  29. data/lib/decision_agent/dmn/validator.rb +315 -0
  30. data/lib/decision_agent/dmn/versioning.rb +229 -0
  31. data/lib/decision_agent/dmn/visualizer.rb +513 -0
  32. data/lib/decision_agent/dsl/condition_evaluator.rb +984 -838
  33. data/lib/decision_agent/dsl/schema_validator.rb +53 -14
  34. data/lib/decision_agent/evaluators/dmn_evaluator.rb +308 -0
  35. data/lib/decision_agent/evaluators/json_rule_evaluator.rb +69 -9
  36. data/lib/decision_agent/explainability/condition_trace.rb +83 -0
  37. data/lib/decision_agent/explainability/explainability_result.rb +52 -0
  38. data/lib/decision_agent/explainability/rule_trace.rb +39 -0
  39. data/lib/decision_agent/explainability/trace_collector.rb +24 -0
  40. data/lib/decision_agent/monitoring/alert_manager.rb +5 -1
  41. data/lib/decision_agent/simulation/errors.rb +18 -0
  42. data/lib/decision_agent/simulation/impact_analyzer.rb +498 -0
  43. data/lib/decision_agent/simulation/monte_carlo_simulator.rb +635 -0
  44. data/lib/decision_agent/simulation/replay_engine.rb +486 -0
  45. data/lib/decision_agent/simulation/scenario_engine.rb +318 -0
  46. data/lib/decision_agent/simulation/scenario_library.rb +163 -0
  47. data/lib/decision_agent/simulation/shadow_test_engine.rb +287 -0
  48. data/lib/decision_agent/simulation/what_if_analyzer.rb +1002 -0
  49. data/lib/decision_agent/simulation.rb +17 -0
  50. data/lib/decision_agent/version.rb +1 -1
  51. data/lib/decision_agent/versioning/activerecord_adapter.rb +23 -8
  52. data/lib/decision_agent/web/dmn_editor.rb +426 -0
  53. data/lib/decision_agent/web/public/app.js +119 -0
  54. data/lib/decision_agent/web/public/dmn-editor.css +596 -0
  55. data/lib/decision_agent/web/public/dmn-editor.html +250 -0
  56. data/lib/decision_agent/web/public/dmn-editor.js +553 -0
  57. data/lib/decision_agent/web/public/index.html +52 -0
  58. data/lib/decision_agent/web/public/simulation.html +130 -0
  59. data/lib/decision_agent/web/public/simulation_impact.html +478 -0
  60. data/lib/decision_agent/web/public/simulation_replay.html +551 -0
  61. data/lib/decision_agent/web/public/simulation_shadow.html +546 -0
  62. data/lib/decision_agent/web/public/simulation_whatif.html +532 -0
  63. data/lib/decision_agent/web/public/styles.css +86 -0
  64. data/lib/decision_agent/web/server.rb +1059 -23
  65. data/lib/decision_agent.rb +60 -2
  66. metadata +105 -61
  67. data/spec/ab_testing/ab_test_assignment_spec.rb +0 -253
  68. data/spec/ab_testing/ab_test_manager_spec.rb +0 -612
  69. data/spec/ab_testing/ab_test_spec.rb +0 -270
  70. data/spec/ab_testing/ab_testing_agent_spec.rb +0 -481
  71. data/spec/ab_testing/storage/adapter_spec.rb +0 -64
  72. data/spec/ab_testing/storage/memory_adapter_spec.rb +0 -485
  73. data/spec/activerecord_thread_safety_spec.rb +0 -553
  74. data/spec/advanced_operators_spec.rb +0 -3150
  75. data/spec/agent_spec.rb +0 -289
  76. data/spec/api_contract_spec.rb +0 -430
  77. data/spec/audit_adapters_spec.rb +0 -92
  78. data/spec/auth/access_audit_logger_spec.rb +0 -394
  79. data/spec/auth/authenticator_spec.rb +0 -112
  80. data/spec/auth/password_reset_spec.rb +0 -294
  81. data/spec/auth/permission_checker_spec.rb +0 -207
  82. data/spec/auth/permission_spec.rb +0 -73
  83. data/spec/auth/rbac_adapter_spec.rb +0 -550
  84. data/spec/auth/rbac_config_spec.rb +0 -82
  85. data/spec/auth/role_spec.rb +0 -51
  86. data/spec/auth/session_manager_spec.rb +0 -172
  87. data/spec/auth/session_spec.rb +0 -112
  88. data/spec/auth/user_spec.rb +0 -130
  89. data/spec/comprehensive_edge_cases_spec.rb +0 -1777
  90. data/spec/context_spec.rb +0 -127
  91. data/spec/decision_agent_spec.rb +0 -96
  92. data/spec/decision_spec.rb +0 -423
  93. data/spec/dsl/condition_evaluator_spec.rb +0 -774
  94. data/spec/dsl_validation_spec.rb +0 -648
  95. data/spec/edge_cases_spec.rb +0 -353
  96. data/spec/evaluation_spec.rb +0 -364
  97. data/spec/evaluation_validator_spec.rb +0 -165
  98. data/spec/examples/feedback_aware_evaluator_spec.rb +0 -460
  99. data/spec/examples.txt +0 -1633
  100. data/spec/issue_verification_spec.rb +0 -759
  101. data/spec/json_rule_evaluator_spec.rb +0 -587
  102. data/spec/monitoring/alert_manager_spec.rb +0 -378
  103. data/spec/monitoring/metrics_collector_spec.rb +0 -499
  104. data/spec/monitoring/monitored_agent_spec.rb +0 -222
  105. data/spec/monitoring/prometheus_exporter_spec.rb +0 -242
  106. data/spec/monitoring/storage/activerecord_adapter_spec.rb +0 -498
  107. data/spec/monitoring/storage/base_adapter_spec.rb +0 -61
  108. data/spec/monitoring/storage/memory_adapter_spec.rb +0 -247
  109. data/spec/performance_optimizations_spec.rb +0 -486
  110. data/spec/replay_edge_cases_spec.rb +0 -699
  111. data/spec/replay_spec.rb +0 -210
  112. data/spec/rfc8785_canonicalization_spec.rb +0 -215
  113. data/spec/scoring_spec.rb +0 -225
  114. data/spec/spec_helper.rb +0 -60
  115. data/spec/testing/batch_test_importer_spec.rb +0 -693
  116. data/spec/testing/batch_test_runner_spec.rb +0 -307
  117. data/spec/testing/test_coverage_analyzer_spec.rb +0 -292
  118. data/spec/testing/test_result_comparator_spec.rb +0 -392
  119. data/spec/testing/test_scenario_spec.rb +0 -113
  120. data/spec/thread_safety_spec.rb +0 -482
  121. data/spec/thread_safety_spec.rb.broken +0 -878
  122. data/spec/versioning/adapter_spec.rb +0 -156
  123. data/spec/versioning_spec.rb +0 -1030
  124. data/spec/web/middleware/auth_middleware_spec.rb +0 -133
  125. data/spec/web/middleware/permission_middleware_spec.rb +0 -247
  126. data/spec/web_ui_rack_spec.rb +0 -1840
@@ -0,0 +1,130 @@
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>DecisionAgent Simulation Dashboard</title>
7
+ <link rel="stylesheet" href="styles.css">
8
+ <style>
9
+ .simulation-dashboard {
10
+ max-width: 1400px;
11
+ margin: 0 auto;
12
+ padding: 20px;
13
+ }
14
+
15
+ .feature-grid {
16
+ display: grid;
17
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
18
+ gap: 20px;
19
+ margin-top: 30px;
20
+ }
21
+
22
+ .feature-card {
23
+ background: var(--panel-bg);
24
+ border-radius: 10px;
25
+ padding: 25px;
26
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
27
+ transition: transform 0.2s, box-shadow 0.2s;
28
+ cursor: pointer;
29
+ text-decoration: none;
30
+ color: inherit;
31
+ display: block;
32
+ }
33
+
34
+ .feature-card:hover {
35
+ transform: translateY(-2px);
36
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.15);
37
+ }
38
+
39
+ .feature-icon {
40
+ font-size: 3rem;
41
+ margin-bottom: 15px;
42
+ }
43
+
44
+ .feature-title {
45
+ font-size: 1.5rem;
46
+ font-weight: 600;
47
+ margin-bottom: 10px;
48
+ color: var(--primary-color);
49
+ }
50
+
51
+ .feature-description {
52
+ color: var(--text-secondary);
53
+ line-height: 1.6;
54
+ }
55
+
56
+ .header-links {
57
+ margin-top: 20px;
58
+ }
59
+
60
+ .header-links a {
61
+ margin-right: 15px;
62
+ text-decoration: none;
63
+ color: var(--primary-color);
64
+ }
65
+ </style>
66
+ </head>
67
+ <body>
68
+ <div class="simulation-dashboard">
69
+ <header>
70
+ <h1>🧪 DecisionAgent Simulation Dashboard</h1>
71
+ <p class="subtitle">Test rule changes, predict impact, and validate decisions before deployment</p>
72
+ <div class="header-links">
73
+ <a href="/">🏠 Rule Builder</a>
74
+ <a href="/testing/batch">📊 Batch Testing</a>
75
+ <a href="/dmn/editor">📋 DMN Editor</a>
76
+ </div>
77
+ </header>
78
+
79
+ <div class="feature-grid">
80
+ <a href="/simulation/replay" class="feature-card">
81
+ <div class="feature-icon">🔄</div>
82
+ <div class="feature-title">Historical Replay / Backtesting</div>
83
+ <div class="feature-description">
84
+ Replay historical decisions with different rule versions to see how outcomes would change.
85
+ Compare baseline vs. proposed versions and analyze decision changes.
86
+ </div>
87
+ </a>
88
+
89
+ <a href="/simulation/whatif" class="feature-card">
90
+ <div class="feature-icon">❓</div>
91
+ <div class="feature-title">What-If Analysis</div>
92
+ <div class="feature-description">
93
+ Simulate different scenarios and analyze how decisions change based on input variations.
94
+ Perform sensitivity analysis to identify which inputs matter most.
95
+ </div>
96
+ </a>
97
+
98
+ <a href="/simulation/impact" class="feature-card">
99
+ <div class="feature-icon">📊</div>
100
+ <div class="feature-title">Impact Analysis</div>
101
+ <div class="feature-description">
102
+ Quantify the impact of rule changes before deploying to production.
103
+ Get risk scores, decision distribution changes, and confidence shifts.
104
+ </div>
105
+ </a>
106
+
107
+ <a href="/simulation/shadow" class="feature-card">
108
+ <div class="feature-icon">👻</div>
109
+ <div class="feature-title">Shadow Testing</div>
110
+ <div class="feature-description">
111
+ Compare new rules against production without affecting actual outcomes.
112
+ Test on production traffic with zero impact on real decisions.
113
+ </div>
114
+ </a>
115
+ </div>
116
+
117
+ <div style="margin-top: 40px; padding: 20px; background: var(--panel-bg); border-radius: 10px;">
118
+ <h2>Quick Start</h2>
119
+ <p>Choose a simulation type above to get started:</p>
120
+ <ul style="line-height: 2;">
121
+ <li><strong>Historical Replay:</strong> Upload historical data (CSV/JSON) and replay with different rule versions</li>
122
+ <li><strong>What-If Analysis:</strong> Define scenarios and see how decisions change</li>
123
+ <li><strong>Impact Analysis:</strong> Compare two rule versions to assess change impact</li>
124
+ <li><strong>Shadow Testing:</strong> Test new rules alongside production without affecting outcomes</li>
125
+ </ul>
126
+ </div>
127
+ </div>
128
+ </body>
129
+ </html>
130
+
@@ -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
+