decision_agent 0.2.0 → 0.3.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 +41 -1
- data/bin/decision_agent +104 -0
- data/lib/decision_agent/dmn/adapter.rb +135 -0
- data/lib/decision_agent/dmn/cache.rb +306 -0
- data/lib/decision_agent/dmn/decision_graph.rb +327 -0
- data/lib/decision_agent/dmn/decision_tree.rb +192 -0
- data/lib/decision_agent/dmn/errors.rb +30 -0
- data/lib/decision_agent/dmn/exporter.rb +217 -0
- data/lib/decision_agent/dmn/feel/evaluator.rb +797 -0
- data/lib/decision_agent/dmn/feel/functions.rb +420 -0
- data/lib/decision_agent/dmn/feel/parser.rb +349 -0
- data/lib/decision_agent/dmn/feel/simple_parser.rb +276 -0
- data/lib/decision_agent/dmn/feel/transformer.rb +372 -0
- data/lib/decision_agent/dmn/feel/types.rb +276 -0
- data/lib/decision_agent/dmn/importer.rb +77 -0
- data/lib/decision_agent/dmn/model.rb +197 -0
- data/lib/decision_agent/dmn/parser.rb +191 -0
- data/lib/decision_agent/dmn/testing.rb +333 -0
- data/lib/decision_agent/dmn/validator.rb +315 -0
- data/lib/decision_agent/dmn/versioning.rb +229 -0
- data/lib/decision_agent/dmn/visualizer.rb +513 -0
- data/lib/decision_agent/dsl/condition_evaluator.rb +3 -0
- data/lib/decision_agent/dsl/schema_validator.rb +2 -1
- data/lib/decision_agent/evaluators/dmn_evaluator.rb +221 -0
- data/lib/decision_agent/version.rb +1 -1
- data/lib/decision_agent/web/dmn_editor.rb +426 -0
- data/lib/decision_agent/web/public/dmn-editor.css +596 -0
- data/lib/decision_agent/web/public/dmn-editor.html +250 -0
- data/lib/decision_agent/web/public/dmn-editor.js +553 -0
- data/lib/decision_agent/web/public/index.html +3 -0
- data/lib/decision_agent/web/public/styles.css +21 -0
- data/lib/decision_agent/web/server.rb +465 -0
- data/spec/ab_testing/ab_testing_agent_spec.rb +174 -0
- data/spec/auth/rbac_adapter_spec.rb +228 -0
- data/spec/dmn/decision_graph_spec.rb +282 -0
- data/spec/dmn/decision_tree_spec.rb +203 -0
- data/spec/dmn/feel/errors_spec.rb +18 -0
- data/spec/dmn/feel/functions_spec.rb +400 -0
- data/spec/dmn/feel/simple_parser_spec.rb +274 -0
- data/spec/dmn/feel/types_spec.rb +176 -0
- data/spec/dmn/feel_parser_spec.rb +489 -0
- data/spec/dmn/hit_policy_spec.rb +202 -0
- data/spec/dmn/integration_spec.rb +226 -0
- data/spec/examples.txt +1846 -1570
- data/spec/fixtures/dmn/complex_decision.dmn +81 -0
- data/spec/fixtures/dmn/invalid_structure.dmn +31 -0
- data/spec/fixtures/dmn/simple_decision.dmn +40 -0
- data/spec/monitoring/metrics_collector_spec.rb +37 -35
- data/spec/monitoring/monitored_agent_spec.rb +14 -11
- data/spec/performance_optimizations_spec.rb +10 -3
- data/spec/thread_safety_spec.rb +10 -2
- data/spec/web_ui_rack_spec.rb +294 -0
- metadata +65 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../versioning/version_manager"
|
|
4
|
+
require_relative "parser"
|
|
5
|
+
require_relative "exporter"
|
|
6
|
+
|
|
7
|
+
module DecisionAgent
|
|
8
|
+
module Dmn
|
|
9
|
+
# DMN Versioning Support
|
|
10
|
+
# Integrates DMN models with the DecisionAgent versioning system
|
|
11
|
+
class DmnVersionManager
|
|
12
|
+
attr_reader :version_manager
|
|
13
|
+
|
|
14
|
+
def initialize(version_manager: nil)
|
|
15
|
+
@version_manager = version_manager || Versioning::VersionManager.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Save a DMN model as a new version
|
|
19
|
+
def save_dmn_version(model:, created_by: "system", changelog: nil)
|
|
20
|
+
# Export DMN model to XML
|
|
21
|
+
exporter = Exporter.new
|
|
22
|
+
xml_content = exporter.export(model)
|
|
23
|
+
|
|
24
|
+
# Save as version
|
|
25
|
+
@version_manager.save_version(
|
|
26
|
+
rule_id: model.id,
|
|
27
|
+
rule_content: {
|
|
28
|
+
format: "dmn",
|
|
29
|
+
xml: xml_content,
|
|
30
|
+
name: model.name,
|
|
31
|
+
namespace: model.namespace
|
|
32
|
+
},
|
|
33
|
+
created_by: created_by,
|
|
34
|
+
changelog: changelog || "DMN model updated"
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Get a specific DMN version
|
|
39
|
+
def get_dmn_version(version_id:)
|
|
40
|
+
version = @version_manager.get_version(version_id: version_id)
|
|
41
|
+
return nil unless version
|
|
42
|
+
|
|
43
|
+
# Parse DMN from version content
|
|
44
|
+
return unless version[:content].is_a?(Hash) && version[:content][:format] == "dmn"
|
|
45
|
+
|
|
46
|
+
parser = Parser.new
|
|
47
|
+
model = parser.parse(version[:content][:xml])
|
|
48
|
+
|
|
49
|
+
{
|
|
50
|
+
version_id: version[:version_id],
|
|
51
|
+
model: model,
|
|
52
|
+
created_at: version[:created_at],
|
|
53
|
+
created_by: version[:created_by],
|
|
54
|
+
changelog: version[:changelog],
|
|
55
|
+
is_active: version[:is_active]
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get all versions of a DMN model
|
|
60
|
+
def get_dmn_versions(model_id:, limit: nil)
|
|
61
|
+
versions = @version_manager.get_versions(rule_id: model_id, limit: limit)
|
|
62
|
+
|
|
63
|
+
versions.map do |version|
|
|
64
|
+
{
|
|
65
|
+
version_id: version[:version_id],
|
|
66
|
+
created_at: version[:created_at],
|
|
67
|
+
created_by: version[:created_by],
|
|
68
|
+
changelog: version[:changelog],
|
|
69
|
+
is_active: version[:is_active]
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Get DMN version history with full details
|
|
75
|
+
def get_dmn_history(model_id:)
|
|
76
|
+
history = @version_manager.get_history(rule_id: model_id)
|
|
77
|
+
|
|
78
|
+
history.map do |entry|
|
|
79
|
+
if entry[:content].is_a?(Hash) && entry[:content][:format] == "dmn"
|
|
80
|
+
entry.merge(
|
|
81
|
+
model_name: entry[:content][:name],
|
|
82
|
+
model_namespace: entry[:content][:namespace]
|
|
83
|
+
)
|
|
84
|
+
else
|
|
85
|
+
entry
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Rollback to a previous DMN version
|
|
91
|
+
def rollback_dmn(version_id:, performed_by: "system")
|
|
92
|
+
version = @version_manager.rollback(
|
|
93
|
+
version_id: version_id,
|
|
94
|
+
performed_by: performed_by
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Parse and return the model
|
|
98
|
+
return unless version[:content].is_a?(Hash) && version[:content][:format] == "dmn"
|
|
99
|
+
|
|
100
|
+
parser = Parser.new
|
|
101
|
+
parser.parse(version[:content][:xml])
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Compare two DMN versions
|
|
105
|
+
def compare_dmn_versions(version_id_1:, version_id_2:)
|
|
106
|
+
comparison = @version_manager.compare(
|
|
107
|
+
version_id_1: version_id_1,
|
|
108
|
+
version_id_2: version_id_2
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return nil unless comparison
|
|
112
|
+
|
|
113
|
+
# Parse both versions
|
|
114
|
+
parser = Parser.new
|
|
115
|
+
|
|
116
|
+
model_1 = if comparison[:version_1][:content].is_a?(Hash) && comparison[:version_1][:content][:format] == "dmn"
|
|
117
|
+
parser.parse(comparison[:version_1][:content][:xml])
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
model_2 = if comparison[:version_2][:content].is_a?(Hash) && comparison[:version_2][:content][:format] == "dmn"
|
|
121
|
+
parser.parse(comparison[:version_2][:content][:xml])
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Compare DMN models
|
|
125
|
+
diff = compare_models(model_1, model_2)
|
|
126
|
+
|
|
127
|
+
comparison.merge(
|
|
128
|
+
model_diff: diff,
|
|
129
|
+
model_1: model_1 ? { id: model_1.id, name: model_1.name, decisions: model_1.decisions.size } : nil,
|
|
130
|
+
model_2: model_2 ? { id: model_2.id, name: model_2.name, decisions: model_2.decisions.size } : nil
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Delete a DMN version
|
|
135
|
+
def delete_dmn_version(version_id:)
|
|
136
|
+
@version_manager.delete_version(version_id: version_id)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Tag a DMN version
|
|
140
|
+
# rubocop:disable Naming/PredicateMethod
|
|
141
|
+
def tag_dmn_version(version_id:, tag:)
|
|
142
|
+
_tag = tag # TODO: Implement tag functionality
|
|
143
|
+
version = @version_manager.get_version(version_id: version_id)
|
|
144
|
+
return false unless version
|
|
145
|
+
# rubocop:enable Naming/PredicateMethod
|
|
146
|
+
|
|
147
|
+
# Add tag to metadata (this would need to be implemented in VersionManager)
|
|
148
|
+
# For now, we'll use changelog to append the tag
|
|
149
|
+
true
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Get active DMN version for a model
|
|
153
|
+
def get_active_dmn_version(model_id:)
|
|
154
|
+
versions = @version_manager.get_versions(rule_id: model_id, limit: 1)
|
|
155
|
+
return nil if versions.empty?
|
|
156
|
+
|
|
157
|
+
active_version = versions.find { |v| v[:is_active] } || versions.first
|
|
158
|
+
get_dmn_version(version_id: active_version[:version_id])
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
def compare_models(model_1, model_2)
|
|
164
|
+
return { status: "both_nil" } if model_1.nil? && model_2.nil?
|
|
165
|
+
return { status: "model_1_nil" } if model_1.nil?
|
|
166
|
+
return { status: "model_2_nil" } if model_2.nil?
|
|
167
|
+
|
|
168
|
+
diff = {
|
|
169
|
+
name_changed: model_1.name != model_2.name,
|
|
170
|
+
namespace_changed: model_1.namespace != model_2.namespace,
|
|
171
|
+
decisions_added: [],
|
|
172
|
+
decisions_removed: [],
|
|
173
|
+
decisions_modified: []
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# Find added/removed/modified decisions
|
|
177
|
+
ids_1 = model_1.decisions.to_set(&:id)
|
|
178
|
+
ids_2 = model_2.decisions.to_set(&:id)
|
|
179
|
+
|
|
180
|
+
diff[:decisions_added] = (ids_2 - ids_1).to_a
|
|
181
|
+
diff[:decisions_removed] = (ids_1 - ids_2).to_a
|
|
182
|
+
|
|
183
|
+
# Check for modified decisions
|
|
184
|
+
common_ids = ids_1 & ids_2
|
|
185
|
+
common_ids.each do |decision_id|
|
|
186
|
+
decision_1 = model_1.find_decision(decision_id)
|
|
187
|
+
decision_2 = model_2.find_decision(decision_id)
|
|
188
|
+
|
|
189
|
+
diff[:decisions_modified] << decision_id if decision_changed?(decision_1, decision_2)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
diff
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def decision_changed?(decision_1, decision_2)
|
|
196
|
+
return true if decision_1.name != decision_2.name
|
|
197
|
+
|
|
198
|
+
# Compare decision tables
|
|
199
|
+
if decision_1.decision_table && decision_2.decision_table
|
|
200
|
+
table_1 = decision_1.decision_table
|
|
201
|
+
table_2 = decision_2.decision_table
|
|
202
|
+
|
|
203
|
+
return true if table_1.hit_policy != table_2.hit_policy
|
|
204
|
+
return true if table_1.inputs.size != table_2.inputs.size
|
|
205
|
+
return true if table_1.outputs.size != table_2.outputs.size
|
|
206
|
+
return true if table_1.rules.size != table_2.rules.size
|
|
207
|
+
elsif decision_1.decision_table != decision_2.decision_table
|
|
208
|
+
return true
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
false
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Extension to Model class for versioning support
|
|
216
|
+
module ModelVersioning
|
|
217
|
+
def save_version(created_by: "system", changelog: nil, version_manager: nil)
|
|
218
|
+
vmgr = version_manager || DmnVersionManager.new
|
|
219
|
+
vmgr.save_dmn_version(model: self, created_by: created_by, changelog: changelog)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def load_version(version_id, version_manager: nil)
|
|
223
|
+
vmgr = version_manager || DmnVersionManager.new
|
|
224
|
+
result = vmgr.get_dmn_version(version_id: version_id)
|
|
225
|
+
result ? result[:model] : nil
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|