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,551 @@
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>Historical Replay / Backtesting</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
+ .step-header {
24
+ display: flex;
25
+ align-items: center;
26
+ margin-bottom: 15px;
27
+ }
28
+
29
+ .step-number {
30
+ background: var(--primary-color);
31
+ color: white;
32
+ width: 30px;
33
+ height: 30px;
34
+ border-radius: 50%;
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ font-weight: bold;
39
+ margin-right: 10px;
40
+ }
41
+
42
+ .file-upload-area {
43
+ border: 2px dashed var(--border-color);
44
+ border-radius: 8px;
45
+ padding: 40px;
46
+ text-align: center;
47
+ cursor: pointer;
48
+ transition: all 0.3s;
49
+ }
50
+
51
+ .file-upload-area:hover {
52
+ border-color: var(--primary-color);
53
+ background: var(--hover-bg);
54
+ }
55
+
56
+ .file-upload-area.dragover {
57
+ border-color: var(--primary-color);
58
+ background: var(--hover-bg);
59
+ }
60
+
61
+ .results-table {
62
+ width: 100%;
63
+ border-collapse: collapse;
64
+ margin-top: 15px;
65
+ }
66
+
67
+ .results-table th,
68
+ .results-table td {
69
+ padding: 10px;
70
+ text-align: left;
71
+ border-bottom: 1px solid var(--border-color);
72
+ }
73
+
74
+ .results-table th {
75
+ background: var(--hover-bg);
76
+ font-weight: 600;
77
+ }
78
+
79
+ .metrics-grid {
80
+ display: grid;
81
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
82
+ gap: 15px;
83
+ margin-top: 15px;
84
+ }
85
+
86
+ .metric-card {
87
+ background: var(--hover-bg);
88
+ padding: 15px;
89
+ border-radius: 8px;
90
+ text-align: center;
91
+ }
92
+
93
+ .metric-value {
94
+ font-size: 2rem;
95
+ font-weight: bold;
96
+ color: var(--primary-color);
97
+ }
98
+
99
+ .metric-label {
100
+ font-size: 0.9rem;
101
+ color: var(--text-secondary);
102
+ margin-top: 5px;
103
+ }
104
+
105
+ .hidden {
106
+ display: none;
107
+ }
108
+
109
+ .error-message {
110
+ background: #fee2e2;
111
+ color: #991b1b;
112
+ padding: 12px;
113
+ border-radius: 6px;
114
+ margin-top: 10px;
115
+ }
116
+
117
+ .success-message {
118
+ background: #d1fae5;
119
+ color: #065f46;
120
+ padding: 12px;
121
+ border-radius: 6px;
122
+ margin-top: 10px;
123
+ }
124
+
125
+ .header-links {
126
+ margin-top: 20px;
127
+ }
128
+
129
+ .header-links a {
130
+ margin-right: 15px;
131
+ text-decoration: none;
132
+ color: var(--primary-color);
133
+ }
134
+ </style>
135
+ </head>
136
+ <body>
137
+ <div class="simulation-container">
138
+ <header>
139
+ <h1>🔄 Historical Replay / Backtesting</h1>
140
+ <p class="subtitle">Replay historical decisions with different rule versions</p>
141
+ <div class="header-links">
142
+ <a href="/simulation">← Back to Simulation Dashboard</a>
143
+ </div>
144
+ </header>
145
+
146
+ <!-- Step 1: Upload Historical Data -->
147
+ <div class="step-section">
148
+ <div class="step-header">
149
+ <div class="step-number">1</div>
150
+ <h2>Upload Historical Data</h2>
151
+ </div>
152
+ <p>Upload a CSV or JSON file with historical decision contexts. Each row should contain the context fields used in your rules.</p>
153
+
154
+ <div class="file-upload-area" id="uploadArea">
155
+ <p>📁 Drag and drop your CSV/JSON file here, or click to browse</p>
156
+ <input type="file" id="fileInput" accept=".csv,.json" style="display: none;">
157
+ </div>
158
+
159
+ <div id="uploadStatus" class="hidden"></div>
160
+ </div>
161
+
162
+ <!-- Step 2: Configure Rules -->
163
+ <div class="step-section">
164
+ <div class="step-header">
165
+ <div class="step-number">2</div>
166
+ <h2>Configure Rules</h2>
167
+ </div>
168
+ <p>Paste your rules JSON or select a version to use</p>
169
+
170
+ <div class="form-group">
171
+ <label for="rulesJson">Rules JSON:</label>
172
+ <textarea id="rulesJson" class="input" rows="10" placeholder='{"version": "1.0", "ruleset": "my_ruleset", "rules": [...]}'></textarea>
173
+ </div>
174
+
175
+ <div class="form-group">
176
+ <label for="ruleVersion">Or select version:</label>
177
+ <select id="ruleVersion" class="input">
178
+ <option value="">-- Select Version --</option>
179
+ </select>
180
+ </div>
181
+
182
+ <div class="form-group">
183
+ <label for="compareVersion">Compare with version (optional):</label>
184
+ <select id="compareVersion" class="input">
185
+ <option value="">-- None --</option>
186
+ </select>
187
+ </div>
188
+
189
+ <div class="actions">
190
+ <label for="importRulesFile" class="btn btn-secondary">📁 Import Rules JSON
191
+ <input type="file" id="importRulesFile" accept=".json" style="display: none;">
192
+ </label>
193
+ <button class="btn btn-secondary" id="validateRulesBtn">✓ Validate Rules</button>
194
+ </div>
195
+ </div>
196
+
197
+ <!-- Step 3: Run Replay -->
198
+ <div class="step-section">
199
+ <div class="step-header">
200
+ <div class="step-number">3</div>
201
+ <h2>Run Historical Replay</h2>
202
+ </div>
203
+
204
+ <div class="form-group">
205
+ <label>
206
+ <input type="checkbox" id="parallelExecution" checked> Parallel execution
207
+ </label>
208
+ </div>
209
+ <div class="form-group">
210
+ <label for="threadCount">Thread count:</label>
211
+ <input type="number" id="threadCount" value="4" min="1" max="16" class="input" style="width: 100px;">
212
+ </div>
213
+
214
+ <button class="btn btn-primary" id="runReplayBtn" disabled>▶ Run Replay</button>
215
+
216
+ <div id="runStatus" class="hidden"></div>
217
+ </div>
218
+
219
+ <!-- Step 4: Results -->
220
+ <div class="step-section" id="resultsSection" style="display: none;">
221
+ <div class="step-header">
222
+ <div class="step-number">4</div>
223
+ <h2>Replay Results</h2>
224
+ </div>
225
+
226
+ <div id="replayInfo">
227
+ <p><strong>Replay ID:</strong> <span id="replayId"></span></p>
228
+ </div>
229
+
230
+ <!-- Statistics -->
231
+ <div id="statisticsSection" class="hidden">
232
+ <h3>Statistics</h3>
233
+ <div class="metrics-grid" id="statisticsGrid"></div>
234
+ </div>
235
+
236
+ <!-- Decision Distribution -->
237
+ <div id="distributionSection" class="hidden">
238
+ <h3>Decision Distribution</h3>
239
+ <div id="distributionContent"></div>
240
+ </div>
241
+
242
+ <!-- Comparison Results (if comparing) -->
243
+ <div id="comparisonSection" class="hidden">
244
+ <h3>Comparison Results</h3>
245
+ <div class="metrics-grid" id="comparisonGrid"></div>
246
+ </div>
247
+ </div>
248
+ </div>
249
+
250
+ <script>
251
+ // Helper function to get the base path for API calls
252
+ function getBasePath() {
253
+ const baseTag = document.querySelector('base');
254
+ if (baseTag && baseTag.href) {
255
+ try {
256
+ const baseUrl = new URL(baseTag.href, window.location.href);
257
+ let path = baseUrl.pathname;
258
+ if (path && !path.endsWith('/')) {
259
+ path += '/';
260
+ }
261
+ return path;
262
+ } catch (e) {
263
+ if (baseTag.href.startsWith('/')) {
264
+ const match = baseTag.href.match(/^(https?:\/\/[^\/]+)?(\/.*?)\/?$/);
265
+ if (match && match[2]) {
266
+ return match[2].endsWith('/') ? match[2] : match[2] + '/';
267
+ }
268
+ } else if (baseTag.href.startsWith('./')) {
269
+ const pathname = window.location.pathname;
270
+ return pathname.substring(0, pathname.lastIndexOf('/') + 1);
271
+ }
272
+ }
273
+ }
274
+ const pathname = window.location.pathname;
275
+ if (pathname.includes('/decision_agent')) {
276
+ const match = pathname.match(/^(\/.*?\/decision_agent)\/?/);
277
+ if (match) {
278
+ return match[1].endsWith('/') ? match[1] : match[1] + '/';
279
+ }
280
+ }
281
+ const dirPath = pathname.substring(0, pathname.lastIndexOf('/') + 1);
282
+ return dirPath || '/';
283
+ }
284
+
285
+ const basePath = getBasePath();
286
+ let historicalData = null;
287
+
288
+ // Load versions
289
+ function loadVersions() {
290
+ fetch(`${basePath}api/versions`)
291
+ .then(response => response.json())
292
+ .then(data => {
293
+ const ruleVersionSelect = document.getElementById('ruleVersion');
294
+ const compareVersionSelect = document.getElementById('compareVersion');
295
+
296
+ ruleVersionSelect.innerHTML = '<option value="">-- Select Version --</option>';
297
+ compareVersionSelect.innerHTML = '<option value="">-- None --</option>';
298
+
299
+ if (data.versions) {
300
+ data.versions.forEach(v => {
301
+ const option1 = document.createElement('option');
302
+ option1.value = v.id;
303
+ option1.textContent = `${v.rule_id} v${v.version_number} (${v.status})`;
304
+ ruleVersionSelect.appendChild(option1);
305
+
306
+ const option2 = document.createElement('option');
307
+ option2.value = v.id;
308
+ option2.textContent = `${v.rule_id} v${v.version_number} (${v.status})`;
309
+ compareVersionSelect.appendChild(option2);
310
+ });
311
+ }
312
+ })
313
+ .catch(error => console.error('Error loading versions:', error));
314
+ }
315
+
316
+ loadVersions();
317
+
318
+ // File upload
319
+ const uploadArea = document.getElementById('uploadArea');
320
+ const fileInput = document.getElementById('fileInput');
321
+ const uploadStatus = document.getElementById('uploadStatus');
322
+
323
+ uploadArea.addEventListener('click', () => fileInput.click());
324
+ uploadArea.addEventListener('dragover', (e) => {
325
+ e.preventDefault();
326
+ uploadArea.classList.add('dragover');
327
+ });
328
+ uploadArea.addEventListener('dragleave', () => {
329
+ uploadArea.classList.remove('dragover');
330
+ });
331
+ uploadArea.addEventListener('drop', (e) => {
332
+ e.preventDefault();
333
+ uploadArea.classList.remove('dragover');
334
+ const files = e.dataTransfer.files;
335
+ if (files.length > 0) {
336
+ handleFileUpload(files[0]);
337
+ }
338
+ });
339
+ fileInput.addEventListener('change', (e) => {
340
+ if (e.target.files.length > 0) {
341
+ handleFileUpload(e.target.files[0]);
342
+ }
343
+ });
344
+
345
+ function handleFileUpload(file) {
346
+ const reader = new FileReader();
347
+ reader.onload = (e) => {
348
+ try {
349
+ if (file.name.endsWith('.json')) {
350
+ historicalData = JSON.parse(e.target.result);
351
+ } else if (file.name.endsWith('.csv')) {
352
+ // Simple CSV parsing
353
+ const lines = e.target.result.split('\n');
354
+ const headers = lines[0].split(',');
355
+ historicalData = lines.slice(1).filter(l => l.trim()).map(line => {
356
+ const values = line.split(',');
357
+ const obj = {};
358
+ headers.forEach((h, i) => {
359
+ obj[h.trim()] = values[i]?.trim() || '';
360
+ });
361
+ return obj;
362
+ });
363
+ }
364
+
365
+ uploadStatus.className = 'success-message';
366
+ uploadStatus.textContent = `Successfully loaded ${historicalData.length} historical records`;
367
+ uploadStatus.classList.remove('hidden');
368
+ document.getElementById('runReplayBtn').disabled = false;
369
+ } catch (error) {
370
+ uploadStatus.className = 'error-message';
371
+ uploadStatus.textContent = `Error: ${error.message}`;
372
+ uploadStatus.classList.remove('hidden');
373
+ }
374
+ };
375
+ reader.readAsText(file);
376
+ }
377
+
378
+ // Import rules JSON
379
+ document.getElementById('importRulesFile').addEventListener('change', (e) => {
380
+ const file = e.target.files[0];
381
+ if (file) {
382
+ const reader = new FileReader();
383
+ reader.onload = (e) => {
384
+ try {
385
+ const json = JSON.parse(e.target.result);
386
+ document.getElementById('rulesJson').value = JSON.stringify(json, null, 2);
387
+ } catch (error) {
388
+ alert('Invalid JSON file: ' + error.message);
389
+ }
390
+ };
391
+ reader.readAsText(file);
392
+ }
393
+ });
394
+
395
+ // Validate rules
396
+ document.getElementById('validateRulesBtn').addEventListener('click', () => {
397
+ const rulesJson = document.getElementById('rulesJson').value;
398
+ try {
399
+ JSON.parse(rulesJson);
400
+ fetch(`${basePath}api/validate`, {
401
+ method: 'POST',
402
+ headers: { 'Content-Type': 'application/json' },
403
+ body: JSON.stringify(JSON.parse(rulesJson))
404
+ })
405
+ .then(response => response.json())
406
+ .then(data => {
407
+ if (data.valid) {
408
+ alert('Rules are valid!');
409
+ } else {
410
+ alert('Validation errors: ' + data.errors.join(', '));
411
+ }
412
+ });
413
+ } catch (error) {
414
+ alert('Invalid JSON: ' + error.message);
415
+ }
416
+ });
417
+
418
+ // Run replay
419
+ document.getElementById('runReplayBtn').addEventListener('click', () => {
420
+ if (!historicalData) {
421
+ alert('Please upload historical data first');
422
+ return;
423
+ }
424
+
425
+ const rulesJson = document.getElementById('rulesJson').value;
426
+ const ruleVersion = document.getElementById('ruleVersion').value;
427
+ const compareVersion = document.getElementById('compareVersion').value;
428
+
429
+ if (!rulesJson.trim() && !ruleVersion) {
430
+ alert('Please provide rules JSON or select a version');
431
+ return;
432
+ }
433
+
434
+ try {
435
+ const rules = rulesJson.trim() ? JSON.parse(rulesJson) : null;
436
+
437
+ const runStatus = document.getElementById('runStatus');
438
+ runStatus.className = 'hidden';
439
+
440
+ const requestData = {
441
+ historical_data: historicalData,
442
+ options: {
443
+ parallel: document.getElementById('parallelExecution').checked,
444
+ thread_count: parseInt(document.getElementById('threadCount').value) || 4
445
+ }
446
+ };
447
+
448
+ if (rules) {
449
+ requestData.rules = rules;
450
+ }
451
+ if (ruleVersion) {
452
+ requestData.rule_version = ruleVersion;
453
+ }
454
+ if (compareVersion) {
455
+ requestData.compare_with = compareVersion;
456
+ }
457
+
458
+ fetch(`${basePath}api/simulation/replay`, {
459
+ method: 'POST',
460
+ headers: { 'Content-Type': 'application/json' },
461
+ body: JSON.stringify(requestData)
462
+ })
463
+ .then(response => response.json())
464
+ .then(data => {
465
+ if (data.error) {
466
+ runStatus.className = 'error-message';
467
+ runStatus.textContent = `Error: ${data.error}`;
468
+ runStatus.classList.remove('hidden');
469
+ } else {
470
+ runStatus.className = 'success-message';
471
+ runStatus.textContent = 'Replay completed successfully!';
472
+ runStatus.classList.remove('hidden');
473
+ displayResults(data);
474
+ }
475
+ })
476
+ .catch(error => {
477
+ runStatus.className = 'error-message';
478
+ runStatus.textContent = `Error: ${error.message}`;
479
+ runStatus.classList.remove('hidden');
480
+ });
481
+ } catch (error) {
482
+ alert('Invalid JSON: ' + error.message);
483
+ }
484
+ });
485
+
486
+ function displayResults(data) {
487
+ document.getElementById('resultsSection').style.display = 'block';
488
+ document.getElementById('replayId').textContent = data.replay_id;
489
+
490
+ const results = data.results;
491
+
492
+ // Statistics
493
+ if (results.total_decisions !== undefined) {
494
+ const statsGrid = document.getElementById('statisticsGrid');
495
+ statsGrid.innerHTML = `
496
+ <div class="metric-card">
497
+ <div class="metric-value">${results.total_decisions}</div>
498
+ <div class="metric-label">Total Decisions</div>
499
+ </div>
500
+ ${results.changed_decisions !== undefined ? `
501
+ <div class="metric-card">
502
+ <div class="metric-value">${results.changed_decisions}</div>
503
+ <div class="metric-label">Changed Decisions</div>
504
+ </div>
505
+ <div class="metric-card">
506
+ <div class="metric-value">${(results.change_rate * 100).toFixed(2)}%</div>
507
+ <div class="metric-label">Change Rate</div>
508
+ </div>
509
+ ` : ''}
510
+ ${results.average_confidence_delta !== undefined ? `
511
+ <div class="metric-card">
512
+ <div class="metric-value">${results.average_confidence_delta.toFixed(3)}</div>
513
+ <div class="metric-label">Avg Confidence Delta</div>
514
+ </div>
515
+ ` : ''}
516
+ `;
517
+ document.getElementById('statisticsSection').classList.remove('hidden');
518
+ }
519
+
520
+ // Decision distribution
521
+ if (results.decision_distribution) {
522
+ const distContent = document.getElementById('distributionContent');
523
+ let html = '<table class="results-table"><thead><tr><th>Decision</th><th>Count</th></tr></thead><tbody>';
524
+ Object.entries(results.decision_distribution).forEach(([decision, count]) => {
525
+ html += `<tr><td>${decision}</td><td>${count}</td></tr>`;
526
+ });
527
+ html += '</tbody></table>';
528
+ distContent.innerHTML = html;
529
+ document.getElementById('distributionSection').classList.remove('hidden');
530
+ }
531
+
532
+ // Comparison results
533
+ if (results.changed_decisions !== undefined) {
534
+ const compGrid = document.getElementById('comparisonGrid');
535
+ compGrid.innerHTML = `
536
+ <div class="metric-card">
537
+ <div class="metric-value">${results.changed_decisions}</div>
538
+ <div class="metric-label">Changed Decisions</div>
539
+ </div>
540
+ <div class="metric-card">
541
+ <div class="metric-value">${(results.change_rate * 100).toFixed(2)}%</div>
542
+ <div class="metric-label">Change Rate</div>
543
+ </div>
544
+ `;
545
+ document.getElementById('comparisonSection').classList.remove('hidden');
546
+ }
547
+ }
548
+ </script>
549
+ </body>
550
+ </html>
551
+