kbs 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +85 -57
- data/docs/advanced/performance.md +109 -76
- data/docs/advanced/testing.md +399 -263
- data/docs/api/blackboard.md +1 -1
- data/docs/api/engine.md +77 -8
- data/docs/api/facts.md +3 -3
- data/docs/api/rules.md +110 -40
- data/docs/architecture/blackboard.md +108 -117
- data/docs/assets/images/fact-rule-relationship.svg +65 -0
- data/docs/assets/images/fact-structure.svg +42 -0
- data/docs/assets/images/inference-cycle.svg +47 -0
- data/docs/assets/images/kb-components.svg +43 -0
- data/docs/assets/images/rule-structure.svg +44 -0
- data/docs/assets/images/trading-signal-network.svg +1 -1
- data/docs/examples/index.md +219 -5
- data/docs/guides/blackboard-memory.md +89 -58
- data/docs/guides/dsl.md +24 -24
- data/docs/guides/getting-started.md +109 -107
- data/docs/guides/writing-rules.md +470 -311
- data/docs/index.md +16 -18
- data/docs/quick-start.md +92 -99
- data/docs/what-is-a-fact.md +694 -0
- data/docs/what-is-a-knowledge-base.md +350 -0
- data/docs/what-is-a-rule.md +833 -0
- data/examples/.gitignore +1 -0
- data/examples/advanced_example_dsl.rb +1 -1
- data/examples/ai_enhanced_kbs_dsl.rb +1 -1
- data/examples/car_diagnostic_dsl.rb +1 -1
- data/examples/concurrent_inference_demo.rb +0 -1
- data/examples/concurrent_inference_demo_dsl.rb +0 -1
- data/examples/csv_trading_system_dsl.rb +1 -1
- data/examples/iot_demo_using_dsl.rb +1 -1
- data/examples/portfolio_rebalancing_system_dsl.rb +1 -1
- data/examples/rule_source_demo.rb +123 -0
- data/examples/stock_trading_advanced_dsl.rb +1 -1
- data/examples/temp_dsl.txt +6214 -5269
- data/examples/timestamped_trading_dsl.rb +1 -1
- data/examples/trading_demo_dsl.rb +1 -1
- data/examples/working_demo_dsl.rb +1 -1
- data/lib/kbs/decompiler.rb +204 -0
- data/lib/kbs/dsl/knowledge_base.rb +100 -1
- data/lib/kbs/dsl.rb +3 -1
- data/lib/kbs/engine.rb +41 -0
- data/lib/kbs/version.rb +1 -1
- data/lib/kbs.rb +14 -12
- data/mkdocs.yml +30 -30
- metadata +15 -10
- data/docs/DOCUMENTATION_STATUS.md +0 -158
- data/docs/examples/expert-systems.md +0 -1031
- data/docs/examples/multi-agent.md +0 -1335
- data/docs/examples/stock-trading.md +0 -488
- data/examples/knowledge_base.db +0 -0
- data/examples/temp.txt +0 -7693
data/docs/guides/dsl.md
CHANGED
|
@@ -32,7 +32,7 @@ kb = KBS.knowledge_base do
|
|
|
32
32
|
|
|
33
33
|
on :temperature, value: greater_than(80), location: :loc?
|
|
34
34
|
|
|
35
|
-
perform do |bindings|
|
|
35
|
+
perform do |facts, bindings|
|
|
36
36
|
puts "High temperature at #{bindings[:loc?]}"
|
|
37
37
|
end
|
|
38
38
|
end
|
|
@@ -314,7 +314,7 @@ rule "rule_name" do
|
|
|
314
314
|
on :another_type, field: predicate
|
|
315
315
|
|
|
316
316
|
# Action
|
|
317
|
-
perform do |bindings|
|
|
317
|
+
perform do |facts, bindings|
|
|
318
318
|
# Code to execute
|
|
319
319
|
end
|
|
320
320
|
end
|
|
@@ -367,7 +367,7 @@ end
|
|
|
367
367
|
rule "log_reading" do
|
|
368
368
|
priority 1 # Low priority
|
|
369
369
|
on :temperature, value: :temp?
|
|
370
|
-
perform { |b| log(b[:temp?]) }
|
|
370
|
+
perform { |facts, b| log(b[:temp?]) }
|
|
371
371
|
end
|
|
372
372
|
```
|
|
373
373
|
|
|
@@ -436,7 +436,7 @@ Capture attribute values in variables (symbols starting with `?`):
|
|
|
436
436
|
on :temperature, value: :temp?, location: :loc?
|
|
437
437
|
|
|
438
438
|
# In action:
|
|
439
|
-
perform do |bindings|
|
|
439
|
+
perform do |facts, bindings|
|
|
440
440
|
puts "Temperature: #{bindings[:temp?]}"
|
|
441
441
|
puts "Location: #{bindings[:loc?]}"
|
|
442
442
|
end
|
|
@@ -648,7 +648,7 @@ Variables allow you to:
|
|
|
648
648
|
|
|
649
649
|
### Variable Syntax
|
|
650
650
|
|
|
651
|
-
Variables are symbols
|
|
651
|
+
Variables are symbols ending with `?`:
|
|
652
652
|
|
|
653
653
|
```ruby
|
|
654
654
|
:temp? # Variable named "temp"
|
|
@@ -664,7 +664,7 @@ Variables are symbols starting with `?`:
|
|
|
664
664
|
rule "temperature_report" do
|
|
665
665
|
on :temperature, value: :temp?, location: :loc?, timestamp: :time?
|
|
666
666
|
|
|
667
|
-
perform do |bindings|
|
|
667
|
+
perform do |facts, bindings|
|
|
668
668
|
puts "Temperature at #{bindings[:loc?]}: #{bindings[:temp?]}°F"
|
|
669
669
|
puts "Recorded: #{bindings[:time?]}"
|
|
670
670
|
end
|
|
@@ -682,7 +682,7 @@ rule "check_inventory" do
|
|
|
682
682
|
on :order, product_id: :pid?, quantity: :qty?
|
|
683
683
|
on :inventory, product_id: :pid?, available: :avail?
|
|
684
684
|
|
|
685
|
-
perform do |bindings|
|
|
685
|
+
perform do |facts, bindings|
|
|
686
686
|
if bindings[:avail?] < bindings[:qty?]
|
|
687
687
|
puts "Insufficient inventory for product #{bindings[:pid?]}"
|
|
688
688
|
end
|
|
@@ -705,7 +705,7 @@ rule "sensor_temperature_correlation" do
|
|
|
705
705
|
on :temperature, sensor_id: :sensor_id?, value: :temp?
|
|
706
706
|
on :reading, sensor_id: :sensor_id?, timestamp: :time?
|
|
707
707
|
|
|
708
|
-
perform do |bindings|
|
|
708
|
+
perform do |facts, bindings|
|
|
709
709
|
# All three facts share the same sensor_id
|
|
710
710
|
puts "Sensor #{bindings[:sensor_id?]} at #{bindings[:loc?]}"
|
|
711
711
|
puts "Reading: #{bindings[:temp?]}°F at #{bindings[:time?]}"
|
|
@@ -777,7 +777,7 @@ rule "no_matching_inventory" do
|
|
|
777
777
|
on :order, product_id: :pid?
|
|
778
778
|
without :inventory, product_id: :pid?
|
|
779
779
|
|
|
780
|
-
perform do |bindings|
|
|
780
|
+
perform do |facts, bindings|
|
|
781
781
|
puts "No inventory for product #{bindings[:pid?]}"
|
|
782
782
|
end
|
|
783
783
|
end
|
|
@@ -810,7 +810,7 @@ end
|
|
|
810
810
|
rule "sensor_timeout" do
|
|
811
811
|
on :sensor, id: :sensor_id?, expected: true
|
|
812
812
|
without :reading, sensor_id: :sensor_id?
|
|
813
|
-
perform { |b| puts "Sensor #{b[:sensor_id?]} timeout" }
|
|
813
|
+
perform { |facts, b| puts "Sensor #{b[:sensor_id?]} timeout" }
|
|
814
814
|
end
|
|
815
815
|
```
|
|
816
816
|
|
|
@@ -827,7 +827,7 @@ All of these are **aliases**:
|
|
|
827
827
|
- **`perform(&block)`** - Primary action keyword
|
|
828
828
|
- **`action(&block)`** - Alias
|
|
829
829
|
- **`execute(&block)`** - Alias
|
|
830
|
-
- **`then(&block)`** - Alias
|
|
830
|
+
- **`then(&block)`** - Alias - TODO: isn't "then" a ruby keyword?
|
|
831
831
|
|
|
832
832
|
---
|
|
833
833
|
|
|
@@ -839,7 +839,7 @@ Actions receive a `bindings` hash containing all variable bindings:
|
|
|
839
839
|
rule "example" do
|
|
840
840
|
on :temperature, value: :temp?, location: :loc?
|
|
841
841
|
|
|
842
|
-
perform do |bindings|
|
|
842
|
+
perform do |facts, bindings|
|
|
843
843
|
temp = bindings[:temp?]
|
|
844
844
|
location = bindings[:loc?]
|
|
845
845
|
puts "Temperature at #{location}: #{temp}°F"
|
|
@@ -855,7 +855,7 @@ Actions can:
|
|
|
855
855
|
|
|
856
856
|
1. **Read bindings**:
|
|
857
857
|
```ruby
|
|
858
|
-
perform do |bindings|
|
|
858
|
+
perform do |facts, bindings|
|
|
859
859
|
value = bindings[:temp?]
|
|
860
860
|
end
|
|
861
861
|
```
|
|
@@ -874,7 +874,7 @@ end
|
|
|
874
874
|
|
|
875
875
|
3. **Call external methods**:
|
|
876
876
|
```ruby
|
|
877
|
-
perform do |bindings|
|
|
877
|
+
perform do |facts, bindings|
|
|
878
878
|
send_email_alert(bindings[:temp?])
|
|
879
879
|
log_to_database(bindings)
|
|
880
880
|
trigger_alarm if bindings[:level?] == "critical"
|
|
@@ -883,7 +883,7 @@ end
|
|
|
883
883
|
|
|
884
884
|
4. **Add/remove facts**:
|
|
885
885
|
```ruby
|
|
886
|
-
perform do |bindings|
|
|
886
|
+
perform do |facts, bindings|
|
|
887
887
|
# Add derived fact
|
|
888
888
|
fact :alert, level: "high", source: bindings[:sensor_id?]
|
|
889
889
|
|
|
@@ -901,7 +901,7 @@ end
|
|
|
901
901
|
# Simple logging
|
|
902
902
|
rule "log_temperature" do
|
|
903
903
|
on :temperature, value: :temp?
|
|
904
|
-
perform { |b| puts "Temperature: #{b[:temp?]}" }
|
|
904
|
+
perform { |facts, b| puts "Temperature: #{b[:temp?]}" }
|
|
905
905
|
end
|
|
906
906
|
|
|
907
907
|
# State machine transition
|
|
@@ -909,7 +909,7 @@ rule "pending_to_processing" do
|
|
|
909
909
|
on :order, id: :order_id?, status: "pending"
|
|
910
910
|
on :worker, status: "available", id: :worker_id?
|
|
911
911
|
|
|
912
|
-
perform do |bindings|
|
|
912
|
+
perform do |facts, bindings|
|
|
913
913
|
# Update order status
|
|
914
914
|
order = query(:order, id: bindings[:order_id?]).first
|
|
915
915
|
retract order
|
|
@@ -1079,7 +1079,7 @@ end
|
|
|
1079
1079
|
kb = KBS.knowledge_base do
|
|
1080
1080
|
rule "example" do
|
|
1081
1081
|
on :temperature, value: :temp?
|
|
1082
|
-
perform { |b| puts b[:temp?] }
|
|
1082
|
+
perform { |facts, b| puts b[:temp?] }
|
|
1083
1083
|
end
|
|
1084
1084
|
end
|
|
1085
1085
|
|
|
@@ -1112,7 +1112,7 @@ kb = KBS.knowledge_base do
|
|
|
1112
1112
|
on :temperature, sensor_id: :sensor_id?, value: greater_than(80)
|
|
1113
1113
|
without :alert, sensor_id: :sensor_id? # No existing alert
|
|
1114
1114
|
|
|
1115
|
-
perform do |bindings|
|
|
1115
|
+
perform do |facts, bindings|
|
|
1116
1116
|
puts "⚠️ HIGH TEMPERATURE ALERT"
|
|
1117
1117
|
puts "Sensor: #{bindings[:sensor_id?]}"
|
|
1118
1118
|
puts "Temperature: #{bindings[:value?]}°F"
|
|
@@ -1132,7 +1132,7 @@ kb = KBS.knowledge_base do
|
|
|
1132
1132
|
on :temperature, sensor_id: :sensor_id?, value: less_than(75)
|
|
1133
1133
|
on :alert, sensor_id: :sensor_id?
|
|
1134
1134
|
|
|
1135
|
-
perform do |bindings|
|
|
1135
|
+
perform do |facts, bindings|
|
|
1136
1136
|
puts "✓ Temperature normal for sensor #{bindings[:sensor_id?]}"
|
|
1137
1137
|
|
|
1138
1138
|
# Remove alert
|
|
@@ -1167,7 +1167,7 @@ kb = KBS.knowledge_base do
|
|
|
1167
1167
|
on :order, id: :order_id?, status: "new", product_id: :pid?, quantity: :qty?
|
|
1168
1168
|
on :inventory, product_id: :pid?, quantity: :available?
|
|
1169
1169
|
|
|
1170
|
-
perform do |bindings|
|
|
1170
|
+
perform do |facts, bindings|
|
|
1171
1171
|
if bindings[:available?] >= bindings[:qty?]
|
|
1172
1172
|
order = query(:order, id: bindings[:order_id?]).first
|
|
1173
1173
|
retract order
|
|
@@ -1191,7 +1191,7 @@ kb = KBS.knowledge_base do
|
|
|
1191
1191
|
product_id: :pid?, quantity: :qty?
|
|
1192
1192
|
on :inventory, product_id: :pid?, quantity: :available?
|
|
1193
1193
|
|
|
1194
|
-
perform do |bindings|
|
|
1194
|
+
perform do |facts, bindings|
|
|
1195
1195
|
# Deduct inventory
|
|
1196
1196
|
inventory = query(:inventory, product_id: bindings[:pid?]).first
|
|
1197
1197
|
retract inventory
|
|
@@ -1296,13 +1296,13 @@ on :order, status: ->(s) { ["pending", "processing"].include?(s) }
|
|
|
1296
1296
|
# Good - Simple, focused action
|
|
1297
1297
|
rule "log_temperature" do
|
|
1298
1298
|
on :temperature, value: :temp?
|
|
1299
|
-
perform { |b| logger.info("Temperature: #{b[:temp?]}") }
|
|
1299
|
+
perform { |facts, b| logger.info("Temperature: #{b[:temp?]}") }
|
|
1300
1300
|
end
|
|
1301
1301
|
|
|
1302
1302
|
# Avoid - Complex logic in action
|
|
1303
1303
|
rule "complex_action" do
|
|
1304
1304
|
on :temperature, value: :temp?
|
|
1305
|
-
perform do |b|
|
|
1305
|
+
perform do |facts, b|
|
|
1306
1306
|
# 100 lines of complex logic...
|
|
1307
1307
|
# Better to extract to methods
|
|
1308
1308
|
end
|
|
@@ -28,35 +28,35 @@ gem install kbs
|
|
|
28
28
|
|
|
29
29
|
Let's create a simple rule that fires when temperature exceeds a threshold.
|
|
30
30
|
|
|
31
|
-
### Step 1: Create
|
|
31
|
+
### Step 1: Create a Knowledge Base
|
|
32
32
|
|
|
33
33
|
```ruby
|
|
34
34
|
require 'kbs'
|
|
35
35
|
|
|
36
|
-
# Create
|
|
37
|
-
|
|
36
|
+
# Create a knowledge base with DSL
|
|
37
|
+
kb = KBS.knowledge_base do
|
|
38
|
+
# Rules will be defined here
|
|
39
|
+
end
|
|
38
40
|
```
|
|
39
41
|
|
|
40
|
-
The
|
|
42
|
+
The knowledge base manages rules, facts, and executes the pattern matching algorithm.
|
|
41
43
|
|
|
42
44
|
### Step 2: Define a Rule
|
|
43
45
|
|
|
44
46
|
```ruby
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
kb = KBS.knowledge_base do
|
|
48
|
+
# Define a rule for high temperature alerts
|
|
49
|
+
rule "high_temperature_alert" do
|
|
50
|
+
on :sensor, id: :sensor_id?, temp: :temp?
|
|
51
|
+
on :threshold, id: :sensor_id?, max: :max?
|
|
52
|
+
|
|
53
|
+
perform do |facts, bindings|
|
|
54
|
+
if bindings[:temp?] > bindings[:max?]
|
|
55
|
+
puts "🚨 ALERT: Sensor #{bindings[:sensor_id?]} at #{bindings[:temp?]}°C"
|
|
56
|
+
end
|
|
55
57
|
end
|
|
56
58
|
end
|
|
57
59
|
end
|
|
58
|
-
|
|
59
|
-
engine.add_rule(high_temp_rule)
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
**What this rule does:**
|
|
@@ -67,24 +67,32 @@ engine.add_rule(high_temp_rule)
|
|
|
67
67
|
|
|
68
68
|
**Variable binding** (`:sensor_id?`) ensures we only compare sensors with their own thresholds.
|
|
69
69
|
|
|
70
|
-
### Step 3: Add Facts
|
|
70
|
+
### Step 3: Add Facts and Run
|
|
71
71
|
|
|
72
72
|
```ruby
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
kb = KBS.knowledge_base do
|
|
74
|
+
rule "high_temperature_alert" do
|
|
75
|
+
on :sensor, id: :sensor_id?, temp: :temp?
|
|
76
|
+
on :threshold, id: :sensor_id?, max: :max?
|
|
77
|
+
|
|
78
|
+
perform do |facts, bindings|
|
|
79
|
+
if bindings[:temp?] > bindings[:max?]
|
|
80
|
+
puts "🚨 ALERT: Sensor #{bindings[:sensor_id?]} at #{bindings[:temp?]}°C"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
81
84
|
|
|
82
|
-
|
|
85
|
+
# Add facts
|
|
86
|
+
fact :sensor, id: "bedroom", temp: 28
|
|
87
|
+
fact :threshold, id: "bedroom", max: 25
|
|
83
88
|
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
# Run inference
|
|
90
|
+
run
|
|
91
|
+
end
|
|
86
92
|
```
|
|
87
93
|
|
|
94
|
+
Facts are observations about the world. The knowledge base automatically matches them against rule conditions.
|
|
95
|
+
|
|
88
96
|
**Output:**
|
|
89
97
|
```
|
|
90
98
|
🚨 ALERT: Sensor bedroom at 28°C
|
|
@@ -97,10 +105,10 @@ The rule fired because the bedroom temperature (28°C) exceeds its threshold (25
|
|
|
97
105
|
Variable binding connects facts across conditions. Here's how it works:
|
|
98
106
|
|
|
99
107
|
```ruby
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
rule "example" do
|
|
109
|
+
on :sensor, id: :sensor_id?, temp: :temp?
|
|
110
|
+
on :threshold, id: :sensor_id?, max: :max?
|
|
111
|
+
end
|
|
104
112
|
```
|
|
105
113
|
|
|
106
114
|
**Binding Process:**
|
|
@@ -119,19 +127,19 @@ Without variable binding, the rule would incorrectly match bedroom sensors with
|
|
|
119
127
|
Let's prevent the same alert from firing repeatedly:
|
|
120
128
|
|
|
121
129
|
```ruby
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
kb = KBS.knowledge_base do
|
|
131
|
+
rule "smart_temperature_alert" do
|
|
132
|
+
on :sensor, id: :sensor_id?, temp: :temp?
|
|
133
|
+
on :threshold, id: :sensor_id?, max: :max?
|
|
126
134
|
# Only fire if no alert already exists for this sensor
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
without :alert, sensor_id: :sensor_id?
|
|
136
|
+
|
|
137
|
+
perform do |facts, bindings|
|
|
138
|
+
if bindings[:temp?] > bindings[:max?]
|
|
139
|
+
puts "🚨 ALERT: Sensor #{bindings[:sensor_id?]} at #{bindings[:temp?]}°C"
|
|
140
|
+
# Record that we sent this alert
|
|
141
|
+
fact :alert, sensor_id: bindings[:sensor_id?]
|
|
142
|
+
end
|
|
135
143
|
end
|
|
136
144
|
end
|
|
137
145
|
end
|
|
@@ -187,25 +195,24 @@ Learn more: [Blackboard Memory Guide](blackboard-memory.md)
|
|
|
187
195
|
When multiple rules match, control firing order with priorities:
|
|
188
196
|
|
|
189
197
|
```ruby
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
198
|
+
kb = KBS.knowledge_base do
|
|
199
|
+
rule "critical_alert", priority: 100 do
|
|
200
|
+
on :sensor, temp: :temp?
|
|
201
|
+
|
|
202
|
+
perform do |facts, bindings|
|
|
203
|
+
if bindings[:temp?] > 50
|
|
204
|
+
puts "🔥 CRITICAL: Immediate shutdown required!"
|
|
205
|
+
exit(1)
|
|
206
|
+
end
|
|
199
207
|
end
|
|
200
208
|
end
|
|
201
|
-
end
|
|
202
209
|
|
|
203
|
-
|
|
204
|
-
|
|
210
|
+
rule "normal_alert", priority: 10 do
|
|
211
|
+
on :sensor, temp: :temp?
|
|
212
|
+
# ... (less urgent alerts)
|
|
213
|
+
perform { |facts| puts "Normal alert" }
|
|
214
|
+
end
|
|
205
215
|
end
|
|
206
|
-
|
|
207
|
-
engine.add_rule(critical_rule)
|
|
208
|
-
engine.add_rule(normal_rule)
|
|
209
216
|
```
|
|
210
217
|
|
|
211
218
|
**Priority:** Higher numbers fire first. Default is `0`.
|
|
@@ -224,74 +231,69 @@ require 'kbs'
|
|
|
224
231
|
class TemperatureMonitor
|
|
225
232
|
def initialize
|
|
226
233
|
@engine = KBS::Blackboard::Engine.new(db_path: 'sensors.db')
|
|
227
|
-
setup_rules
|
|
234
|
+
@kb = setup_rules
|
|
228
235
|
end
|
|
229
236
|
|
|
230
237
|
def setup_rules
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
238
|
+
engine = @engine
|
|
239
|
+
monitor = self
|
|
240
|
+
|
|
241
|
+
KBS.knowledge_base(engine: engine) do
|
|
242
|
+
# Rule 1: Send alert when temp exceeds threshold
|
|
243
|
+
rule "temperature_alert", priority: 50 do
|
|
244
|
+
on :sensor, id: :id?, temp: :temp?
|
|
245
|
+
on :threshold, id: :id?, max: :max?
|
|
246
|
+
without :alert, sensor_id: :id?
|
|
247
|
+
|
|
248
|
+
perform do |facts, bindings|
|
|
249
|
+
if bindings[:temp?] > bindings[:max?]
|
|
250
|
+
monitor.send_alert(bindings[:id?], bindings[:temp?], bindings[:max?])
|
|
251
|
+
fact :alert, sensor_id: bindings[:id?]
|
|
252
|
+
end
|
|
243
253
|
end
|
|
244
254
|
end
|
|
245
|
-
end
|
|
246
255
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
@engine.remove_fact(alert_fact) if alert_fact
|
|
256
|
+
# Rule 2: Clear alert when temp drops below threshold
|
|
257
|
+
rule "clear_alert", priority: 40 do
|
|
258
|
+
on :sensor, id: :id?, temp: :temp?
|
|
259
|
+
on :threshold, id: :id?, max: :max?
|
|
260
|
+
on :alert, sensor_id: :id?
|
|
261
|
+
|
|
262
|
+
perform do |facts, bindings|
|
|
263
|
+
if bindings[:temp?] <= bindings[:max?]
|
|
264
|
+
monitor.clear_alert(bindings[:id?])
|
|
265
|
+
# Find and retract the alert fact
|
|
266
|
+
alert_fact = query(:alert, sensor_id: bindings[:id?]).first
|
|
267
|
+
retract alert_fact if alert_fact
|
|
268
|
+
end
|
|
261
269
|
end
|
|
262
270
|
end
|
|
263
|
-
end
|
|
264
271
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
KBS::Condition.new(:sensor, { temp: :temp? })
|
|
269
|
-
]
|
|
272
|
+
# Rule 3: Emergency shutdown for extreme temps
|
|
273
|
+
rule "emergency_shutdown", priority: 100 do
|
|
274
|
+
on :sensor, temp: :temp?
|
|
270
275
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
276
|
+
perform do |facts, bindings|
|
|
277
|
+
if bindings[:temp?] > 60
|
|
278
|
+
monitor.emergency_shutdown(bindings[:temp?])
|
|
279
|
+
end
|
|
274
280
|
end
|
|
275
281
|
end
|
|
276
282
|
end
|
|
277
|
-
|
|
278
|
-
@engine.add_rule(alert_rule)
|
|
279
|
-
@engine.add_rule(clear_rule)
|
|
280
|
-
@engine.add_rule(emergency_rule)
|
|
281
283
|
end
|
|
282
284
|
|
|
283
285
|
def add_sensor(id, max_temp)
|
|
284
|
-
@
|
|
286
|
+
@kb.fact :threshold, id: id, max: max_temp
|
|
285
287
|
end
|
|
286
288
|
|
|
287
289
|
def update_reading(id, temp)
|
|
288
|
-
#
|
|
289
|
-
old = @
|
|
290
|
-
@
|
|
290
|
+
# Find and remove old reading
|
|
291
|
+
old = @kb.query(:sensor, id: id).first
|
|
292
|
+
@kb.retract old if old
|
|
291
293
|
|
|
292
294
|
# Add new reading
|
|
293
|
-
@
|
|
294
|
-
@
|
|
295
|
+
@kb.fact :sensor, id: id, temp: temp
|
|
296
|
+
@kb.run
|
|
295
297
|
end
|
|
296
298
|
|
|
297
299
|
def send_alert(sensor_id, temp, threshold)
|
|
@@ -375,7 +377,7 @@ Now that you understand the basics, explore:
|
|
|
375
377
|
- **[Writing Rules](writing-rules.md)** - Advanced rule patterns and techniques
|
|
376
378
|
- **[Pattern Matching](pattern-matching.md)** - Deep dive into condition syntax
|
|
377
379
|
- **[Blackboard Memory](blackboard-memory.md)** - Multi-agent collaboration
|
|
378
|
-
- **[Stock Trading
|
|
380
|
+
- **[Stock Trading Examples](../examples/index.md#stock-trading-systems)** - Real-world applications
|
|
379
381
|
- **[API Reference](../api/engine.md)** - Complete method documentation
|
|
380
382
|
|
|
381
383
|
---
|