kbs 0.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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +3 -0
  3. data/CHANGELOG.md +5 -0
  4. data/COMMITS.md +196 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +481 -0
  7. data/Rakefile +8 -0
  8. data/examples/README.md +531 -0
  9. data/examples/advanced_example.rb +270 -0
  10. data/examples/ai_enhanced_kbs.rb +523 -0
  11. data/examples/blackboard_demo.rb +50 -0
  12. data/examples/car_diagnostic.rb +64 -0
  13. data/examples/concurrent_inference_demo.rb +363 -0
  14. data/examples/csv_trading_system.rb +559 -0
  15. data/examples/iot_demo_using_dsl.rb +83 -0
  16. data/examples/portfolio_rebalancing_system.rb +651 -0
  17. data/examples/redis_trading_demo.rb +177 -0
  18. data/examples/sample_stock_data.csv +46 -0
  19. data/examples/stock_trading_advanced.rb +469 -0
  20. data/examples/stock_trading_system.rb.bak +563 -0
  21. data/examples/timestamped_trading.rb +286 -0
  22. data/examples/trading_demo.rb +334 -0
  23. data/examples/working_demo.rb +176 -0
  24. data/lib/kbs/alpha_memory.rb +37 -0
  25. data/lib/kbs/beta_memory.rb +57 -0
  26. data/lib/kbs/blackboard/audit_log.rb +115 -0
  27. data/lib/kbs/blackboard/engine.rb +83 -0
  28. data/lib/kbs/blackboard/fact.rb +65 -0
  29. data/lib/kbs/blackboard/memory.rb +191 -0
  30. data/lib/kbs/blackboard/message_queue.rb +96 -0
  31. data/lib/kbs/blackboard/persistence/hybrid_store.rb +118 -0
  32. data/lib/kbs/blackboard/persistence/redis_store.rb +218 -0
  33. data/lib/kbs/blackboard/persistence/sqlite_store.rb +242 -0
  34. data/lib/kbs/blackboard/persistence/store.rb +55 -0
  35. data/lib/kbs/blackboard/redis_audit_log.rb +107 -0
  36. data/lib/kbs/blackboard/redis_message_queue.rb +111 -0
  37. data/lib/kbs/blackboard.rb +23 -0
  38. data/lib/kbs/condition.rb +26 -0
  39. data/lib/kbs/dsl/condition_helpers.rb +57 -0
  40. data/lib/kbs/dsl/knowledge_base.rb +86 -0
  41. data/lib/kbs/dsl/pattern_evaluator.rb +69 -0
  42. data/lib/kbs/dsl/rule_builder.rb +115 -0
  43. data/lib/kbs/dsl/variable.rb +35 -0
  44. data/lib/kbs/dsl.rb +18 -0
  45. data/lib/kbs/fact.rb +43 -0
  46. data/lib/kbs/join_node.rb +117 -0
  47. data/lib/kbs/negation_node.rb +88 -0
  48. data/lib/kbs/production_node.rb +28 -0
  49. data/lib/kbs/rete_engine.rb +108 -0
  50. data/lib/kbs/rule.rb +46 -0
  51. data/lib/kbs/token.rb +37 -0
  52. data/lib/kbs/version.rb +5 -0
  53. data/lib/kbs/working_memory.rb +32 -0
  54. data/lib/kbs.rb +20 -0
  55. metadata +164 -0
@@ -0,0 +1,363 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/kbs'
4
+ require_relative '../lib/kbs/dsl'
5
+ require 'thread'
6
+
7
+ # =============================================================================
8
+ # Pattern 1: Auto-Inference Mode (Reactive)
9
+ # Rules fire immediately when facts are added
10
+ # =============================================================================
11
+
12
+ class ReactiveEngine < KBS::ReteEngine
13
+ attr_accessor :auto_inference
14
+
15
+ def initialize(auto_inference: true)
16
+ super()
17
+ @auto_inference = auto_inference
18
+ @inference_mutex = Mutex.new
19
+ end
20
+
21
+ def add_fact(type, attributes = {})
22
+ fact = super(type, attributes)
23
+
24
+ # Automatically run inference if enabled
25
+ if @auto_inference
26
+ @inference_mutex.synchronize do
27
+ run
28
+ end
29
+ end
30
+
31
+ fact
32
+ end
33
+ end
34
+
35
+ # =============================================================================
36
+ # Pattern 2: Background Thread Mode
37
+ # Inference runs continuously in a background thread
38
+ # =============================================================================
39
+
40
+ class BackgroundInferenceEngine < KBS::ReteEngine
41
+ def initialize
42
+ super()
43
+ @running = false
44
+ @inference_thread = nil
45
+ @fact_queue = Queue.new
46
+ @mutex = Mutex.new
47
+ end
48
+
49
+ def start_background_inference(interval: 0.1)
50
+ return if @running
51
+
52
+ @running = true
53
+ @inference_thread = Thread.new do
54
+ while @running
55
+ begin
56
+ # Process any queued facts
57
+ until @fact_queue.empty?
58
+ fact_data = @fact_queue.pop(true) rescue nil
59
+ if fact_data
60
+ @mutex.synchronize do
61
+ super_add_fact(fact_data[:type], fact_data[:attributes])
62
+ end
63
+ end
64
+ end
65
+
66
+ # Run inference
67
+ @mutex.synchronize do
68
+ run
69
+ end
70
+
71
+ sleep interval
72
+ rescue => e
73
+ puts "Background inference error: #{e.message}"
74
+ end
75
+ end
76
+ end
77
+
78
+ puts "āœ“ Background inference started (interval: #{interval}s)"
79
+ end
80
+
81
+ def stop_background_inference
82
+ @running = false
83
+ @inference_thread&.join
84
+ puts "āœ“ Background inference stopped"
85
+ end
86
+
87
+ def add_fact(type, attributes = {})
88
+ @fact_queue.push({type: type, attributes: attributes})
89
+ puts " → Fact queued: #{type}(#{attributes.map{|k,v| "#{k}: #{v}"}.join(', ')})"
90
+ end
91
+
92
+ private
93
+
94
+ def super_add_fact(type, attributes)
95
+ fact = KBS::Fact.new(type, attributes)
96
+ @working_memory.add_fact(fact)
97
+ fact
98
+ end
99
+ end
100
+
101
+ # =============================================================================
102
+ # Pattern 3: Event-Driven with Callbacks
103
+ # Execute callbacks immediately when rules fire
104
+ # =============================================================================
105
+
106
+ class EventDrivenEngine < KBS::ReteEngine
107
+ def initialize
108
+ super()
109
+ @rule_callbacks = {}
110
+ end
111
+
112
+ def on_rule(rule_name, &callback)
113
+ @rule_callbacks[rule_name] = callback
114
+ end
115
+
116
+ def add_fact(type, attributes = {})
117
+ fact = super(type, attributes)
118
+
119
+ # Check each production node and fire matching rules
120
+ @production_nodes.each do |rule_name, node|
121
+ node.tokens.each do |token|
122
+ if token.facts.include?(fact) && @rule_callbacks[rule_name]
123
+ Thread.new do
124
+ @rule_callbacks[rule_name].call(token.facts)
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ fact
131
+ end
132
+ end
133
+
134
+ # =============================================================================
135
+ # DEMO: Pattern 1 - Auto-Inference (Reactive)
136
+ # =============================================================================
137
+
138
+ def demo_reactive_inference
139
+ puts "\n" + "="*80
140
+ puts "PATTERN 1: Auto-Inference Mode (Reactive)"
141
+ puts "="*80
142
+ puts "Facts trigger immediate inference\n\n"
143
+
144
+ engine = ReactiveEngine.new(auto_inference: true)
145
+
146
+ # Define rules
147
+ rule = KBS::Rule.new(
148
+ "temperature_alert",
149
+ conditions: [
150
+ KBS::Condition.new(:sensor, {type: "temperature"}),
151
+ KBS::Condition.new(:reading, {value: ->(v) { v > 100 }})
152
+ ],
153
+ action: lambda do |facts, bindings|
154
+ reading = facts.find { |f| f.type == :reading }
155
+ sensor = facts.find { |f| f.type == :sensor }
156
+ puts " šŸ”„ RULE FIRED: Temperature Alert!"
157
+ puts " Location: #{sensor[:location]}"
158
+ puts " Temperature: #{reading[:value]}°C exceeds threshold!"
159
+ end
160
+ )
161
+
162
+ engine.add_rule(rule)
163
+
164
+ puts "Adding sensor..."
165
+ engine.add_fact(:sensor, type: "temperature", location: "reactor")
166
+ puts " āœ“ Sensor added (1/2 conditions met)"
167
+
168
+ puts "\nAdding high temperature reading (should fire rule immediately)..."
169
+ engine.add_fact(:reading, value: 105, unit: "celsius")
170
+ puts " āœ“ Reading added (2/2 conditions met)"
171
+
172
+ puts "\nAdding another high reading (should fire again)..."
173
+ engine.add_fact(:reading, value: 110, unit: "celsius")
174
+
175
+ puts "\nAdding normal temperature reading (should not fire)..."
176
+ engine.add_fact(:reading, value: 75, unit: "celsius")
177
+ puts " āœ“ Reading added but rule not fired (condition not met)"
178
+ end
179
+
180
+ # =============================================================================
181
+ # DEMO: Pattern 2 - Background Thread
182
+ # =============================================================================
183
+
184
+ def demo_background_inference
185
+ puts "\n" + "="*80
186
+ puts "PATTERN 2: Background Thread Mode"
187
+ puts "="*80
188
+ puts "Inference runs continuously in background\n\n"
189
+
190
+ engine = BackgroundInferenceEngine.new
191
+
192
+ # Define rules
193
+ rule = KBS::Rule.new(
194
+ "stock_momentum",
195
+ conditions: [
196
+ KBS::Condition.new(:stock, {symbol: "AAPL"}),
197
+ KBS::Condition.new(:price, {change: ->(v) { v > 5 }})
198
+ ],
199
+ action: lambda do |facts, bindings|
200
+ price = facts.find { |f| f.type == :price }
201
+ stock = facts.find { |f| f.type == :stock }
202
+ puts " šŸ“ˆ RULE FIRED: Stock Momentum Alert!"
203
+ puts " Stock: #{stock[:symbol]}"
204
+ puts " Change: +#{price[:change]}%"
205
+ end
206
+ )
207
+
208
+ engine.add_rule(rule)
209
+
210
+ # Start background processing
211
+ engine.start_background_inference(interval: 0.5)
212
+
213
+ puts "\nStreaming facts (processed by background thread)...\n"
214
+
215
+ engine.add_fact(:stock, symbol: "AAPL", sector: "Technology")
216
+ sleep 0.7 # Let background thread process
217
+
218
+ engine.add_fact(:price, symbol: "AAPL", change: 6.5, timestamp: Time.now)
219
+ sleep 0.7 # Let background thread process
220
+
221
+ engine.add_fact(:price, symbol: "AAPL", change: 2.1, timestamp: Time.now)
222
+ sleep 0.7 # Let background thread process
223
+
224
+ engine.stop_background_inference
225
+ end
226
+
227
+ # =============================================================================
228
+ # DEMO: Pattern 3 - Event-Driven with Callbacks
229
+ # =============================================================================
230
+
231
+ def demo_event_driven
232
+ puts "\n" + "="*80
233
+ puts "PATTERN 3: Event-Driven with Callbacks"
234
+ puts "="*80
235
+ puts "Rules have individual callback handlers\n\n"
236
+
237
+ engine = EventDrivenEngine.new
238
+
239
+ # Define rule
240
+ rule = KBS::Rule.new(
241
+ "order_fulfillment",
242
+ conditions: [
243
+ KBS::Condition.new(:order, {status: "pending"}),
244
+ KBS::Condition.new(:inventory, {available: ->(v) { v > 0 }})
245
+ ],
246
+ action: lambda do |facts, bindings|
247
+ order = facts.find { |f| f.type == :order }
248
+ inventory = facts.find { |f| f.type == :inventory }
249
+ puts " šŸ“¦ RULE FIRED: Order Fulfillment!"
250
+ puts " Order ID: #{order[:id]}"
251
+ puts " Item: #{order[:item]}"
252
+ puts " Available: #{inventory[:available]}"
253
+ end
254
+ )
255
+
256
+ engine.add_rule(rule)
257
+
258
+ # Register callback
259
+ engine.on_rule("order_fulfillment") do |facts|
260
+ order = facts.find { |f| f.type == :order }
261
+ puts " āœ“ Async callback executed for order #{order[:id]}"
262
+ end
263
+
264
+ puts "Adding facts with event callbacks...\n"
265
+
266
+ engine.add_fact(:order, id: "ORD-001", status: "pending", item: "Widget")
267
+ puts " āœ“ Order added (1/2 conditions met)"
268
+
269
+ engine.add_fact(:inventory, item: "Widget", available: 50)
270
+ puts " āœ“ Inventory added (2/2 conditions met)"
271
+
272
+ # Trigger the event-driven execution
273
+ engine.run
274
+
275
+ sleep 0.5 # Let async callbacks complete
276
+ end
277
+
278
+ # =============================================================================
279
+ # DEMO: Pattern 4 - Queue-Based Processing
280
+ # =============================================================================
281
+
282
+ def demo_queue_based
283
+ puts "\n" + "="*80
284
+ puts "PATTERN 4: Queue-Based Batch Processing"
285
+ puts "="*80
286
+ puts "Facts accumulate and are processed in batches\n\n"
287
+
288
+ engine = KBS::ReteEngine.new
289
+ fact_queue = Queue.new
290
+
291
+ # Define rule
292
+ rule = KBS::Rule.new(
293
+ "batch_processor",
294
+ conditions: [
295
+ KBS::Condition.new(:transaction, {})
296
+ ],
297
+ action: lambda do |facts, bindings|
298
+ tx = facts.find { |f| f.type == :transaction }
299
+ puts " šŸ’³ RULE FIRED: Transaction Processed!"
300
+ puts " ID: #{tx[:id]}"
301
+ puts " Amount: $#{'%.2f' % tx[:amount]}"
302
+ end
303
+ )
304
+
305
+ engine.add_rule(rule)
306
+
307
+ # Worker thread processes queue
308
+ worker = Thread.new do
309
+ while true
310
+ batch = []
311
+ 5.times { batch << fact_queue.pop(true) rescue nil }
312
+ batch.compact!
313
+
314
+ break if batch.empty?
315
+
316
+ puts "\n→ Processing batch of #{batch.size} facts..."
317
+ batch.each do |fact_data|
318
+ engine.add_fact(fact_data[:type], fact_data[:attributes])
319
+ end
320
+
321
+ puts "→ Running inference on batch..."
322
+ engine.run
323
+
324
+ sleep 0.5
325
+ end
326
+ end
327
+
328
+ # Producer adds facts to queue
329
+ puts "Queuing transactions...\n"
330
+
331
+ fact_queue << {type: :transaction, attributes: {id: "TX-001", amount: 100.00}}
332
+ fact_queue << {type: :transaction, attributes: {id: "TX-002", amount: 250.00}}
333
+ fact_queue << {type: :transaction, attributes: {id: "TX-003", amount: 75.50}}
334
+ fact_queue << {type: :transaction, attributes: {id: "TX-004", amount: 500.00}}
335
+ fact_queue << {type: :transaction, attributes: {id: "TX-005", amount: 125.75}}
336
+
337
+ puts " āœ“ 5 transactions queued"
338
+
339
+ worker.join(3)
340
+ end
341
+
342
+ # =============================================================================
343
+ # Run all demos
344
+ # =============================================================================
345
+
346
+ if __FILE__ == $0
347
+ puts "\nšŸš€ CONCURRENT INFERENCE PATTERNS FOR RETE II\n"
348
+
349
+ demo_reactive_inference
350
+ sleep 1
351
+
352
+ demo_background_inference
353
+ sleep 1
354
+
355
+ demo_event_driven
356
+ sleep 1
357
+
358
+ demo_queue_based
359
+
360
+ puts "\n" + "="*80
361
+ puts "All demos completed!"
362
+ puts "="*80
363
+ end