kbs 0.0.1 → 0.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 (86) 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 +235 -334
  5. data/docs/DOCUMENTATION_STATUS.md +158 -0
  6. data/docs/advanced/custom-persistence.md +775 -0
  7. data/docs/advanced/debugging.md +726 -0
  8. data/docs/advanced/index.md +8 -0
  9. data/docs/advanced/performance.md +832 -0
  10. data/docs/advanced/testing.md +691 -0
  11. data/docs/api/blackboard.md +1157 -0
  12. data/docs/api/engine.md +978 -0
  13. data/docs/api/facts.md +1212 -0
  14. data/docs/api/index.md +12 -0
  15. data/docs/api/rules.md +1034 -0
  16. data/docs/architecture/blackboard.md +553 -0
  17. data/docs/architecture/index.md +277 -0
  18. data/docs/architecture/network-structure.md +343 -0
  19. data/docs/architecture/rete-algorithm.md +737 -0
  20. data/docs/assets/css/custom.css +83 -0
  21. data/docs/assets/images/blackboard-architecture.svg +136 -0
  22. data/docs/assets/images/compiled-network.svg +101 -0
  23. data/docs/assets/images/fact-assertion-flow.svg +117 -0
  24. data/docs/assets/images/kbs.jpg +0 -0
  25. data/docs/assets/images/pattern-matching-trace.svg +136 -0
  26. data/docs/assets/images/rete-network-layers.svg +96 -0
  27. data/docs/assets/images/system-layers.svg +69 -0
  28. data/docs/assets/images/trading-signal-network.svg +139 -0
  29. data/docs/assets/js/mathjax.js +17 -0
  30. data/docs/examples/expert-systems.md +1031 -0
  31. data/docs/examples/index.md +9 -0
  32. data/docs/examples/multi-agent.md +1335 -0
  33. data/docs/examples/stock-trading.md +488 -0
  34. data/docs/guides/blackboard-memory.md +558 -0
  35. data/docs/guides/dsl.md +1321 -0
  36. data/docs/guides/facts.md +652 -0
  37. data/docs/guides/getting-started.md +383 -0
  38. data/docs/guides/index.md +23 -0
  39. data/docs/guides/negation.md +529 -0
  40. data/docs/guides/pattern-matching.md +561 -0
  41. data/docs/guides/persistence.md +451 -0
  42. data/docs/guides/variable-binding.md +491 -0
  43. data/docs/guides/writing-rules.md +755 -0
  44. data/docs/index.md +157 -0
  45. data/docs/installation.md +156 -0
  46. data/docs/quick-start.md +228 -0
  47. data/examples/README.md +2 -2
  48. data/examples/advanced_example.rb +2 -2
  49. data/examples/advanced_example_dsl.rb +224 -0
  50. data/examples/ai_enhanced_kbs.rb +1 -1
  51. data/examples/ai_enhanced_kbs_dsl.rb +538 -0
  52. data/examples/blackboard_demo_dsl.rb +50 -0
  53. data/examples/car_diagnostic.rb +1 -1
  54. data/examples/car_diagnostic_dsl.rb +54 -0
  55. data/examples/concurrent_inference_demo.rb +5 -5
  56. data/examples/concurrent_inference_demo_dsl.rb +363 -0
  57. data/examples/csv_trading_system.rb +1 -1
  58. data/examples/csv_trading_system_dsl.rb +525 -0
  59. data/examples/knowledge_base.db +0 -0
  60. data/examples/portfolio_rebalancing_system.rb +2 -2
  61. data/examples/portfolio_rebalancing_system_dsl.rb +613 -0
  62. data/examples/redis_trading_demo_dsl.rb +177 -0
  63. data/examples/run_all.rb +50 -0
  64. data/examples/run_all_dsl.rb +49 -0
  65. data/examples/stock_trading_advanced.rb +1 -1
  66. data/examples/stock_trading_advanced_dsl.rb +404 -0
  67. data/examples/temp.txt +7693 -0
  68. data/examples/temp_dsl.txt +8447 -0
  69. data/examples/timestamped_trading.rb +1 -1
  70. data/examples/timestamped_trading_dsl.rb +258 -0
  71. data/examples/trading_demo.rb +1 -1
  72. data/examples/trading_demo_dsl.rb +322 -0
  73. data/examples/working_demo.rb +1 -1
  74. data/examples/working_demo_dsl.rb +160 -0
  75. data/lib/kbs/blackboard/engine.rb +3 -3
  76. data/lib/kbs/blackboard/fact.rb +1 -1
  77. data/lib/kbs/condition.rb +1 -1
  78. data/lib/kbs/dsl/knowledge_base.rb +1 -1
  79. data/lib/kbs/dsl/variable.rb +1 -1
  80. data/lib/kbs/{rete_engine.rb → engine.rb} +1 -1
  81. data/lib/kbs/fact.rb +1 -1
  82. data/lib/kbs/version.rb +1 -1
  83. data/lib/kbs.rb +2 -2
  84. data/mkdocs.yml +181 -0
  85. metadata +66 -6
  86. data/examples/stock_trading_system.rb.bak +0 -563
@@ -0,0 +1,383 @@
1
+ # Getting Started
2
+
3
+ Build your first knowledge-based system in 10 minutes. This hands-on tutorial teaches the fundamentals by creating a temperature monitoring system that alerts when sensors exceed thresholds.
4
+
5
+ ## What You'll Learn
6
+
7
+ - Creating rules and facts
8
+ - Variable binding across conditions
9
+ - Using negation to prevent duplicate alerts
10
+ - Persisting facts with blackboard memory
11
+ - Controlling rule execution with priorities
12
+
13
+ ## Installation
14
+
15
+ Add KBS to your Gemfile:
16
+
17
+ ```ruby
18
+ gem 'kbs'
19
+ ```
20
+
21
+ Or install directly:
22
+
23
+ ```bash
24
+ gem install kbs
25
+ ```
26
+
27
+ ## Your First Rule
28
+
29
+ Let's create a simple rule that fires when temperature exceeds a threshold.
30
+
31
+ ### Step 1: Create the Engine
32
+
33
+ ```ruby
34
+ require 'kbs'
35
+
36
+ # Create the inference engine
37
+ engine = KBS::Engine.new
38
+ ```
39
+
40
+ The engine manages rules, facts, and executes the pattern matching algorithm.
41
+
42
+ ### Step 2: Define a Rule
43
+
44
+ ```ruby
45
+ # Define a rule for high temperature alerts
46
+ high_temp_rule = KBS::Rule.new("high_temperature_alert") do |r|
47
+ r.conditions = [
48
+ KBS::Condition.new(:sensor, { id: :sensor_id?, temp: :temp? }),
49
+ KBS::Condition.new(:threshold, { id: :sensor_id?, max: :max? })
50
+ ]
51
+
52
+ r.action = lambda do |facts, bindings|
53
+ if bindings[:temp?] > bindings[:max?]
54
+ puts "🚨 ALERT: Sensor #{bindings[:sensor_id?]} at #{bindings[:temp?]}°C"
55
+ end
56
+ end
57
+ end
58
+
59
+ engine.add_rule(high_temp_rule)
60
+ ```
61
+
62
+ **What this rule does:**
63
+
64
+ - **Condition 1**: Match any `:sensor` fact, binding its `id` to `:sensor_id?` and `temp` to `:temp?`
65
+ - **Condition 2**: Match a `:threshold` fact with the same `id`, binding `max` to `:max?`
66
+ - **Action**: When both conditions match, compare temperature against threshold
67
+
68
+ **Variable binding** (`:sensor_id?`) ensures we only compare sensors with their own thresholds.
69
+
70
+ ### Step 3: Add Facts
71
+
72
+ ```ruby
73
+ # Add sensor reading
74
+ engine.add_fact(:sensor, id: "bedroom", temp: 28)
75
+
76
+ # Add threshold
77
+ engine.add_fact(:threshold, id: "bedroom", max: 25)
78
+ ```
79
+
80
+ Facts are observations about the world. The engine automatically matches them against rule conditions.
81
+
82
+ ### Step 4: Run the Engine
83
+
84
+ ```ruby
85
+ engine.run
86
+ ```
87
+
88
+ **Output:**
89
+ ```
90
+ 🚨 ALERT: Sensor bedroom at 28°C
91
+ ```
92
+
93
+ The rule fired because the bedroom temperature (28°C) exceeds its threshold (25°C).
94
+
95
+ ## Understanding Variable Binding
96
+
97
+ Variable binding connects facts across conditions. Here's how it works:
98
+
99
+ ```ruby
100
+ r.conditions = [
101
+ KBS::Condition.new(:sensor, { id: :sensor_id?, temp: :temp? }),
102
+ KBS::Condition.new(:threshold, { id: :sensor_id?, max: :max? })
103
+ ]
104
+ ```
105
+
106
+ **Binding Process:**
107
+
108
+ 1. Engine finds a `:sensor` fact: `{ id: "bedroom", temp: 28 }`
109
+ 2. Binds `:sensor_id?` → `"bedroom"`, `:temp?` → `28`
110
+ 3. Searches for `:threshold` fact where `id` also equals `"bedroom"`
111
+ 4. Finds `{ id: "bedroom", max: 25 }`
112
+ 5. Binds `:max?` → `25`
113
+ 6. Both conditions satisfied → rule fires with bindings: `{ :sensor_id? => "bedroom", :temp? => 28, :max? => 25 }`
114
+
115
+ Without variable binding, the rule would incorrectly match bedroom sensors with kitchen thresholds.
116
+
117
+ ## Preventing Duplicate Alerts with Negation
118
+
119
+ Let's prevent the same alert from firing repeatedly:
120
+
121
+ ```ruby
122
+ smart_alert_rule = KBS::Rule.new("smart_temperature_alert") do |r|
123
+ r.conditions = [
124
+ KBS::Condition.new(:sensor, { id: :sensor_id?, temp: :temp? }),
125
+ KBS::Condition.new(:threshold, { id: :sensor_id?, max: :max? }),
126
+ # Only fire if no alert already exists for this sensor
127
+ KBS::Condition.new(:alert, { sensor_id: :sensor_id? }, negated: true)
128
+ ]
129
+
130
+ r.action = lambda do |facts, bindings|
131
+ if bindings[:temp?] > bindings[:max?]
132
+ puts "🚨 ALERT: Sensor #{bindings[:sensor_id?]} at #{bindings[:temp?]}°C"
133
+ # Record that we sent this alert
134
+ engine.add_fact(:alert, sensor_id: bindings[:sensor_id?])
135
+ end
136
+ end
137
+ end
138
+ ```
139
+
140
+ **Negated condition** (`negated: true`): Rule fires only when NO `:alert` fact exists for this sensor.
141
+
142
+ **Flow:**
143
+
144
+ 1. First execution: No `:alert` fact → rule fires, creates `:alert` fact
145
+ 2. Second execution: `:alert` fact exists → rule doesn't fire (negation blocks it)
146
+
147
+ ## Persisting Facts with Blackboard Memory
148
+
149
+ So far, facts disappear when your program exits. Use blackboard memory for persistence:
150
+
151
+ ```ruby
152
+ require 'kbs'
153
+
154
+ # Create engine with SQLite persistence
155
+ engine = KBS::Blackboard::Engine.new(db_path: 'sensors.db')
156
+
157
+ # Add rules (same as before)
158
+ engine.add_rule(smart_alert_rule)
159
+
160
+ # Add facts - these are saved to database
161
+ engine.add_fact(:sensor, id: "bedroom", temp: 28)
162
+ engine.add_fact(:threshold, id: "bedroom", max: 25)
163
+
164
+ engine.run
165
+
166
+ # Facts survive program restart
167
+ engine.close
168
+ ```
169
+
170
+ **Next time you run:**
171
+
172
+ ```ruby
173
+ engine = KBS::Blackboard::Engine.new(db_path: 'sensors.db')
174
+ # Facts automatically loaded from database
175
+ puts engine.facts.size # => 2 (sensor + threshold)
176
+ ```
177
+
178
+ Blackboard provides:
179
+ - **Persistence**: Facts saved to SQLite/Redis
180
+ - **Audit Trail**: Complete history of changes
181
+ - **Transactions**: ACID guarantees for multi-fact updates
182
+
183
+ Learn more: [Blackboard Memory Guide](blackboard-memory.md)
184
+
185
+ ## Controlling Execution with Priorities
186
+
187
+ When multiple rules match, control firing order with priorities:
188
+
189
+ ```ruby
190
+ critical_rule = KBS::Rule.new("critical_alert", priority: 100) do |r|
191
+ r.conditions = [
192
+ KBS::Condition.new(:sensor, { temp: :temp? })
193
+ ]
194
+
195
+ r.action = lambda do |facts, bindings|
196
+ if bindings[:temp?] > 50
197
+ puts "🔥 CRITICAL: Immediate shutdown required!"
198
+ exit(1)
199
+ end
200
+ end
201
+ end
202
+
203
+ normal_rule = KBS::Rule.new("normal_alert", priority: 10) do |r|
204
+ # ... (less urgent alerts)
205
+ end
206
+
207
+ engine.add_rule(critical_rule)
208
+ engine.add_rule(normal_rule)
209
+ ```
210
+
211
+ **Priority:** Higher numbers fire first. Default is `0`.
212
+
213
+ **Execution order:**
214
+ 1. `critical_alert` (priority 100) - checks for emergency shutdown
215
+ 2. `normal_alert` (priority 10) - handles routine alerts
216
+
217
+ ## Complete Working Example
218
+
219
+ Here's a complete temperature monitoring system:
220
+
221
+ ```ruby
222
+ require 'kbs'
223
+
224
+ class TemperatureMonitor
225
+ def initialize
226
+ @engine = KBS::Blackboard::Engine.new(db_path: 'sensors.db')
227
+ setup_rules
228
+ end
229
+
230
+ def setup_rules
231
+ # Rule 1: Send alert when temp exceeds threshold
232
+ alert_rule = KBS::Rule.new("temperature_alert", priority: 50) do |r|
233
+ r.conditions = [
234
+ KBS::Condition.new(:sensor, { id: :id?, temp: :temp? }),
235
+ KBS::Condition.new(:threshold, { id: :id?, max: :max? }),
236
+ KBS::Condition.new(:alert, { sensor_id: :id? }, negated: true)
237
+ ]
238
+
239
+ r.action = lambda do |facts, bindings|
240
+ if bindings[:temp?] > bindings[:max?]
241
+ send_alert(bindings[:id?], bindings[:temp?], bindings[:max?])
242
+ @engine.add_fact(:alert, sensor_id: bindings[:id?])
243
+ end
244
+ end
245
+ end
246
+
247
+ # Rule 2: Clear alert when temp drops below threshold
248
+ clear_rule = KBS::Rule.new("clear_alert", priority: 40) do |r|
249
+ r.conditions = [
250
+ KBS::Condition.new(:sensor, { id: :id?, temp: :temp? }),
251
+ KBS::Condition.new(:threshold, { id: :id?, max: :max? }),
252
+ KBS::Condition.new(:alert, { sensor_id: :id? })
253
+ ]
254
+
255
+ r.action = lambda do |facts, bindings|
256
+ if bindings[:temp?] <= bindings[:max?]
257
+ clear_alert(bindings[:id?])
258
+ # Remove the alert fact
259
+ alert_fact = facts.find { |f| f.type == :alert && f[:sensor_id] == bindings[:id?] }
260
+ @engine.remove_fact(alert_fact) if alert_fact
261
+ end
262
+ end
263
+ end
264
+
265
+ # Rule 3: Emergency shutdown for extreme temps
266
+ emergency_rule = KBS::Rule.new("emergency_shutdown", priority: 100) do |r|
267
+ r.conditions = [
268
+ KBS::Condition.new(:sensor, { temp: :temp? })
269
+ ]
270
+
271
+ r.action = lambda do |facts, bindings|
272
+ if bindings[:temp?] > 60
273
+ emergency_shutdown(bindings[:temp?])
274
+ end
275
+ end
276
+ end
277
+
278
+ @engine.add_rule(alert_rule)
279
+ @engine.add_rule(clear_rule)
280
+ @engine.add_rule(emergency_rule)
281
+ end
282
+
283
+ def add_sensor(id, max_temp)
284
+ @engine.add_fact(:threshold, id: id, max: max_temp)
285
+ end
286
+
287
+ def update_reading(id, temp)
288
+ # Remove old reading
289
+ old = @engine.facts.find { |f| f.type == :sensor && f[:id] == id }
290
+ @engine.remove_fact(old) if old
291
+
292
+ # Add new reading
293
+ @engine.add_fact(:sensor, id: id, temp: temp)
294
+ @engine.run
295
+ end
296
+
297
+ def send_alert(sensor_id, temp, threshold)
298
+ puts "🚨 ALERT: #{sensor_id} at #{temp}°C (threshold: #{threshold}°C)"
299
+ end
300
+
301
+ def clear_alert(sensor_id)
302
+ puts "✅ CLEAR: #{sensor_id} back to normal"
303
+ end
304
+
305
+ def emergency_shutdown(temp)
306
+ puts "🔥 EMERGENCY SHUTDOWN: Temperature #{temp}°C!"
307
+ exit(1)
308
+ end
309
+
310
+ def close
311
+ @engine.close
312
+ end
313
+ end
314
+
315
+ # Usage
316
+ monitor = TemperatureMonitor.new
317
+
318
+ # Register sensors with thresholds
319
+ monitor.add_sensor("bedroom", 25)
320
+ monitor.add_sensor("server_room", 30)
321
+
322
+ # Simulate sensor readings
323
+ monitor.update_reading("bedroom", 28) # => 🚨 ALERT
324
+ monitor.update_reading("server_room", 45) # => 🚨 ALERT
325
+ monitor.update_reading("bedroom", 22) # => ✅ CLEAR
326
+ monitor.update_reading("server_room", 65) # => 🔥 EMERGENCY SHUTDOWN
327
+
328
+ monitor.close
329
+ ```
330
+
331
+ ## Key Concepts Learned
332
+
333
+ ✅ **Rules** - Define patterns and actions
334
+ ✅ **Facts** - Observations stored in working memory
335
+ ✅ **Conditions** - Patterns that match facts
336
+ ✅ **Variable Binding** - Connect facts across conditions using `:variable?`
337
+ ✅ **Negation** - Match when patterns are absent
338
+ ✅ **Priorities** - Control rule firing order
339
+ ✅ **Persistence** - Save facts to database with blackboard memory
340
+
341
+ ## Troubleshooting
342
+
343
+ ### Rule Not Firing
344
+
345
+ **Problem**: Added facts but rule doesn't fire
346
+
347
+ **Checklist**:
348
+ 1. Did you call `engine.run`?
349
+ 2. Do variable bindings match? (`:sensor_id?` must appear in both conditions)
350
+ 3. Check negated conditions - is there a blocking fact?
351
+ 4. Verify fact types match condition types exactly (`:sensor` vs `:sensors`)
352
+
353
+ ### Performance Issues
354
+
355
+ **Problem**: Slow when adding many facts
356
+
357
+ **Solutions**:
358
+ - Order conditions from most selective to least selective
359
+ - Use Redis store for high-frequency updates: `KBS::Blackboard::Engine.new(store: KBS::Blackboard::Persistence::RedisStore.new)`
360
+ - Minimize negated conditions
361
+
362
+ ### Facts Not Persisting
363
+
364
+ **Problem**: Facts disappear after restart
365
+
366
+ **Check**:
367
+ - Using `KBS::Blackboard::Engine` (not `KBS::Engine`)?
368
+ - Provided `db_path` parameter?
369
+ - Called `engine.close` before exit?
370
+
371
+ ## Next Steps
372
+
373
+ Now that you understand the basics, explore:
374
+
375
+ - **[Writing Rules](writing-rules.md)** - Advanced rule patterns and techniques
376
+ - **[Pattern Matching](pattern-matching.md)** - Deep dive into condition syntax
377
+ - **[Blackboard Memory](blackboard-memory.md)** - Multi-agent collaboration
378
+ - **[Stock Trading Example](../examples/stock-trading.md)** - Real-world application
379
+ - **[API Reference](../api/engine.md)** - Complete method documentation
380
+
381
+ ---
382
+
383
+ **Questions?** Open an issue at [github.com/madbomber/kbs](https://github.com/madbomber/kbs)
@@ -0,0 +1,23 @@
1
+ # Guides
2
+
3
+ Practical guides for using KBS effectively.
4
+
5
+ ## Getting Started
6
+ - **[Getting Started](getting-started.md)** - Your first rule-based system
7
+ - **[Writing Rules](writing-rules.md)** - Rule authoring best practices
8
+ - **[DSL Reference](dsl.md)** - Complete syntax guide
9
+
10
+ ## Core Concepts
11
+ - **[Facts](facts.md)** - Working with facts and queries
12
+ - **[Pattern Matching](pattern-matching.md)** - Pattern syntax and operators
13
+ - **[Variable Binding](variable-binding.md)** - Using variables in rules
14
+ - **[Negation](negation.md)** - Matching absent patterns
15
+
16
+ ## Persistence
17
+ - **[Blackboard Memory](blackboard-memory.md)** - Persistent shared memory
18
+ - **[Persistence Options](persistence.md)** - SQLite, Redis, and Hybrid backends
19
+
20
+ ## Next Steps
21
+ - **[Examples](../examples/index.md)** - See KBS in action
22
+ - **[Advanced Topics](../advanced/index.md)** - Production optimization
23
+ - **[API Reference](../api/index.md)** - Complete class documentation