decision_agent 0.3.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +234 -14
  3. data/lib/decision_agent/ab_testing/ab_test.rb +5 -1
  4. data/lib/decision_agent/ab_testing/ab_test_assignment.rb +2 -0
  5. data/lib/decision_agent/ab_testing/ab_test_manager.rb +2 -0
  6. data/lib/decision_agent/ab_testing/ab_testing_agent.rb +2 -0
  7. data/lib/decision_agent/ab_testing/storage/activerecord_adapter.rb +2 -13
  8. data/lib/decision_agent/ab_testing/storage/adapter.rb +2 -0
  9. data/lib/decision_agent/ab_testing/storage/memory_adapter.rb +2 -0
  10. data/lib/decision_agent/agent.rb +78 -9
  11. data/lib/decision_agent/audit/adapter.rb +2 -0
  12. data/lib/decision_agent/audit/logger_adapter.rb +2 -0
  13. data/lib/decision_agent/audit/null_adapter.rb +2 -0
  14. data/lib/decision_agent/auth/access_audit_logger.rb +2 -0
  15. data/lib/decision_agent/auth/authenticator.rb +2 -0
  16. data/lib/decision_agent/auth/password_reset_manager.rb +2 -0
  17. data/lib/decision_agent/auth/password_reset_token.rb +2 -0
  18. data/lib/decision_agent/auth/permission.rb +2 -0
  19. data/lib/decision_agent/auth/permission_checker.rb +2 -0
  20. data/lib/decision_agent/auth/rbac_adapter.rb +2 -0
  21. data/lib/decision_agent/auth/rbac_config.rb +2 -0
  22. data/lib/decision_agent/auth/role.rb +2 -0
  23. data/lib/decision_agent/auth/session.rb +2 -0
  24. data/lib/decision_agent/auth/session_manager.rb +2 -0
  25. data/lib/decision_agent/auth/user.rb +2 -0
  26. data/lib/decision_agent/context.rb +14 -0
  27. data/lib/decision_agent/decision.rb +113 -4
  28. data/lib/decision_agent/dmn/adapter.rb +2 -0
  29. data/lib/decision_agent/dmn/cache.rb +2 -2
  30. data/lib/decision_agent/dmn/decision_graph.rb +7 -7
  31. data/lib/decision_agent/dmn/decision_tree.rb +16 -8
  32. data/lib/decision_agent/dmn/errors.rb +2 -0
  33. data/lib/decision_agent/dmn/exporter.rb +2 -0
  34. data/lib/decision_agent/dmn/feel/evaluator.rb +130 -114
  35. data/lib/decision_agent/dmn/feel/functions.rb +2 -0
  36. data/lib/decision_agent/dmn/feel/parser.rb +2 -0
  37. data/lib/decision_agent/dmn/feel/simple_parser.rb +98 -77
  38. data/lib/decision_agent/dmn/feel/transformer.rb +56 -102
  39. data/lib/decision_agent/dmn/feel/types.rb +2 -0
  40. data/lib/decision_agent/dmn/importer.rb +2 -0
  41. data/lib/decision_agent/dmn/model.rb +2 -4
  42. data/lib/decision_agent/dmn/parser.rb +2 -0
  43. data/lib/decision_agent/dmn/testing.rb +3 -2
  44. data/lib/decision_agent/dmn/validator.rb +5 -3
  45. data/lib/decision_agent/dmn/visualizer.rb +7 -6
  46. data/lib/decision_agent/dsl/condition_evaluator.rb +242 -1375
  47. data/lib/decision_agent/dsl/helpers/cache_helpers.rb +82 -0
  48. data/lib/decision_agent/dsl/helpers/comparison_helpers.rb +98 -0
  49. data/lib/decision_agent/dsl/helpers/date_helpers.rb +91 -0
  50. data/lib/decision_agent/dsl/helpers/geospatial_helpers.rb +85 -0
  51. data/lib/decision_agent/dsl/helpers/operator_evaluation_helpers.rb +160 -0
  52. data/lib/decision_agent/dsl/helpers/parameter_parsing_helpers.rb +206 -0
  53. data/lib/decision_agent/dsl/helpers/template_helpers.rb +39 -0
  54. data/lib/decision_agent/dsl/helpers/utility_helpers.rb +45 -0
  55. data/lib/decision_agent/dsl/operators/base.rb +70 -0
  56. data/lib/decision_agent/dsl/operators/basic_comparison_operators.rb +80 -0
  57. data/lib/decision_agent/dsl/operators/collection_operators.rb +60 -0
  58. data/lib/decision_agent/dsl/operators/date_arithmetic_operators.rb +206 -0
  59. data/lib/decision_agent/dsl/operators/date_time_operators.rb +47 -0
  60. data/lib/decision_agent/dsl/operators/duration_operators.rb +149 -0
  61. data/lib/decision_agent/dsl/operators/financial_operators.rb +237 -0
  62. data/lib/decision_agent/dsl/operators/geospatial_operators.rb +106 -0
  63. data/lib/decision_agent/dsl/operators/mathematical_operators.rb +234 -0
  64. data/lib/decision_agent/dsl/operators/moving_window_operators.rb +135 -0
  65. data/lib/decision_agent/dsl/operators/numeric_operators.rb +120 -0
  66. data/lib/decision_agent/dsl/operators/rate_operators.rb +65 -0
  67. data/lib/decision_agent/dsl/operators/statistical_aggregations.rb +187 -0
  68. data/lib/decision_agent/dsl/operators/string_aggregations.rb +84 -0
  69. data/lib/decision_agent/dsl/operators/string_operators.rb +72 -0
  70. data/lib/decision_agent/dsl/operators/time_component_operators.rb +72 -0
  71. data/lib/decision_agent/dsl/rule_parser.rb +2 -0
  72. data/lib/decision_agent/dsl/schema_validator.rb +37 -14
  73. data/lib/decision_agent/errors.rb +2 -0
  74. data/lib/decision_agent/evaluation.rb +14 -2
  75. data/lib/decision_agent/evaluators/base.rb +2 -0
  76. data/lib/decision_agent/evaluators/dmn_evaluator.rb +108 -19
  77. data/lib/decision_agent/evaluators/json_rule_evaluator.rb +56 -11
  78. data/lib/decision_agent/evaluators/static_evaluator.rb +2 -0
  79. data/lib/decision_agent/explainability/condition_trace.rb +85 -0
  80. data/lib/decision_agent/explainability/explainability_result.rb +50 -0
  81. data/lib/decision_agent/explainability/rule_trace.rb +41 -0
  82. data/lib/decision_agent/explainability/trace_collector.rb +26 -0
  83. data/lib/decision_agent/monitoring/alert_manager.rb +7 -16
  84. data/lib/decision_agent/monitoring/dashboard_server.rb +383 -250
  85. data/lib/decision_agent/monitoring/metrics_collector.rb +2 -0
  86. data/lib/decision_agent/monitoring/monitored_agent.rb +2 -0
  87. data/lib/decision_agent/monitoring/prometheus_exporter.rb +3 -1
  88. data/lib/decision_agent/replay/replay.rb +4 -1
  89. data/lib/decision_agent/scoring/base.rb +2 -0
  90. data/lib/decision_agent/scoring/consensus.rb +2 -0
  91. data/lib/decision_agent/scoring/max_weight.rb +2 -0
  92. data/lib/decision_agent/scoring/threshold.rb +2 -0
  93. data/lib/decision_agent/scoring/weighted_average.rb +2 -0
  94. data/lib/decision_agent/simulation/errors.rb +20 -0
  95. data/lib/decision_agent/simulation/impact_analyzer.rb +500 -0
  96. data/lib/decision_agent/simulation/monte_carlo_simulator.rb +638 -0
  97. data/lib/decision_agent/simulation/replay_engine.rb +488 -0
  98. data/lib/decision_agent/simulation/scenario_engine.rb +320 -0
  99. data/lib/decision_agent/simulation/scenario_library.rb +165 -0
  100. data/lib/decision_agent/simulation/shadow_test_engine.rb +274 -0
  101. data/lib/decision_agent/simulation/what_if_analyzer.rb +1008 -0
  102. data/lib/decision_agent/simulation.rb +19 -0
  103. data/lib/decision_agent/testing/batch_test_importer.rb +6 -2
  104. data/lib/decision_agent/testing/batch_test_runner.rb +5 -2
  105. data/lib/decision_agent/testing/test_coverage_analyzer.rb +2 -0
  106. data/lib/decision_agent/testing/test_result_comparator.rb +2 -0
  107. data/lib/decision_agent/testing/test_scenario.rb +2 -0
  108. data/lib/decision_agent/version.rb +3 -1
  109. data/lib/decision_agent/versioning/activerecord_adapter.rb +108 -43
  110. data/lib/decision_agent/versioning/adapter.rb +9 -0
  111. data/lib/decision_agent/versioning/file_storage_adapter.rb +19 -6
  112. data/lib/decision_agent/versioning/version_manager.rb +9 -0
  113. data/lib/decision_agent/web/dmn_editor/serialization.rb +74 -0
  114. data/lib/decision_agent/web/dmn_editor/xml_builder.rb +107 -0
  115. data/lib/decision_agent/web/dmn_editor.rb +8 -67
  116. data/lib/decision_agent/web/middleware/auth_middleware.rb +2 -0
  117. data/lib/decision_agent/web/middleware/permission_middleware.rb +3 -1
  118. data/lib/decision_agent/web/public/app.js +186 -26
  119. data/lib/decision_agent/web/public/batch_testing.html +80 -6
  120. data/lib/decision_agent/web/public/dmn-editor.html +2 -2
  121. data/lib/decision_agent/web/public/dmn-editor.js +74 -8
  122. data/lib/decision_agent/web/public/index.html +69 -3
  123. data/lib/decision_agent/web/public/login.html +1 -1
  124. data/lib/decision_agent/web/public/sample_batch.csv +11 -0
  125. data/lib/decision_agent/web/public/sample_impact.csv +11 -0
  126. data/lib/decision_agent/web/public/sample_replay.csv +11 -0
  127. data/lib/decision_agent/web/public/sample_rules.json +118 -0
  128. data/lib/decision_agent/web/public/sample_shadow.csv +11 -0
  129. data/lib/decision_agent/web/public/sample_whatif.csv +11 -0
  130. data/lib/decision_agent/web/public/simulation.html +146 -0
  131. data/lib/decision_agent/web/public/simulation_impact.html +495 -0
  132. data/lib/decision_agent/web/public/simulation_replay.html +547 -0
  133. data/lib/decision_agent/web/public/simulation_shadow.html +561 -0
  134. data/lib/decision_agent/web/public/simulation_whatif.html +549 -0
  135. data/lib/decision_agent/web/public/styles.css +65 -0
  136. data/lib/decision_agent/web/public/users.html +1 -1
  137. data/lib/decision_agent/web/rack_helpers.rb +106 -0
  138. data/lib/decision_agent/web/rack_request_helpers.rb +196 -0
  139. data/lib/decision_agent/web/server.rb +2126 -1374
  140. data/lib/decision_agent.rb +19 -1
  141. data/lib/generators/decision_agent/install/install_generator.rb +2 -0
  142. data/lib/generators/decision_agent/install/templates/ab_test_assignment_model.rb +2 -0
  143. data/lib/generators/decision_agent/install/templates/ab_test_model.rb +2 -0
  144. data/lib/generators/decision_agent/install/templates/ab_testing_migration.rb +2 -0
  145. data/lib/generators/decision_agent/install/templates/migration.rb +2 -0
  146. data/lib/generators/decision_agent/install/templates/rule.rb +2 -0
  147. data/lib/generators/decision_agent/install/templates/rule_version.rb +2 -0
  148. metadata +103 -89
  149. data/spec/ab_testing/ab_test_assignment_spec.rb +0 -253
  150. data/spec/ab_testing/ab_test_manager_spec.rb +0 -612
  151. data/spec/ab_testing/ab_test_spec.rb +0 -270
  152. data/spec/ab_testing/ab_testing_agent_spec.rb +0 -655
  153. data/spec/ab_testing/storage/adapter_spec.rb +0 -64
  154. data/spec/ab_testing/storage/memory_adapter_spec.rb +0 -485
  155. data/spec/activerecord_thread_safety_spec.rb +0 -553
  156. data/spec/advanced_operators_spec.rb +0 -3150
  157. data/spec/agent_spec.rb +0 -289
  158. data/spec/api_contract_spec.rb +0 -430
  159. data/spec/audit_adapters_spec.rb +0 -92
  160. data/spec/auth/access_audit_logger_spec.rb +0 -394
  161. data/spec/auth/authenticator_spec.rb +0 -112
  162. data/spec/auth/password_reset_spec.rb +0 -294
  163. data/spec/auth/permission_checker_spec.rb +0 -207
  164. data/spec/auth/permission_spec.rb +0 -73
  165. data/spec/auth/rbac_adapter_spec.rb +0 -778
  166. data/spec/auth/rbac_config_spec.rb +0 -82
  167. data/spec/auth/role_spec.rb +0 -51
  168. data/spec/auth/session_manager_spec.rb +0 -172
  169. data/spec/auth/session_spec.rb +0 -112
  170. data/spec/auth/user_spec.rb +0 -130
  171. data/spec/comprehensive_edge_cases_spec.rb +0 -1777
  172. data/spec/context_spec.rb +0 -127
  173. data/spec/decision_agent_spec.rb +0 -96
  174. data/spec/decision_spec.rb +0 -423
  175. data/spec/dmn/decision_graph_spec.rb +0 -282
  176. data/spec/dmn/decision_tree_spec.rb +0 -203
  177. data/spec/dmn/feel/errors_spec.rb +0 -18
  178. data/spec/dmn/feel/functions_spec.rb +0 -400
  179. data/spec/dmn/feel/simple_parser_spec.rb +0 -274
  180. data/spec/dmn/feel/types_spec.rb +0 -176
  181. data/spec/dmn/feel_parser_spec.rb +0 -489
  182. data/spec/dmn/hit_policy_spec.rb +0 -202
  183. data/spec/dmn/integration_spec.rb +0 -226
  184. data/spec/dsl/condition_evaluator_spec.rb +0 -774
  185. data/spec/dsl_validation_spec.rb +0 -648
  186. data/spec/edge_cases_spec.rb +0 -353
  187. data/spec/evaluation_spec.rb +0 -364
  188. data/spec/evaluation_validator_spec.rb +0 -165
  189. data/spec/examples/feedback_aware_evaluator_spec.rb +0 -460
  190. data/spec/examples.txt +0 -1909
  191. data/spec/fixtures/dmn/complex_decision.dmn +0 -81
  192. data/spec/fixtures/dmn/invalid_structure.dmn +0 -31
  193. data/spec/fixtures/dmn/simple_decision.dmn +0 -40
  194. data/spec/issue_verification_spec.rb +0 -759
  195. data/spec/json_rule_evaluator_spec.rb +0 -587
  196. data/spec/monitoring/alert_manager_spec.rb +0 -378
  197. data/spec/monitoring/metrics_collector_spec.rb +0 -501
  198. data/spec/monitoring/monitored_agent_spec.rb +0 -225
  199. data/spec/monitoring/prometheus_exporter_spec.rb +0 -242
  200. data/spec/monitoring/storage/activerecord_adapter_spec.rb +0 -498
  201. data/spec/monitoring/storage/base_adapter_spec.rb +0 -61
  202. data/spec/monitoring/storage/memory_adapter_spec.rb +0 -247
  203. data/spec/performance_optimizations_spec.rb +0 -493
  204. data/spec/replay_edge_cases_spec.rb +0 -699
  205. data/spec/replay_spec.rb +0 -210
  206. data/spec/rfc8785_canonicalization_spec.rb +0 -215
  207. data/spec/scoring_spec.rb +0 -225
  208. data/spec/spec_helper.rb +0 -60
  209. data/spec/testing/batch_test_importer_spec.rb +0 -693
  210. data/spec/testing/batch_test_runner_spec.rb +0 -307
  211. data/spec/testing/test_coverage_analyzer_spec.rb +0 -292
  212. data/spec/testing/test_result_comparator_spec.rb +0 -392
  213. data/spec/testing/test_scenario_spec.rb +0 -113
  214. data/spec/thread_safety_spec.rb +0 -490
  215. data/spec/thread_safety_spec.rb.broken +0 -878
  216. data/spec/versioning/adapter_spec.rb +0 -156
  217. data/spec/versioning_spec.rb +0 -1030
  218. data/spec/web/middleware/auth_middleware_spec.rb +0 -133
  219. data/spec/web/middleware/permission_middleware_spec.rb +0 -247
  220. data/spec/web_ui_rack_spec.rb +0 -2134
@@ -0,0 +1,549 @@
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
+ display: flex;
106
+ gap: 1rem;
107
+ align-items: center;
108
+ margin-top: 1.5rem;
109
+ padding-top: 1rem;
110
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
111
+ }
112
+
113
+ .header-links a {
114
+ padding: 0.5rem 1rem;
115
+ border-radius: 6px;
116
+ text-decoration: none;
117
+ color: var(--text-primary);
118
+ background: rgba(255, 255, 255, 0.05);
119
+ transition: all 0.2s;
120
+ font-size: 0.9rem;
121
+ }
122
+
123
+ .header-links a:hover {
124
+ background: rgba(255, 255, 255, 0.1);
125
+ transform: translateY(-1px);
126
+ }
127
+
128
+ .results-table {
129
+ width: 100%;
130
+ border-collapse: collapse;
131
+ margin-top: 15px;
132
+ }
133
+
134
+ .results-table th,
135
+ .results-table td {
136
+ padding: 10px;
137
+ text-align: left;
138
+ border-bottom: 1px solid var(--border-color);
139
+ }
140
+
141
+ .results-table th {
142
+ background: var(--hover-bg);
143
+ font-weight: 600;
144
+ }
145
+ </style>
146
+ </head>
147
+ <body>
148
+ <div class="simulation-container">
149
+ <header>
150
+ <h1>❓ What-If Analysis</h1>
151
+ <p class="subtitle">Simulate different scenarios and analyze how decisions change</p>
152
+ <div class="header-links">
153
+ <a href="/simulation">← Back to Simulation Dashboard</a>
154
+ </div>
155
+ </header>
156
+
157
+ <!-- Step 1: Configure Rules -->
158
+ <div class="step-section">
159
+ <h2>Configure Rules</h2>
160
+ <p>Paste your rules JSON or select a version to use</p>
161
+ <p style="margin-top: 10px;">
162
+ <strong>Need a template?</strong> Download sample files:
163
+ <a href="/sample_whatif.csv" download style="color: #007bff; text-decoration: none; margin-left: 5px;">📥 Sample Scenarios CSV</a> |
164
+ <a href="/sample_rules.json" download style="color: #007bff; text-decoration: none; margin-left: 5px;">📥 Sample Rules</a>
165
+ </p>
166
+
167
+ <div class="form-group">
168
+ <label for="rulesJson">Rules JSON:</label>
169
+ <textarea id="rulesJson" class="input" rows="10" placeholder='{"version": "1.0", "ruleset": "my_ruleset", "rules": [...]}'></textarea>
170
+ </div>
171
+
172
+ <div class="form-group">
173
+ <label for="ruleVersion">Or select version:</label>
174
+ <select id="ruleVersion" class="input">
175
+ <option value="">-- Select Version --</option>
176
+ </select>
177
+ </div>
178
+
179
+ <div class="actions">
180
+ <label for="importRulesFile" class="btn btn-secondary">📁 Import Rules JSON
181
+ <input type="file" id="importRulesFile" accept=".json" style="display: none;">
182
+ </label>
183
+ <button class="btn btn-secondary" id="validateRulesBtn">✓ Validate Rules</button>
184
+ </div>
185
+ </div>
186
+
187
+ <!-- Step 2: Define Scenarios -->
188
+ <div class="step-section">
189
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
190
+ <h2>Define Scenarios</h2>
191
+ <button class="btn btn-primary" id="addScenarioBtn">+ Add Scenario</button>
192
+ </div>
193
+
194
+ <div id="scenariosContainer" class="scenario-builder">
195
+ <div class="scenario-item">
196
+ <div class="scenario-item-header">
197
+ <strong>Scenario 1</strong>
198
+ <button class="btn btn-secondary" onclick="removeScenario(this)" style="padding: 5px 10px;">Remove</button>
199
+ </div>
200
+ <div class="scenario-fields" id="scenarioFields1">
201
+ <div class="field-input">
202
+ <label>Field Name</label>
203
+ <input type="text" class="input scenario-field-name" placeholder="e.g., credit_score">
204
+ </div>
205
+ <div class="field-input">
206
+ <label>Value</label>
207
+ <input type="text" class="input scenario-field-value" placeholder="e.g., 650">
208
+ </div>
209
+ </div>
210
+ <button class="btn btn-secondary" onclick="addField(this)" style="margin-top: 10px; padding: 5px 10px;">+ Add Field</button>
211
+ </div>
212
+ </div>
213
+ </div>
214
+
215
+ <!-- Step 3: Run Analysis -->
216
+ <div class="step-section">
217
+ <h2>Run What-If Analysis</h2>
218
+
219
+ <div class="form-group">
220
+ <label>
221
+ <input type="checkbox" id="parallelExecution" checked> Parallel execution
222
+ </label>
223
+ </div>
224
+ <div class="form-group">
225
+ <label>
226
+ <input type="checkbox" id="sensitivityAnalysis"> Perform sensitivity analysis
227
+ </label>
228
+ </div>
229
+ <div class="form-group">
230
+ <label for="threadCount">Thread count:</label>
231
+ <input type="number" id="threadCount" value="4" min="1" max="16" class="input" style="width: 100px;">
232
+ </div>
233
+
234
+ <button class="btn btn-primary" id="runAnalysisBtn">▶ Run Analysis</button>
235
+
236
+ <div id="runStatus" class="hidden"></div>
237
+ </div>
238
+
239
+ <!-- Step 4: Results -->
240
+ <div class="step-section" id="resultsSection" style="display: none;">
241
+ <h2>Analysis Results</h2>
242
+
243
+ <!-- Statistics -->
244
+ <div id="statisticsSection" class="hidden">
245
+ <h3>Statistics</h3>
246
+ <div class="metrics-grid" id="statisticsGrid"></div>
247
+ </div>
248
+
249
+ <!-- Decision Distribution -->
250
+ <div id="distributionSection" class="hidden">
251
+ <h3>Decision Distribution</h3>
252
+ <div id="distributionContent"></div>
253
+ </div>
254
+
255
+ <!-- Scenario Results -->
256
+ <div id="scenarioResultsSection" class="hidden">
257
+ <h3>Scenario Results</h3>
258
+ <div id="scenarioResultsContent"></div>
259
+ </div>
260
+
261
+ <!-- Sensitivity Analysis -->
262
+ <div id="sensitivitySection" class="hidden">
263
+ <h3>Sensitivity Analysis</h3>
264
+ <div id="sensitivityContent"></div>
265
+ </div>
266
+ </div>
267
+ </div>
268
+
269
+ <script>
270
+ // Helper function to get the base path for API calls
271
+ function getBasePath() {
272
+ const baseTag = document.querySelector('base');
273
+ if (baseTag && baseTag.href) {
274
+ try {
275
+ const baseUrl = new URL(baseTag.href, window.location.href);
276
+ let path = baseUrl.pathname;
277
+ if (path && !path.endsWith('/')) path += '/';
278
+ return path;
279
+ } catch (e) {
280
+ // fallback
281
+ }
282
+ }
283
+ const pathname = window.location.pathname;
284
+ const dirPath = pathname.substring(0, pathname.lastIndexOf('/') + 1);
285
+ return dirPath || '/';
286
+ }
287
+
288
+ function getAuthHeaders() {
289
+ const token = localStorage.getItem('auth_token');
290
+ const headers = { 'Content-Type': 'application/json' };
291
+ if (token) headers['Authorization'] = `Bearer ${token}`;
292
+ return headers;
293
+ }
294
+
295
+ const basePath = getBasePath();
296
+ let scenarioCount = 1;
297
+
298
+ // Load versions
299
+ function loadVersions() {
300
+ fetch(`${basePath}api/versions`, { headers: getAuthHeaders() })
301
+ .then(response => response.json())
302
+ .then(data => {
303
+ const select = document.getElementById('ruleVersion');
304
+ select.innerHTML = '<option value="">-- Select Version --</option>';
305
+ if (data.versions) {
306
+ data.versions.forEach(v => {
307
+ const option = document.createElement('option');
308
+ option.value = v.id;
309
+ option.textContent = `${v.rule_id} v${v.version_number} (${v.status})`;
310
+ select.appendChild(option);
311
+ });
312
+ }
313
+ })
314
+ .catch(error => console.error('Error loading versions:', error));
315
+ }
316
+
317
+ loadVersions();
318
+
319
+ // Add scenario
320
+ document.getElementById('addScenarioBtn').addEventListener('click', () => {
321
+ scenarioCount++;
322
+ const container = document.getElementById('scenariosContainer');
323
+ const scenario = document.createElement('div');
324
+ scenario.className = 'scenario-item';
325
+ scenario.innerHTML = `
326
+ <div class="scenario-item-header">
327
+ <strong>Scenario ${scenarioCount}</strong>
328
+ <button class="btn btn-secondary" onclick="removeScenario(this)" style="padding: 5px 10px;">Remove</button>
329
+ </div>
330
+ <div class="scenario-fields" id="scenarioFields${scenarioCount}">
331
+ <div class="field-input">
332
+ <label>Field Name</label>
333
+ <input type="text" class="input scenario-field-name" placeholder="e.g., credit_score">
334
+ </div>
335
+ <div class="field-input">
336
+ <label>Value</label>
337
+ <input type="text" class="input scenario-field-value" placeholder="e.g., 650">
338
+ </div>
339
+ </div>
340
+ <button class="btn btn-secondary" onclick="addField(this)" style="margin-top: 10px; padding: 5px 10px;">+ Add Field</button>
341
+ `;
342
+ container.appendChild(scenario);
343
+ });
344
+
345
+ function removeScenario(btn) {
346
+ btn.closest('.scenario-item').remove();
347
+ }
348
+
349
+ function addField(btn) {
350
+ const fieldsContainer = btn.previousElementSibling;
351
+ const fieldDiv = document.createElement('div');
352
+ fieldDiv.className = 'field-input';
353
+ fieldDiv.innerHTML = `
354
+ <label>Field Name</label>
355
+ <input type="text" class="input scenario-field-name" placeholder="e.g., amount">
356
+ <label style="margin-top: 5px;">Value</label>
357
+ <input type="text" class="input scenario-field-value" placeholder="e.g., 100000">
358
+ `;
359
+ fieldsContainer.appendChild(fieldDiv);
360
+ }
361
+
362
+ // Import rules
363
+ document.getElementById('importRulesFile').addEventListener('change', (e) => {
364
+ const file = e.target.files[0];
365
+ if (file) {
366
+ const reader = new FileReader();
367
+ reader.onload = (e) => {
368
+ try {
369
+ const json = JSON.parse(e.target.result);
370
+ document.getElementById('rulesJson').value = JSON.stringify(json, null, 2);
371
+ } catch (error) {
372
+ alert('Invalid JSON file: ' + error.message);
373
+ }
374
+ };
375
+ reader.readAsText(file);
376
+ }
377
+ });
378
+
379
+ // Validate rules
380
+ document.getElementById('validateRulesBtn').addEventListener('click', () => {
381
+ const rulesJson = document.getElementById('rulesJson').value;
382
+ try {
383
+ JSON.parse(rulesJson);
384
+ fetch(`${basePath}api/validate`, {
385
+ method: 'POST',
386
+ headers: getAuthHeaders(),
387
+ body: JSON.stringify(JSON.parse(rulesJson))
388
+ })
389
+ .then(response => response.json())
390
+ .then(data => {
391
+ if (data.valid) {
392
+ alert('Rules are valid!');
393
+ } else {
394
+ alert('Validation errors: ' + data.errors.join(', '));
395
+ }
396
+ });
397
+ } catch (error) {
398
+ alert('Invalid JSON: ' + error.message);
399
+ }
400
+ });
401
+
402
+ // Run analysis
403
+ document.getElementById('runAnalysisBtn').addEventListener('click', () => {
404
+ // Collect scenarios
405
+ const scenarios = [];
406
+ document.querySelectorAll('.scenario-item').forEach(item => {
407
+ const scenario = {};
408
+ const fields = item.querySelectorAll('.scenario-fields');
409
+ fields.forEach(fieldContainer => {
410
+ const names = fieldContainer.querySelectorAll('.scenario-field-name');
411
+ const values = fieldContainer.querySelectorAll('.scenario-field-value');
412
+ names.forEach((name, i) => {
413
+ if (name.value && values[i] && values[i].value) {
414
+ const value = values[i].value;
415
+ // Try to parse as number
416
+ scenario[name.value] = isNaN(value) ? value : parseFloat(value);
417
+ }
418
+ });
419
+ });
420
+ if (Object.keys(scenario).length > 0) {
421
+ scenarios.push(scenario);
422
+ }
423
+ });
424
+
425
+ if (scenarios.length === 0) {
426
+ alert('Please define at least one scenario');
427
+ return;
428
+ }
429
+
430
+ const rulesJson = document.getElementById('rulesJson').value;
431
+ const ruleVersion = document.getElementById('ruleVersion').value;
432
+
433
+ if (!rulesJson.trim() && !ruleVersion) {
434
+ alert('Please provide rules JSON or select a version');
435
+ return;
436
+ }
437
+
438
+ try {
439
+ const rules = rulesJson.trim() ? JSON.parse(rulesJson) : null;
440
+
441
+ const runStatus = document.getElementById('runStatus');
442
+ runStatus.className = 'hidden';
443
+
444
+ const requestData = {
445
+ scenarios: scenarios,
446
+ options: {
447
+ parallel: document.getElementById('parallelExecution').checked,
448
+ thread_count: parseInt(document.getElementById('threadCount').value) || 4,
449
+ sensitivity_analysis: document.getElementById('sensitivityAnalysis').checked
450
+ }
451
+ };
452
+
453
+ if (rules) {
454
+ requestData.rules = rules;
455
+ }
456
+ if (ruleVersion) {
457
+ requestData.rule_version = ruleVersion;
458
+ }
459
+
460
+ fetch(`${basePath}api/simulation/whatif`, {
461
+ method: 'POST',
462
+ headers: getAuthHeaders(),
463
+ body: JSON.stringify(requestData)
464
+ })
465
+ .then(response => response.json())
466
+ .then(data => {
467
+ if (data.error) {
468
+ runStatus.className = 'error-message';
469
+ runStatus.textContent = `Error: ${data.error}`;
470
+ runStatus.classList.remove('hidden');
471
+ } else {
472
+ runStatus.className = 'success-message';
473
+ runStatus.textContent = 'Analysis completed successfully!';
474
+ runStatus.classList.remove('hidden');
475
+ displayResults(data);
476
+ }
477
+ })
478
+ .catch(error => {
479
+ runStatus.className = 'error-message';
480
+ runStatus.textContent = `Error: ${error.message}`;
481
+ runStatus.classList.remove('hidden');
482
+ });
483
+ } catch (error) {
484
+ alert('Invalid JSON: ' + error.message);
485
+ }
486
+ });
487
+
488
+ function displayResults(data) {
489
+ document.getElementById('resultsSection').style.display = 'block';
490
+ const results = data.results;
491
+
492
+ // Statistics
493
+ if (results.total_scenarios !== undefined) {
494
+ const statsGrid = document.getElementById('statisticsGrid');
495
+ statsGrid.innerHTML = `
496
+ <div class="metric-card">
497
+ <div class="metric-value">${results.total_scenarios}</div>
498
+ <div class="metric-label">Total Scenarios</div>
499
+ </div>
500
+ ${results.average_confidence !== undefined ? `
501
+ <div class="metric-card">
502
+ <div class="metric-value">${results.average_confidence.toFixed(3)}</div>
503
+ <div class="metric-label">Avg Confidence</div>
504
+ </div>
505
+ ` : ''}
506
+ `;
507
+ document.getElementById('statisticsSection').classList.remove('hidden');
508
+ }
509
+
510
+ // Decision distribution
511
+ if (results.decision_distribution) {
512
+ const distContent = document.getElementById('distributionContent');
513
+ let html = '<table class="results-table"><thead><tr><th>Decision</th><th>Count</th></tr></thead><tbody>';
514
+ Object.entries(results.decision_distribution).forEach(([decision, count]) => {
515
+ html += `<tr><td>${decision}</td><td>${count}</td></tr>`;
516
+ });
517
+ html += '</tbody></table>';
518
+ distContent.innerHTML = html;
519
+ document.getElementById('distributionSection').classList.remove('hidden');
520
+ }
521
+
522
+ // Scenario results
523
+ if (results.scenarios) {
524
+ const scenarioContent = document.getElementById('scenarioResultsContent');
525
+ let html = '<table class="results-table"><thead><tr><th>Scenario</th><th>Decision</th><th>Confidence</th></tr></thead><tbody>';
526
+ results.scenarios.forEach((s, i) => {
527
+ html += `<tr>
528
+ <td>${JSON.stringify(s.scenario)}</td>
529
+ <td>${s.decision}</td>
530
+ <td>${s.confidence.toFixed(3)}</td>
531
+ </tr>`;
532
+ });
533
+ html += '</tbody></table>';
534
+ scenarioContent.innerHTML = html;
535
+ document.getElementById('scenarioResultsSection').classList.remove('hidden');
536
+ }
537
+
538
+ // Sensitivity analysis
539
+ if (results.sensitivity) {
540
+ const sensContent = document.getElementById('sensitivityContent');
541
+ let html = '<p>Sensitivity analysis results:</p><pre>' + JSON.stringify(results.sensitivity, null, 2) + '</pre>';
542
+ sensContent.innerHTML = html;
543
+ document.getElementById('sensitivitySection').classList.remove('hidden');
544
+ }
545
+ }
546
+ </script>
547
+ </body>
548
+ </html>
549
+
@@ -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
+ }
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>User Management - DecisionAgent</title>
7
- <link rel="stylesheet" href="styles.css">
7
+ <link rel="stylesheet" href="/styles.css">
8
8
  <style>
9
9
  .users-container {
10
10
  max-width: 1200px;