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.
- checksums.yaml +4 -4
- data/README.md +234 -14
- data/lib/decision_agent/ab_testing/ab_test.rb +5 -1
- data/lib/decision_agent/ab_testing/ab_test_assignment.rb +2 -0
- data/lib/decision_agent/ab_testing/ab_test_manager.rb +2 -0
- data/lib/decision_agent/ab_testing/ab_testing_agent.rb +2 -0
- data/lib/decision_agent/ab_testing/storage/activerecord_adapter.rb +2 -13
- data/lib/decision_agent/ab_testing/storage/adapter.rb +2 -0
- data/lib/decision_agent/ab_testing/storage/memory_adapter.rb +2 -0
- data/lib/decision_agent/agent.rb +78 -9
- data/lib/decision_agent/audit/adapter.rb +2 -0
- data/lib/decision_agent/audit/logger_adapter.rb +2 -0
- data/lib/decision_agent/audit/null_adapter.rb +2 -0
- data/lib/decision_agent/auth/access_audit_logger.rb +2 -0
- data/lib/decision_agent/auth/authenticator.rb +2 -0
- data/lib/decision_agent/auth/password_reset_manager.rb +2 -0
- data/lib/decision_agent/auth/password_reset_token.rb +2 -0
- data/lib/decision_agent/auth/permission.rb +2 -0
- data/lib/decision_agent/auth/permission_checker.rb +2 -0
- data/lib/decision_agent/auth/rbac_adapter.rb +2 -0
- data/lib/decision_agent/auth/rbac_config.rb +2 -0
- data/lib/decision_agent/auth/role.rb +2 -0
- data/lib/decision_agent/auth/session.rb +2 -0
- data/lib/decision_agent/auth/session_manager.rb +2 -0
- data/lib/decision_agent/auth/user.rb +2 -0
- data/lib/decision_agent/context.rb +14 -0
- data/lib/decision_agent/decision.rb +113 -4
- data/lib/decision_agent/dmn/adapter.rb +2 -0
- data/lib/decision_agent/dmn/cache.rb +2 -2
- data/lib/decision_agent/dmn/decision_graph.rb +7 -7
- data/lib/decision_agent/dmn/decision_tree.rb +16 -8
- data/lib/decision_agent/dmn/errors.rb +2 -0
- data/lib/decision_agent/dmn/exporter.rb +2 -0
- data/lib/decision_agent/dmn/feel/evaluator.rb +130 -114
- data/lib/decision_agent/dmn/feel/functions.rb +2 -0
- data/lib/decision_agent/dmn/feel/parser.rb +2 -0
- data/lib/decision_agent/dmn/feel/simple_parser.rb +98 -77
- data/lib/decision_agent/dmn/feel/transformer.rb +56 -102
- data/lib/decision_agent/dmn/feel/types.rb +2 -0
- data/lib/decision_agent/dmn/importer.rb +2 -0
- data/lib/decision_agent/dmn/model.rb +2 -4
- data/lib/decision_agent/dmn/parser.rb +2 -0
- data/lib/decision_agent/dmn/testing.rb +3 -2
- data/lib/decision_agent/dmn/validator.rb +5 -3
- data/lib/decision_agent/dmn/visualizer.rb +7 -6
- data/lib/decision_agent/dsl/condition_evaluator.rb +242 -1375
- data/lib/decision_agent/dsl/helpers/cache_helpers.rb +82 -0
- data/lib/decision_agent/dsl/helpers/comparison_helpers.rb +98 -0
- data/lib/decision_agent/dsl/helpers/date_helpers.rb +91 -0
- data/lib/decision_agent/dsl/helpers/geospatial_helpers.rb +85 -0
- data/lib/decision_agent/dsl/helpers/operator_evaluation_helpers.rb +160 -0
- data/lib/decision_agent/dsl/helpers/parameter_parsing_helpers.rb +206 -0
- data/lib/decision_agent/dsl/helpers/template_helpers.rb +39 -0
- data/lib/decision_agent/dsl/helpers/utility_helpers.rb +45 -0
- data/lib/decision_agent/dsl/operators/base.rb +70 -0
- data/lib/decision_agent/dsl/operators/basic_comparison_operators.rb +80 -0
- data/lib/decision_agent/dsl/operators/collection_operators.rb +60 -0
- data/lib/decision_agent/dsl/operators/date_arithmetic_operators.rb +206 -0
- data/lib/decision_agent/dsl/operators/date_time_operators.rb +47 -0
- data/lib/decision_agent/dsl/operators/duration_operators.rb +149 -0
- data/lib/decision_agent/dsl/operators/financial_operators.rb +237 -0
- data/lib/decision_agent/dsl/operators/geospatial_operators.rb +106 -0
- data/lib/decision_agent/dsl/operators/mathematical_operators.rb +234 -0
- data/lib/decision_agent/dsl/operators/moving_window_operators.rb +135 -0
- data/lib/decision_agent/dsl/operators/numeric_operators.rb +120 -0
- data/lib/decision_agent/dsl/operators/rate_operators.rb +65 -0
- data/lib/decision_agent/dsl/operators/statistical_aggregations.rb +187 -0
- data/lib/decision_agent/dsl/operators/string_aggregations.rb +84 -0
- data/lib/decision_agent/dsl/operators/string_operators.rb +72 -0
- data/lib/decision_agent/dsl/operators/time_component_operators.rb +72 -0
- data/lib/decision_agent/dsl/rule_parser.rb +2 -0
- data/lib/decision_agent/dsl/schema_validator.rb +37 -14
- data/lib/decision_agent/errors.rb +2 -0
- data/lib/decision_agent/evaluation.rb +14 -2
- data/lib/decision_agent/evaluators/base.rb +2 -0
- data/lib/decision_agent/evaluators/dmn_evaluator.rb +108 -19
- data/lib/decision_agent/evaluators/json_rule_evaluator.rb +56 -11
- data/lib/decision_agent/evaluators/static_evaluator.rb +2 -0
- data/lib/decision_agent/explainability/condition_trace.rb +85 -0
- data/lib/decision_agent/explainability/explainability_result.rb +50 -0
- data/lib/decision_agent/explainability/rule_trace.rb +41 -0
- data/lib/decision_agent/explainability/trace_collector.rb +26 -0
- data/lib/decision_agent/monitoring/alert_manager.rb +7 -16
- data/lib/decision_agent/monitoring/dashboard_server.rb +383 -250
- data/lib/decision_agent/monitoring/metrics_collector.rb +2 -0
- data/lib/decision_agent/monitoring/monitored_agent.rb +2 -0
- data/lib/decision_agent/monitoring/prometheus_exporter.rb +3 -1
- data/lib/decision_agent/replay/replay.rb +4 -1
- data/lib/decision_agent/scoring/base.rb +2 -0
- data/lib/decision_agent/scoring/consensus.rb +2 -0
- data/lib/decision_agent/scoring/max_weight.rb +2 -0
- data/lib/decision_agent/scoring/threshold.rb +2 -0
- data/lib/decision_agent/scoring/weighted_average.rb +2 -0
- data/lib/decision_agent/simulation/errors.rb +20 -0
- data/lib/decision_agent/simulation/impact_analyzer.rb +500 -0
- data/lib/decision_agent/simulation/monte_carlo_simulator.rb +638 -0
- data/lib/decision_agent/simulation/replay_engine.rb +488 -0
- data/lib/decision_agent/simulation/scenario_engine.rb +320 -0
- data/lib/decision_agent/simulation/scenario_library.rb +165 -0
- data/lib/decision_agent/simulation/shadow_test_engine.rb +274 -0
- data/lib/decision_agent/simulation/what_if_analyzer.rb +1008 -0
- data/lib/decision_agent/simulation.rb +19 -0
- data/lib/decision_agent/testing/batch_test_importer.rb +6 -2
- data/lib/decision_agent/testing/batch_test_runner.rb +5 -2
- data/lib/decision_agent/testing/test_coverage_analyzer.rb +2 -0
- data/lib/decision_agent/testing/test_result_comparator.rb +2 -0
- data/lib/decision_agent/testing/test_scenario.rb +2 -0
- data/lib/decision_agent/version.rb +3 -1
- data/lib/decision_agent/versioning/activerecord_adapter.rb +108 -43
- data/lib/decision_agent/versioning/adapter.rb +9 -0
- data/lib/decision_agent/versioning/file_storage_adapter.rb +19 -6
- data/lib/decision_agent/versioning/version_manager.rb +9 -0
- data/lib/decision_agent/web/dmn_editor/serialization.rb +74 -0
- data/lib/decision_agent/web/dmn_editor/xml_builder.rb +107 -0
- data/lib/decision_agent/web/dmn_editor.rb +8 -67
- data/lib/decision_agent/web/middleware/auth_middleware.rb +2 -0
- data/lib/decision_agent/web/middleware/permission_middleware.rb +3 -1
- data/lib/decision_agent/web/public/app.js +186 -26
- data/lib/decision_agent/web/public/batch_testing.html +80 -6
- data/lib/decision_agent/web/public/dmn-editor.html +2 -2
- data/lib/decision_agent/web/public/dmn-editor.js +74 -8
- data/lib/decision_agent/web/public/index.html +69 -3
- data/lib/decision_agent/web/public/login.html +1 -1
- data/lib/decision_agent/web/public/sample_batch.csv +11 -0
- data/lib/decision_agent/web/public/sample_impact.csv +11 -0
- data/lib/decision_agent/web/public/sample_replay.csv +11 -0
- data/lib/decision_agent/web/public/sample_rules.json +118 -0
- data/lib/decision_agent/web/public/sample_shadow.csv +11 -0
- data/lib/decision_agent/web/public/sample_whatif.csv +11 -0
- data/lib/decision_agent/web/public/simulation.html +146 -0
- data/lib/decision_agent/web/public/simulation_impact.html +495 -0
- data/lib/decision_agent/web/public/simulation_replay.html +547 -0
- data/lib/decision_agent/web/public/simulation_shadow.html +561 -0
- data/lib/decision_agent/web/public/simulation_whatif.html +549 -0
- data/lib/decision_agent/web/public/styles.css +65 -0
- data/lib/decision_agent/web/public/users.html +1 -1
- data/lib/decision_agent/web/rack_helpers.rb +106 -0
- data/lib/decision_agent/web/rack_request_helpers.rb +196 -0
- data/lib/decision_agent/web/server.rb +2126 -1374
- data/lib/decision_agent.rb +19 -1
- data/lib/generators/decision_agent/install/install_generator.rb +2 -0
- data/lib/generators/decision_agent/install/templates/ab_test_assignment_model.rb +2 -0
- data/lib/generators/decision_agent/install/templates/ab_test_model.rb +2 -0
- data/lib/generators/decision_agent/install/templates/ab_testing_migration.rb +2 -0
- data/lib/generators/decision_agent/install/templates/migration.rb +2 -0
- data/lib/generators/decision_agent/install/templates/rule.rb +2 -0
- data/lib/generators/decision_agent/install/templates/rule_version.rb +2 -0
- metadata +103 -89
- data/spec/ab_testing/ab_test_assignment_spec.rb +0 -253
- data/spec/ab_testing/ab_test_manager_spec.rb +0 -612
- data/spec/ab_testing/ab_test_spec.rb +0 -270
- data/spec/ab_testing/ab_testing_agent_spec.rb +0 -655
- data/spec/ab_testing/storage/adapter_spec.rb +0 -64
- data/spec/ab_testing/storage/memory_adapter_spec.rb +0 -485
- data/spec/activerecord_thread_safety_spec.rb +0 -553
- data/spec/advanced_operators_spec.rb +0 -3150
- data/spec/agent_spec.rb +0 -289
- data/spec/api_contract_spec.rb +0 -430
- data/spec/audit_adapters_spec.rb +0 -92
- data/spec/auth/access_audit_logger_spec.rb +0 -394
- data/spec/auth/authenticator_spec.rb +0 -112
- data/spec/auth/password_reset_spec.rb +0 -294
- data/spec/auth/permission_checker_spec.rb +0 -207
- data/spec/auth/permission_spec.rb +0 -73
- data/spec/auth/rbac_adapter_spec.rb +0 -778
- data/spec/auth/rbac_config_spec.rb +0 -82
- data/spec/auth/role_spec.rb +0 -51
- data/spec/auth/session_manager_spec.rb +0 -172
- data/spec/auth/session_spec.rb +0 -112
- data/spec/auth/user_spec.rb +0 -130
- data/spec/comprehensive_edge_cases_spec.rb +0 -1777
- data/spec/context_spec.rb +0 -127
- data/spec/decision_agent_spec.rb +0 -96
- data/spec/decision_spec.rb +0 -423
- data/spec/dmn/decision_graph_spec.rb +0 -282
- data/spec/dmn/decision_tree_spec.rb +0 -203
- data/spec/dmn/feel/errors_spec.rb +0 -18
- data/spec/dmn/feel/functions_spec.rb +0 -400
- data/spec/dmn/feel/simple_parser_spec.rb +0 -274
- data/spec/dmn/feel/types_spec.rb +0 -176
- data/spec/dmn/feel_parser_spec.rb +0 -489
- data/spec/dmn/hit_policy_spec.rb +0 -202
- data/spec/dmn/integration_spec.rb +0 -226
- data/spec/dsl/condition_evaluator_spec.rb +0 -774
- data/spec/dsl_validation_spec.rb +0 -648
- data/spec/edge_cases_spec.rb +0 -353
- data/spec/evaluation_spec.rb +0 -364
- data/spec/evaluation_validator_spec.rb +0 -165
- data/spec/examples/feedback_aware_evaluator_spec.rb +0 -460
- data/spec/examples.txt +0 -1909
- data/spec/fixtures/dmn/complex_decision.dmn +0 -81
- data/spec/fixtures/dmn/invalid_structure.dmn +0 -31
- data/spec/fixtures/dmn/simple_decision.dmn +0 -40
- data/spec/issue_verification_spec.rb +0 -759
- data/spec/json_rule_evaluator_spec.rb +0 -587
- data/spec/monitoring/alert_manager_spec.rb +0 -378
- data/spec/monitoring/metrics_collector_spec.rb +0 -501
- data/spec/monitoring/monitored_agent_spec.rb +0 -225
- data/spec/monitoring/prometheus_exporter_spec.rb +0 -242
- data/spec/monitoring/storage/activerecord_adapter_spec.rb +0 -498
- data/spec/monitoring/storage/base_adapter_spec.rb +0 -61
- data/spec/monitoring/storage/memory_adapter_spec.rb +0 -247
- data/spec/performance_optimizations_spec.rb +0 -493
- data/spec/replay_edge_cases_spec.rb +0 -699
- data/spec/replay_spec.rb +0 -210
- data/spec/rfc8785_canonicalization_spec.rb +0 -215
- data/spec/scoring_spec.rb +0 -225
- data/spec/spec_helper.rb +0 -60
- data/spec/testing/batch_test_importer_spec.rb +0 -693
- data/spec/testing/batch_test_runner_spec.rb +0 -307
- data/spec/testing/test_coverage_analyzer_spec.rb +0 -292
- data/spec/testing/test_result_comparator_spec.rb +0 -392
- data/spec/testing/test_scenario_spec.rb +0 -113
- data/spec/thread_safety_spec.rb +0 -490
- data/spec/thread_safety_spec.rb.broken +0 -878
- data/spec/versioning/adapter_spec.rb +0 -156
- data/spec/versioning_spec.rb +0 -1030
- data/spec/web/middleware/auth_middleware_spec.rb +0 -133
- data/spec/web/middleware/permission_middleware_spec.rb +0 -247
- data/spec/web_ui_rack_spec.rb +0 -2134
|
@@ -15,6 +15,11 @@ module DecisionAgent
|
|
|
15
15
|
# DMN Editor Backend
|
|
16
16
|
# Provides API endpoints for visual DMN modeling
|
|
17
17
|
class DmnEditor
|
|
18
|
+
require_relative "dmn_editor/serialization"
|
|
19
|
+
require_relative "dmn_editor/xml_builder"
|
|
20
|
+
include DmnEditor::Serialization
|
|
21
|
+
include DmnEditor::XmlBuilder
|
|
22
|
+
|
|
18
23
|
attr_reader :storage
|
|
19
24
|
|
|
20
25
|
def initialize(storage: nil)
|
|
@@ -249,8 +254,9 @@ module DecisionAgent
|
|
|
249
254
|
model = retrieve_model(model_id)
|
|
250
255
|
return nil unless model
|
|
251
256
|
|
|
252
|
-
|
|
253
|
-
|
|
257
|
+
# Convert model to DMN XML directly using the model's to_xml method
|
|
258
|
+
# or generate XML from the model's structure
|
|
259
|
+
generate_dmn_xml(model)
|
|
254
260
|
end
|
|
255
261
|
|
|
256
262
|
# Import DMN model from XML
|
|
@@ -356,71 +362,6 @@ module DecisionAgent
|
|
|
356
362
|
table.instance_variable_set(:@outputs, logic[:outputs]) if logic[:outputs]
|
|
357
363
|
table.instance_variable_set(:@rules, logic[:rules]) if logic[:rules]
|
|
358
364
|
end
|
|
359
|
-
|
|
360
|
-
def serialize_model(model)
|
|
361
|
-
{
|
|
362
|
-
id: model.id,
|
|
363
|
-
name: model.name,
|
|
364
|
-
namespace: model.namespace,
|
|
365
|
-
decisions: model.decisions.map { |d| serialize_decision(d) }
|
|
366
|
-
}
|
|
367
|
-
end
|
|
368
|
-
|
|
369
|
-
def serialize_decision(decision)
|
|
370
|
-
result = {
|
|
371
|
-
id: decision.id,
|
|
372
|
-
name: decision.name
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if decision.decision_table
|
|
376
|
-
result[:decision_table] = serialize_decision_table(decision.decision_table)
|
|
377
|
-
elsif decision.decision_tree
|
|
378
|
-
result[:decision_tree] = decision.decision_tree.to_h
|
|
379
|
-
elsif decision.instance_variable_get(:@literal_expression)
|
|
380
|
-
result[:literal_expression] = decision.instance_variable_get(:@literal_expression)
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
result[:information_requirements] = decision.information_requirements if decision.information_requirements.any?
|
|
384
|
-
|
|
385
|
-
result
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
def serialize_decision_table(table)
|
|
389
|
-
{
|
|
390
|
-
id: table.id,
|
|
391
|
-
hit_policy: table.hit_policy,
|
|
392
|
-
inputs: table.inputs.map { |i| serialize_input(i) },
|
|
393
|
-
outputs: table.outputs.map { |o| serialize_output(o) },
|
|
394
|
-
rules: table.rules.map { |r| serialize_rule(r) }
|
|
395
|
-
}
|
|
396
|
-
end
|
|
397
|
-
|
|
398
|
-
def serialize_input(input)
|
|
399
|
-
{
|
|
400
|
-
id: input.id,
|
|
401
|
-
label: input.label,
|
|
402
|
-
type_ref: input.type_ref,
|
|
403
|
-
expression: input.expression
|
|
404
|
-
}
|
|
405
|
-
end
|
|
406
|
-
|
|
407
|
-
def serialize_output(output)
|
|
408
|
-
{
|
|
409
|
-
id: output.id,
|
|
410
|
-
label: output.label,
|
|
411
|
-
type_ref: output.type_ref,
|
|
412
|
-
name: output.name
|
|
413
|
-
}
|
|
414
|
-
end
|
|
415
|
-
|
|
416
|
-
def serialize_rule(rule)
|
|
417
|
-
{
|
|
418
|
-
id: rule.id,
|
|
419
|
-
input_entries: rule.input_entries,
|
|
420
|
-
output_entries: rule.output_entries,
|
|
421
|
-
description: rule.description
|
|
422
|
-
}
|
|
423
|
-
end
|
|
424
365
|
end
|
|
425
366
|
end
|
|
426
367
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "rack"
|
|
2
4
|
require "json"
|
|
3
5
|
|
|
@@ -58,7 +60,7 @@ module DecisionAgent
|
|
|
58
60
|
|
|
59
61
|
def extract_resource_id(env)
|
|
60
62
|
request = Rack::Request.new(env)
|
|
61
|
-
# Try params first (for query parameters and
|
|
63
|
+
# Try params first (for query parameters and path params)
|
|
62
64
|
resource_id = request.params["id"] || request.params["rule_id"] || request.params["version_id"]
|
|
63
65
|
|
|
64
66
|
# If not in params, try to extract from path (e.g., /api/rules/123 -> 123)
|
|
@@ -84,6 +84,7 @@ class RuleBuilder {
|
|
|
84
84
|
|
|
85
85
|
// Actions
|
|
86
86
|
document.getElementById('validateBtn').addEventListener('click', () => this.validateRules());
|
|
87
|
+
document.getElementById('testRuleBtn').addEventListener('click', () => this.openTestRuleModal());
|
|
87
88
|
document.getElementById('clearBtn').addEventListener('click', () => this.clearAll());
|
|
88
89
|
document.getElementById('loadExampleBtn').addEventListener('click', () => this.loadExample());
|
|
89
90
|
|
|
@@ -101,6 +102,11 @@ class RuleBuilder {
|
|
|
101
102
|
document.getElementById('closeCompareBtn').addEventListener('click', () => this.closeCompareModal());
|
|
102
103
|
document.getElementById('closeCompareModalBtn').addEventListener('click', () => this.closeCompareModal());
|
|
103
104
|
|
|
105
|
+
// Test Rule
|
|
106
|
+
document.getElementById('runTestBtn').addEventListener('click', () => this.runTest());
|
|
107
|
+
document.getElementById('closeTestRuleBtn').addEventListener('click', () => this.closeTestRuleModal());
|
|
108
|
+
document.getElementById('closeTestRuleModalBtn').addEventListener('click', () => this.closeTestRuleModal());
|
|
109
|
+
|
|
104
110
|
// Modal close on outside click
|
|
105
111
|
document.getElementById('ruleModal').addEventListener('click', (e) => {
|
|
106
112
|
if (e.target.id === 'ruleModal') {
|
|
@@ -307,20 +313,40 @@ class RuleBuilder {
|
|
|
307
313
|
'between': '[min, max] or {"min": 0, "max": 100}',
|
|
308
314
|
'modulo': '[divisor, remainder] or {"divisor": 2, "remainder": 0}',
|
|
309
315
|
|
|
310
|
-
// Mathematical functions
|
|
311
|
-
'sin': 'expected result (e.g., 0.0)',
|
|
312
|
-
'cos': 'expected result (e.g., 1.0)',
|
|
313
|
-
'tan': 'expected result (e.g., 0.0)',
|
|
314
|
-
'
|
|
316
|
+
// Mathematical functions - Trigonometric
|
|
317
|
+
'sin': 'expected result (e.g., 0.0 for sin(0))',
|
|
318
|
+
'cos': 'expected result (e.g., 1.0 for cos(0))',
|
|
319
|
+
'tan': 'expected result (e.g., 0.0 for tan(0))',
|
|
320
|
+
'asin': 'expected result (e.g., 1.571 for asin(1))',
|
|
321
|
+
'acos': 'expected result (e.g., 0.0 for acos(1))',
|
|
322
|
+
'atan': 'expected result (e.g., 0.785 for atan(1))',
|
|
323
|
+
'atan2': '{"y": 1, "result": 0.785} or [1, 0.785]',
|
|
324
|
+
// Hyperbolic
|
|
325
|
+
'sinh': 'expected result (e.g., 0.0 for sinh(0))',
|
|
326
|
+
'cosh': 'expected result (e.g., 1.0 for cosh(0))',
|
|
327
|
+
'tanh': 'expected result (e.g., 0.0 for tanh(0))',
|
|
328
|
+
// Power and roots
|
|
329
|
+
'sqrt': 'expected result (e.g., 3.0 for sqrt(9))',
|
|
330
|
+
'cbrt': 'expected result (e.g., 2.0 for cbrt(8))',
|
|
315
331
|
'power': '[exponent, result] or {"exponent": 2, "result": 4}',
|
|
316
|
-
'exp': 'expected result (e.g., 2.718)',
|
|
317
|
-
|
|
318
|
-
'
|
|
319
|
-
'
|
|
320
|
-
'
|
|
321
|
-
|
|
322
|
-
'
|
|
323
|
-
'
|
|
332
|
+
'exp': 'expected result (e.g., 2.718 for exp(1))',
|
|
333
|
+
// Logarithmic
|
|
334
|
+
'log': 'expected result (e.g., 0.0 for log(1))',
|
|
335
|
+
'log10': 'expected result (e.g., 2.0 for log10(100))',
|
|
336
|
+
'log2': 'expected result (e.g., 3.0 for log2(8))',
|
|
337
|
+
// Rounding
|
|
338
|
+
'round': 'expected rounded value (e.g., 3 for round(3.4))',
|
|
339
|
+
'floor': 'expected floor value (e.g., 3 for floor(3.9))',
|
|
340
|
+
'ceil': 'expected ceiling value (e.g., 4 for ceil(3.1))',
|
|
341
|
+
'truncate': 'expected truncated value (e.g., 3 for truncate(3.9))',
|
|
342
|
+
'abs': 'expected absolute value (e.g., 5 for abs(-5))',
|
|
343
|
+
// Advanced math
|
|
344
|
+
'factorial': 'expected result (e.g., 120 for factorial(5))',
|
|
345
|
+
'gcd': '{"other": 12, "result": 6} or [12, 6]',
|
|
346
|
+
'lcm': '{"other": 12, "result": 36} or [12, 36]',
|
|
347
|
+
// Statistical (these are for arrays)
|
|
348
|
+
'min': 'expected minimum value (e.g., 1 for min([3, 1, 5, 2]))',
|
|
349
|
+
'max': 'expected maximum value (e.g., 5 for max([3, 1, 5, 2]))',
|
|
324
350
|
|
|
325
351
|
// Statistical aggregations
|
|
326
352
|
'sum': 'expected sum (e.g., 100) or {"min": 50, "max": 150}',
|
|
@@ -440,19 +466,40 @@ class RuleBuilder {
|
|
|
440
466
|
'day_of_week': 'Day name (monday) or number (0=Sunday, 1=Monday, ...)',
|
|
441
467
|
'within_radius': 'JSON: {"center": {"lat": y, "lon": x}, "radius": km}',
|
|
442
468
|
'in_polygon': 'Array of coordinates: [{"lat": y, "lon": x}, ...]',
|
|
443
|
-
|
|
444
|
-
'
|
|
445
|
-
'
|
|
446
|
-
'
|
|
447
|
-
'
|
|
448
|
-
'
|
|
449
|
-
'
|
|
450
|
-
'
|
|
451
|
-
|
|
452
|
-
'
|
|
453
|
-
'
|
|
454
|
-
'
|
|
455
|
-
|
|
469
|
+
// Trigonometric functions
|
|
470
|
+
'sin': 'Expected result of sin(field_value). Example: 0.0 for sin(0), 1.0 for sin(π/2)',
|
|
471
|
+
'cos': 'Expected result of cos(field_value). Example: 1.0 for cos(0), 0.0 for cos(π/2)',
|
|
472
|
+
'tan': 'Expected result of tan(field_value). Example: 0.0 for tan(0), 1.0 for tan(π/4)',
|
|
473
|
+
'asin': 'Expected result of asin(field_value). Input must be [-1, 1]. Example: 1.571 for asin(1) = π/2',
|
|
474
|
+
'acos': 'Expected result of acos(field_value). Input must be [-1, 1]. Example: 0.0 for acos(1)',
|
|
475
|
+
'atan': 'Expected result of atan(field_value). Example: 0.785 for atan(1) = π/4',
|
|
476
|
+
'atan2': 'atan2(field_value, y). Format: {"y": 1, "result": 0.785} or [1, 0.785]',
|
|
477
|
+
// Hyperbolic functions
|
|
478
|
+
'sinh': 'Expected result of sinh(field_value). Example: 0.0 for sinh(0), 1.175 for sinh(1)',
|
|
479
|
+
'cosh': 'Expected result of cosh(field_value). Example: 1.0 for cosh(0), 1.543 for cosh(1)',
|
|
480
|
+
'tanh': 'Expected result of tanh(field_value). Example: 0.0 for tanh(0), 0.762 for tanh(1)',
|
|
481
|
+
// Power and roots
|
|
482
|
+
'sqrt': 'Expected result of sqrt(field_value). Example: 3.0 for sqrt(9), 5.0 for sqrt(25)',
|
|
483
|
+
'cbrt': 'Expected result of cbrt(field_value) = cube root. Example: 2.0 for cbrt(8), -2.0 for cbrt(-8)',
|
|
484
|
+
'power': 'Power: Checks if field^exponent == result. Format: [exponent, result] or {"exponent": 2, "result": 4}. Example: [2, 4] means 2^2 == 4',
|
|
485
|
+
'exp': 'Expected result of exp(field_value) = e^field_value. Example: 2.718 for exp(1), 7.389 for exp(2)',
|
|
486
|
+
// Logarithmic functions
|
|
487
|
+
'log': 'Expected result of log(field_value) = natural logarithm (base e). Example: 0.0 for log(1), 2.303 for log(10)',
|
|
488
|
+
'log10': 'Expected result of log10(field_value) = base 10 logarithm. Example: 2.0 for log10(100), 3.0 for log10(1000)',
|
|
489
|
+
'log2': 'Expected result of log2(field_value) = base 2 logarithm. Example: 3.0 for log2(8), 4.0 for log2(16)',
|
|
490
|
+
// Rounding functions
|
|
491
|
+
'round': 'Expected rounded value to nearest integer. Example: 3 for round(3.4), 4 for round(3.6)',
|
|
492
|
+
'floor': 'Expected floor value (round down). Example: 3 for floor(3.9), -4 for floor(-3.1)',
|
|
493
|
+
'ceil': 'Expected ceiling value (round up). Example: 4 for ceil(3.1), -3 for ceil(-3.9)',
|
|
494
|
+
'truncate': 'Expected truncated value (remove decimal part). Example: 3 for truncate(3.9), -3 for truncate(-3.9)',
|
|
495
|
+
'abs': 'Expected absolute value. Example: 5 for abs(-5) or abs(5), 3.14 for abs(-3.14)',
|
|
496
|
+
// Advanced mathematical functions
|
|
497
|
+
'factorial': 'Expected result of factorial(field_value). Input must be non-negative integer. Example: 120 for factorial(5), 720 for factorial(6)',
|
|
498
|
+
'gcd': 'Greatest Common Divisor: gcd(field_value, other) == result. Format: {"other": 12, "result": 6} or [12, 6]. Example: gcd(18, 12) == 6',
|
|
499
|
+
'lcm': 'Least Common Multiple: lcm(field_value, other) == result. Format: {"other": 12, "result": 36} or [12, 36]. Example: lcm(9, 12) == 36',
|
|
500
|
+
// Statistical aggregations (for arrays)
|
|
501
|
+
'min': 'Expected minimum value from array. Example: 1 for min([3, 1, 5, 2]), 0.5 for min([1.5, 2.0, 0.5])',
|
|
502
|
+
'max': 'Expected maximum value from array. Example: 5 for max([3, 1, 5, 2]), 100.0 for max([10.5, 100.0, 50.0])'
|
|
456
503
|
};
|
|
457
504
|
|
|
458
505
|
if (hints[operator]) {
|
|
@@ -1134,6 +1181,119 @@ class RuleBuilder {
|
|
|
1134
1181
|
document.getElementById('compareVersionsModal').classList.add('hidden');
|
|
1135
1182
|
}
|
|
1136
1183
|
|
|
1184
|
+
getRulesJSON() {
|
|
1185
|
+
const version = document.getElementById('rulesetVersion').value || '1.0';
|
|
1186
|
+
const ruleset = document.getElementById('rulesetName').value || 'my_ruleset';
|
|
1187
|
+
const rules = this.rules.map(rule => ({
|
|
1188
|
+
id: rule.id,
|
|
1189
|
+
if: rule.if,
|
|
1190
|
+
then: rule.then
|
|
1191
|
+
}));
|
|
1192
|
+
|
|
1193
|
+
return {
|
|
1194
|
+
version: version,
|
|
1195
|
+
ruleset: ruleset,
|
|
1196
|
+
rules: rules
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
openTestRuleModal() {
|
|
1201
|
+
document.getElementById('testRuleModal').classList.remove('hidden');
|
|
1202
|
+
document.getElementById('testContext').value = '{}';
|
|
1203
|
+
document.getElementById('testResults').classList.add('hidden');
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
closeTestRuleModal() {
|
|
1207
|
+
document.getElementById('testRuleModal').classList.add('hidden');
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
async runTest() {
|
|
1211
|
+
const contextText = document.getElementById('testContext').value.trim();
|
|
1212
|
+
|
|
1213
|
+
// Get current rules
|
|
1214
|
+
const rules = this.getRulesJSON();
|
|
1215
|
+
|
|
1216
|
+
if (!rules || !rules.rules || rules.rules.length === 0) {
|
|
1217
|
+
alert('Please add at least one rule before testing');
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
let context;
|
|
1222
|
+
try {
|
|
1223
|
+
context = contextText ? JSON.parse(contextText) : {};
|
|
1224
|
+
} catch (e) {
|
|
1225
|
+
alert('Invalid JSON in context field: ' + e.message);
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
try {
|
|
1230
|
+
const response = await fetch(`${this.basePath}api/evaluate`, {
|
|
1231
|
+
method: 'POST',
|
|
1232
|
+
headers: this.getAuthHeaders(),
|
|
1233
|
+
body: JSON.stringify({
|
|
1234
|
+
rules: rules,
|
|
1235
|
+
context: context
|
|
1236
|
+
})
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
const data = await response.json();
|
|
1240
|
+
|
|
1241
|
+
if (!response.ok || !data.success) {
|
|
1242
|
+
alert('Test failed: ' + (data.error || 'Unknown error'));
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// Display results
|
|
1247
|
+
const resultsDiv = document.getElementById('testResults');
|
|
1248
|
+
resultsDiv.classList.remove('hidden');
|
|
1249
|
+
|
|
1250
|
+
if (data.decision) {
|
|
1251
|
+
document.getElementById('testDecisionValue').textContent = data.decision;
|
|
1252
|
+
document.getElementById('testConfidenceValue').textContent = (data.confidence || 0).toFixed(3);
|
|
1253
|
+
document.getElementById('testReasonValue').textContent = data.reason || 'N/A';
|
|
1254
|
+
|
|
1255
|
+
// Display explainability
|
|
1256
|
+
const becauseList = document.getElementById('testBecauseList');
|
|
1257
|
+
const failedList = document.getElementById('testFailedList');
|
|
1258
|
+
|
|
1259
|
+
if (data.because && data.because.length > 0) {
|
|
1260
|
+
becauseList.innerHTML = '';
|
|
1261
|
+
data.because.forEach(condition => {
|
|
1262
|
+
const li = document.createElement('li');
|
|
1263
|
+
li.textContent = condition;
|
|
1264
|
+
li.style.color = '#28a745';
|
|
1265
|
+
becauseList.appendChild(li);
|
|
1266
|
+
});
|
|
1267
|
+
document.getElementById('testBecause').style.display = 'block';
|
|
1268
|
+
} else {
|
|
1269
|
+
document.getElementById('testBecause').style.display = 'none';
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
if (data.failed_conditions && data.failed_conditions.length > 0) {
|
|
1273
|
+
failedList.innerHTML = '';
|
|
1274
|
+
data.failed_conditions.forEach(condition => {
|
|
1275
|
+
const li = document.createElement('li');
|
|
1276
|
+
li.textContent = condition;
|
|
1277
|
+
li.style.color = '#dc3545';
|
|
1278
|
+
failedList.appendChild(li);
|
|
1279
|
+
});
|
|
1280
|
+
document.getElementById('testFailedConditions').style.display = 'block';
|
|
1281
|
+
} else {
|
|
1282
|
+
document.getElementById('testFailedConditions').style.display = 'none';
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
document.getElementById('testExplainability').style.display = 'block';
|
|
1286
|
+
} else {
|
|
1287
|
+
document.getElementById('testDecisionValue').textContent = 'No match';
|
|
1288
|
+
document.getElementById('testConfidenceValue').textContent = 'N/A';
|
|
1289
|
+
document.getElementById('testReasonValue').textContent = data.message || 'No rules matched';
|
|
1290
|
+
document.getElementById('testExplainability').style.display = 'none';
|
|
1291
|
+
}
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
alert('Error running test: ' + error.message);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1137
1297
|
async deleteVersion(versionId) {
|
|
1138
1298
|
if (!confirm('Are you sure you want to delete this version? This action cannot be undone.')) {
|
|
1139
1299
|
return;
|
|
@@ -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>DecisionAgent Batch Testing</title>
|
|
7
|
-
<link rel="stylesheet" href="styles.css">
|
|
7
|
+
<link rel="stylesheet" href="/styles.css">
|
|
8
8
|
<style>
|
|
9
9
|
.batch-testing-container {
|
|
10
10
|
max-width: 1400px;
|
|
@@ -180,7 +180,11 @@
|
|
|
180
180
|
<h2>Upload Test Scenarios</h2>
|
|
181
181
|
</div>
|
|
182
182
|
<p>Upload a CSV or Excel file with your test scenarios. Required columns: <code>id</code>, context fields. Optional: <code>expected_decision</code>, <code>expected_confidence</code></p>
|
|
183
|
-
|
|
183
|
+
<p style="margin-top: 10px;">
|
|
184
|
+
<strong>Not sure about the format?</strong> Download a sample file:
|
|
185
|
+
<a href="/sample_batch.csv" download style="color: #007bff; text-decoration: none; margin-left: 5px;">📥 Sample CSV</a>
|
|
186
|
+
</p>
|
|
187
|
+
|
|
184
188
|
<div class="file-upload-area" id="uploadArea">
|
|
185
189
|
<p>📁 Drag and drop your CSV/Excel file here, or click to browse</p>
|
|
186
190
|
<input type="file" id="fileInput" accept=".csv,.xlsx,.xls" style="display: none;">
|
|
@@ -333,6 +337,13 @@
|
|
|
333
337
|
return dirPath || '/';
|
|
334
338
|
}
|
|
335
339
|
|
|
340
|
+
function getAuthHeaders() {
|
|
341
|
+
const token = localStorage.getItem('auth_token');
|
|
342
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
343
|
+
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
344
|
+
return headers;
|
|
345
|
+
}
|
|
346
|
+
|
|
336
347
|
const basePath = getBasePath();
|
|
337
348
|
let currentTestId = null;
|
|
338
349
|
let currentScenarios = null;
|
|
@@ -376,8 +387,13 @@
|
|
|
376
387
|
progressFill.style.width = '0%';
|
|
377
388
|
progressText.textContent = 'Uploading...';
|
|
378
389
|
|
|
390
|
+
const uploadHeaders = {};
|
|
391
|
+
const token = localStorage.getItem('auth_token');
|
|
392
|
+
if (token) uploadHeaders['Authorization'] = `Bearer ${token}`;
|
|
393
|
+
|
|
379
394
|
fetch(`${basePath}api/testing/batch/import`, {
|
|
380
395
|
method: 'POST',
|
|
396
|
+
headers: uploadHeaders,
|
|
381
397
|
body: formData
|
|
382
398
|
})
|
|
383
399
|
.then(response => response.json())
|
|
@@ -393,6 +409,7 @@
|
|
|
393
409
|
uploadStatus.textContent = `Successfully imported ${data.scenarios_count} scenarios. Test ID: ${data.test_id}`;
|
|
394
410
|
uploadProgress.classList.add('hidden');
|
|
395
411
|
document.getElementById('runTestBtn').disabled = false;
|
|
412
|
+
document.getElementById('resumeTestBtn').disabled = false;
|
|
396
413
|
|
|
397
414
|
if (data.errors && data.errors.length > 0) {
|
|
398
415
|
uploadStatus.textContent += `\nWarnings: ${data.errors.join(', ')}`;
|
|
@@ -430,7 +447,7 @@
|
|
|
430
447
|
JSON.parse(rulesJson);
|
|
431
448
|
fetch(`${basePath}api/validate`, {
|
|
432
449
|
method: 'POST',
|
|
433
|
-
headers:
|
|
450
|
+
headers: getAuthHeaders(),
|
|
434
451
|
body: JSON.stringify(JSON.parse(rulesJson))
|
|
435
452
|
})
|
|
436
453
|
.then(response => response.json())
|
|
@@ -483,7 +500,7 @@
|
|
|
483
500
|
|
|
484
501
|
fetch(`${basePath}api/testing/batch/run`, {
|
|
485
502
|
method: 'POST',
|
|
486
|
-
headers:
|
|
503
|
+
headers: getAuthHeaders(),
|
|
487
504
|
body: JSON.stringify({
|
|
488
505
|
test_id: currentTestId,
|
|
489
506
|
rules: JSON.parse(rulesJson),
|
|
@@ -510,9 +527,66 @@
|
|
|
510
527
|
});
|
|
511
528
|
});
|
|
512
529
|
|
|
530
|
+
// Resume test - re-run the previously imported test
|
|
531
|
+
document.getElementById('resumeTestBtn').addEventListener('click', () => {
|
|
532
|
+
if (!currentTestId) {
|
|
533
|
+
alert('No test to resume. Please import test data first.');
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const rulesJson = document.getElementById('rulesJson').value;
|
|
538
|
+
try {
|
|
539
|
+
JSON.parse(rulesJson);
|
|
540
|
+
} catch (e) {
|
|
541
|
+
alert('Invalid rules JSON: ' + e.message);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const runStatus = document.getElementById('runStatus');
|
|
546
|
+
const runProgress = document.getElementById('runProgress');
|
|
547
|
+
const runProgressText = document.getElementById('runProgressText');
|
|
548
|
+
|
|
549
|
+
runStatus.classList.add('hidden');
|
|
550
|
+
runProgress.classList.remove('hidden');
|
|
551
|
+
runProgressText.textContent = 'Resuming...';
|
|
552
|
+
|
|
553
|
+
const options = {
|
|
554
|
+
parallel: document.getElementById('parallelExecution').checked,
|
|
555
|
+
thread_count: parseInt(document.getElementById('threadCount').value) || 4
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
fetch(`${basePath}api/testing/batch/run`, {
|
|
559
|
+
method: 'POST',
|
|
560
|
+
headers: getAuthHeaders(),
|
|
561
|
+
body: JSON.stringify({
|
|
562
|
+
test_id: currentTestId,
|
|
563
|
+
rules: JSON.parse(rulesJson),
|
|
564
|
+
options: options
|
|
565
|
+
})
|
|
566
|
+
})
|
|
567
|
+
.then(response => response.json())
|
|
568
|
+
.then(data => {
|
|
569
|
+
if (data.error) {
|
|
570
|
+
runStatus.className = 'error-message';
|
|
571
|
+
runStatus.textContent = `Error: ${data.error}`;
|
|
572
|
+
runProgress.classList.add('hidden');
|
|
573
|
+
} else {
|
|
574
|
+
runStatus.className = 'success-message';
|
|
575
|
+
runStatus.textContent = 'Test resumed and completed successfully!';
|
|
576
|
+
runProgress.classList.add('hidden');
|
|
577
|
+
loadResults(currentTestId);
|
|
578
|
+
}
|
|
579
|
+
})
|
|
580
|
+
.catch(error => {
|
|
581
|
+
runStatus.className = 'error-message';
|
|
582
|
+
runStatus.textContent = `Error: ${error.message}`;
|
|
583
|
+
runProgress.classList.add('hidden');
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
|
|
513
587
|
// Load results
|
|
514
588
|
function loadResults(testId) {
|
|
515
|
-
fetch(`${basePath}api/testing/batch/${testId}/results
|
|
589
|
+
fetch(`${basePath}api/testing/batch/${testId}/results`, { headers: getAuthHeaders() })
|
|
516
590
|
.then(response => response.json())
|
|
517
591
|
.then(data => {
|
|
518
592
|
if (data.error) {
|
|
@@ -600,7 +674,7 @@
|
|
|
600
674
|
|
|
601
675
|
// Load coverage
|
|
602
676
|
function loadCoverage(testId) {
|
|
603
|
-
fetch(`${basePath}api/testing/batch/${testId}/coverage
|
|
677
|
+
fetch(`${basePath}api/testing/batch/${testId}/coverage`, { headers: getAuthHeaders() })
|
|
604
678
|
.then(response => response.json())
|
|
605
679
|
.then(data => {
|
|
606
680
|
if (data.error) {
|
|
@@ -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>DMN Editor - DecisionAgent</title>
|
|
7
|
-
<link rel="stylesheet" href="dmn-editor.css">
|
|
7
|
+
<link rel="stylesheet" href="/dmn-editor.css">
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div class="container">
|
|
@@ -245,6 +245,6 @@
|
|
|
245
245
|
|
|
246
246
|
<div id="notification" class="notification"></div>
|
|
247
247
|
|
|
248
|
-
<script src="dmn-editor.js"></script>
|
|
248
|
+
<script src="/dmn-editor.js"></script>
|
|
249
249
|
</body>
|
|
250
250
|
</html>
|