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