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.
- checksums.yaml +4 -4
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/CHANGELOG.md +68 -2
- data/README.md +235 -334
- data/docs/DOCUMENTATION_STATUS.md +158 -0
- data/docs/advanced/custom-persistence.md +775 -0
- data/docs/advanced/debugging.md +726 -0
- data/docs/advanced/index.md +8 -0
- data/docs/advanced/performance.md +832 -0
- data/docs/advanced/testing.md +691 -0
- data/docs/api/blackboard.md +1157 -0
- data/docs/api/engine.md +978 -0
- data/docs/api/facts.md +1212 -0
- data/docs/api/index.md +12 -0
- data/docs/api/rules.md +1034 -0
- data/docs/architecture/blackboard.md +553 -0
- data/docs/architecture/index.md +277 -0
- data/docs/architecture/network-structure.md +343 -0
- data/docs/architecture/rete-algorithm.md +737 -0
- data/docs/assets/css/custom.css +83 -0
- data/docs/assets/images/blackboard-architecture.svg +136 -0
- data/docs/assets/images/compiled-network.svg +101 -0
- data/docs/assets/images/fact-assertion-flow.svg +117 -0
- data/docs/assets/images/kbs.jpg +0 -0
- data/docs/assets/images/pattern-matching-trace.svg +136 -0
- data/docs/assets/images/rete-network-layers.svg +96 -0
- data/docs/assets/images/system-layers.svg +69 -0
- data/docs/assets/images/trading-signal-network.svg +139 -0
- data/docs/assets/js/mathjax.js +17 -0
- data/docs/examples/expert-systems.md +1031 -0
- data/docs/examples/index.md +9 -0
- data/docs/examples/multi-agent.md +1335 -0
- data/docs/examples/stock-trading.md +488 -0
- data/docs/guides/blackboard-memory.md +558 -0
- data/docs/guides/dsl.md +1321 -0
- data/docs/guides/facts.md +652 -0
- data/docs/guides/getting-started.md +383 -0
- data/docs/guides/index.md +23 -0
- data/docs/guides/negation.md +529 -0
- data/docs/guides/pattern-matching.md +561 -0
- data/docs/guides/persistence.md +451 -0
- data/docs/guides/variable-binding.md +491 -0
- data/docs/guides/writing-rules.md +755 -0
- data/docs/index.md +157 -0
- data/docs/installation.md +156 -0
- data/docs/quick-start.md +228 -0
- data/examples/README.md +2 -2
- data/examples/advanced_example.rb +2 -2
- data/examples/advanced_example_dsl.rb +224 -0
- data/examples/ai_enhanced_kbs.rb +1 -1
- data/examples/ai_enhanced_kbs_dsl.rb +538 -0
- data/examples/blackboard_demo_dsl.rb +50 -0
- data/examples/car_diagnostic.rb +1 -1
- data/examples/car_diagnostic_dsl.rb +54 -0
- data/examples/concurrent_inference_demo.rb +5 -5
- data/examples/concurrent_inference_demo_dsl.rb +363 -0
- data/examples/csv_trading_system.rb +1 -1
- data/examples/csv_trading_system_dsl.rb +525 -0
- data/examples/knowledge_base.db +0 -0
- data/examples/portfolio_rebalancing_system.rb +2 -2
- data/examples/portfolio_rebalancing_system_dsl.rb +613 -0
- data/examples/redis_trading_demo_dsl.rb +177 -0
- data/examples/run_all.rb +50 -0
- data/examples/run_all_dsl.rb +49 -0
- data/examples/stock_trading_advanced.rb +1 -1
- data/examples/stock_trading_advanced_dsl.rb +404 -0
- data/examples/temp.txt +7693 -0
- data/examples/temp_dsl.txt +8447 -0
- data/examples/timestamped_trading.rb +1 -1
- data/examples/timestamped_trading_dsl.rb +258 -0
- data/examples/trading_demo.rb +1 -1
- data/examples/trading_demo_dsl.rb +322 -0
- data/examples/working_demo.rb +1 -1
- data/examples/working_demo_dsl.rb +160 -0
- data/lib/kbs/blackboard/engine.rb +3 -3
- data/lib/kbs/blackboard/fact.rb +1 -1
- data/lib/kbs/condition.rb +1 -1
- data/lib/kbs/dsl/knowledge_base.rb +1 -1
- data/lib/kbs/dsl/variable.rb +1 -1
- data/lib/kbs/{rete_engine.rb → engine.rb} +1 -1
- data/lib/kbs/fact.rb +1 -1
- data/lib/kbs/version.rb +1 -1
- data/lib/kbs.rb +2 -2
- data/mkdocs.yml +181 -0
- metadata +66 -6
- data/examples/stock_trading_system.rb.bak +0 -563
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
# Variable Binding
|
|
2
|
+
|
|
3
|
+
Variables connect facts across conditions, enabling join constraints in the RETE network. This guide explains how binding works, join tests, and optimization strategies.
|
|
4
|
+
|
|
5
|
+
## Variable Syntax
|
|
6
|
+
|
|
7
|
+
Variables start with `?` and are symbols:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
:temp? # Variable named "temp"
|
|
11
|
+
:sensor_id? # Variable named "sensor_id"
|
|
12
|
+
:x? # Variable named "x"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Naming conventions:**
|
|
16
|
+
- Use lowercase with underscores
|
|
17
|
+
- Be descriptive
|
|
18
|
+
- Match domain terminology
|
|
19
|
+
|
|
20
|
+
## Basic Binding
|
|
21
|
+
|
|
22
|
+
### Single Variable
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
KBS::Condition.new(:sensor, { temp: :t? })
|
|
26
|
+
|
|
27
|
+
# Matches fact:
|
|
28
|
+
{ type: :sensor, temp: 28 }
|
|
29
|
+
|
|
30
|
+
# Creates binding:
|
|
31
|
+
{ :t? => 28 }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Multiple Variables
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
KBS::Condition.new(:stock, {
|
|
38
|
+
symbol: :sym?,
|
|
39
|
+
price: :price?,
|
|
40
|
+
volume: :vol?
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
# Matches:
|
|
44
|
+
{ type: :stock, symbol: "AAPL", price: 150, volume: 1000 }
|
|
45
|
+
|
|
46
|
+
# Bindings:
|
|
47
|
+
{
|
|
48
|
+
:sym? => "AAPL",
|
|
49
|
+
:price? => 150,
|
|
50
|
+
:vol? => 1000
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Cross-Condition Binding
|
|
55
|
+
|
|
56
|
+
### Join Constraints
|
|
57
|
+
|
|
58
|
+
Variables with the same name create equality constraints:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
r.conditions = [
|
|
62
|
+
# Condition 1: Binds :id? to sensor's id
|
|
63
|
+
KBS::Condition.new(:sensor, { id: :id?, temp: :temp? }),
|
|
64
|
+
|
|
65
|
+
# Condition 2: Must match same :id?
|
|
66
|
+
KBS::Condition.new(:threshold, { sensor_id: :id?, max: :max? })
|
|
67
|
+
]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Join test:**
|
|
71
|
+
```
|
|
72
|
+
sensor[:id] == threshold[:sensor_id]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Multiple Joins
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
r.conditions = [
|
|
79
|
+
KBS::Condition.new(:a, { x: :v1?, y: :v2? }),
|
|
80
|
+
KBS::Condition.new(:b, { p: :v1?, q: :v3? }),
|
|
81
|
+
KBS::Condition.new(:c, { m: :v2?, n: :v3? })
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
# Join tests:
|
|
85
|
+
# a[:x] == b[:p] (via :v1?)
|
|
86
|
+
# a[:y] == c[:m] (via :v2?)
|
|
87
|
+
# b[:q] == c[:n] (via :v3?)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Visual Example
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
Condition 1: stock(symbol: :sym?, price: :p?)
|
|
94
|
+
Condition 2: watchlist(symbol: :sym?)
|
|
95
|
+
Condition 3: alert_config(symbol: :sym?, threshold: :t?)
|
|
96
|
+
|
|
97
|
+
Variable :sym? creates two joins:
|
|
98
|
+
├─ stock[:symbol] == watchlist[:symbol]
|
|
99
|
+
└─ stock[:symbol] == alert_config[:symbol]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Binding Lifecycle
|
|
103
|
+
|
|
104
|
+
### 1. First Occurrence: Bind
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
# First condition with :sym?
|
|
108
|
+
KBS::Condition.new(:stock, { symbol: :sym? })
|
|
109
|
+
|
|
110
|
+
# Fact matches
|
|
111
|
+
{ type: :stock, symbol: "AAPL" }
|
|
112
|
+
|
|
113
|
+
# Binding created:
|
|
114
|
+
{ :sym? => "AAPL" }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 2. Subsequent Occurrences: Test
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
# Second condition with :sym?
|
|
121
|
+
KBS::Condition.new(:watchlist, { symbol: :sym? })
|
|
122
|
+
|
|
123
|
+
# Checks if symbol == "AAPL" (from previous binding)
|
|
124
|
+
# Matches:
|
|
125
|
+
{ type: :watchlist, symbol: "AAPL" } # ✓
|
|
126
|
+
|
|
127
|
+
# Does not match:
|
|
128
|
+
{ type: :watchlist, symbol: "GOOGL" } # ✗
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 3. Action: Access
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
r.action = lambda do |facts, bindings|
|
|
135
|
+
# Access bound variables
|
|
136
|
+
symbol = bindings[:sym?]
|
|
137
|
+
price = bindings[:p?]
|
|
138
|
+
|
|
139
|
+
puts "#{symbol} at $#{price}"
|
|
140
|
+
end
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Join Tests
|
|
144
|
+
|
|
145
|
+
### What is a Join Test?
|
|
146
|
+
|
|
147
|
+
A join test verifies that variable values match across facts:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
r.conditions = [
|
|
151
|
+
KBS::Condition.new(:a, { x: :v? }),
|
|
152
|
+
KBS::Condition.new(:b, { y: :v? })
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
# Join test structure:
|
|
156
|
+
{
|
|
157
|
+
token_field_index: 0, # Index of fact in token (first condition)
|
|
158
|
+
token_field: :x, # Attribute name in first fact
|
|
159
|
+
fact_field: :y, # Attribute name in new fact
|
|
160
|
+
operation: :eq # Equality test
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Join Test Execution
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
def perform_join_test(token, new_fact, test)
|
|
168
|
+
# Get value from token (previous facts)
|
|
169
|
+
token_fact = token.facts[test[:token_field_index]]
|
|
170
|
+
token_value = token_fact[test[:token_field]]
|
|
171
|
+
|
|
172
|
+
# Get value from new fact
|
|
173
|
+
fact_value = new_fact[test[:fact_field]]
|
|
174
|
+
|
|
175
|
+
# Test equality
|
|
176
|
+
token_value == fact_value
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Example Execution
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
# Rule
|
|
184
|
+
r.conditions = [
|
|
185
|
+
KBS::Condition.new(:sensor, { id: :id?, temp: :temp? }),
|
|
186
|
+
KBS::Condition.new(:threshold, { sensor_id: :id?, max: :max? })
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
# Facts
|
|
190
|
+
sensor = { type: :sensor, id: "bedroom", temp: 28 }
|
|
191
|
+
threshold = { type: :threshold, sensor_id: "bedroom", max: 25 }
|
|
192
|
+
|
|
193
|
+
# Join execution:
|
|
194
|
+
# 1. sensor matches → token created
|
|
195
|
+
token = Token.new(parent: root, fact: sensor)
|
|
196
|
+
# Bindings: { :id? => "bedroom", :temp? => 28 }
|
|
197
|
+
|
|
198
|
+
# 2. threshold tested against token
|
|
199
|
+
test = {
|
|
200
|
+
token_field_index: 0, # sensor is first fact
|
|
201
|
+
token_field: :id, # sensor's id attribute
|
|
202
|
+
fact_field: :sensor_id, # threshold's sensor_id attribute
|
|
203
|
+
operation: :eq
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
# 3. Perform join
|
|
207
|
+
token_value = sensor[:id] # "bedroom"
|
|
208
|
+
fact_value = threshold[:sensor_id] # "bedroom"
|
|
209
|
+
result = token_value == fact_value # true
|
|
210
|
+
# ✓ Join succeeds → new token created
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Binding Strategies
|
|
214
|
+
|
|
215
|
+
### Pattern 1: Primary Key Join
|
|
216
|
+
|
|
217
|
+
Connect facts via identifier:
|
|
218
|
+
|
|
219
|
+
```ruby
|
|
220
|
+
r.conditions = [
|
|
221
|
+
KBS::Condition.new(:order, {
|
|
222
|
+
id: :order_id?,
|
|
223
|
+
status: "pending"
|
|
224
|
+
}),
|
|
225
|
+
|
|
226
|
+
KBS::Condition.new(:payment, {
|
|
227
|
+
order_id: :order_id?,
|
|
228
|
+
verified: true
|
|
229
|
+
})
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
# Matches orders with verified payments
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Pattern 2: Multi-Attribute Join
|
|
236
|
+
|
|
237
|
+
Join on multiple fields:
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
r.conditions = [
|
|
241
|
+
KBS::Condition.new(:trade, {
|
|
242
|
+
symbol: :sym?,
|
|
243
|
+
date: :date?,
|
|
244
|
+
volume: :vol?
|
|
245
|
+
}),
|
|
246
|
+
|
|
247
|
+
KBS::Condition.new(:settlement, {
|
|
248
|
+
symbol: :sym?,
|
|
249
|
+
trade_date: :date?
|
|
250
|
+
})
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
# Joins on both symbol AND date
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Pattern 3: Transitive Binding
|
|
257
|
+
|
|
258
|
+
Chain bindings across three+ conditions:
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
r.conditions = [
|
|
262
|
+
KBS::Condition.new(:a, { id: :x? }),
|
|
263
|
+
KBS::Condition.new(:b, { a_id: :x?, id: :y? }),
|
|
264
|
+
KBS::Condition.new(:c, { b_id: :y? })
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
# a[:id] == b[:a_id]
|
|
268
|
+
# b[:id] == c[:b_id]
|
|
269
|
+
# Creates chain: a → b → c
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Pattern 4: Fan-Out Join
|
|
273
|
+
|
|
274
|
+
One fact joins with multiple:
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
r.conditions = [
|
|
278
|
+
KBS::Condition.new(:sensor, { id: :id?, temp: :t? }),
|
|
279
|
+
KBS::Condition.new(:threshold, { sensor_id: :id? }),
|
|
280
|
+
KBS::Condition.new(:alert_config, { sensor_id: :id? }),
|
|
281
|
+
KBS::Condition.new(:location, { sensor_id: :id? })
|
|
282
|
+
]
|
|
283
|
+
|
|
284
|
+
# All join on :id?
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Performance Implications
|
|
288
|
+
|
|
289
|
+
### Join Cardinality
|
|
290
|
+
|
|
291
|
+
```ruby
|
|
292
|
+
# Condition 1: 100 sensor facts
|
|
293
|
+
KBS::Condition.new(:sensor, { temp: :t? })
|
|
294
|
+
|
|
295
|
+
# Condition 2: 200 threshold facts
|
|
296
|
+
KBS::Condition.new(:threshold, { max: :m? })
|
|
297
|
+
|
|
298
|
+
# Without variable binding:
|
|
299
|
+
# Potential matches: 100 × 200 = 20,000
|
|
300
|
+
|
|
301
|
+
# With variable binding:
|
|
302
|
+
KBS::Condition.new(:sensor, { id: :id?, temp: :t? })
|
|
303
|
+
KBS::Condition.new(:threshold, { sensor_id: :id?, max: :m? })
|
|
304
|
+
|
|
305
|
+
# Actual matches: ~100 (1:1 relationship)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Variable bindings dramatically reduce join size.**
|
|
309
|
+
|
|
310
|
+
### Beta Memory Size
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
# Bad: No shared variables
|
|
314
|
+
r.conditions = [
|
|
315
|
+
KBS::Condition.new(:a, {}), # 1000 facts
|
|
316
|
+
KBS::Condition.new(:b, {}), # 1000 facts
|
|
317
|
+
KBS::Condition.new(:c, {}) # 1000 facts
|
|
318
|
+
]
|
|
319
|
+
# Beta memory: 1000 × 1000 × 1000 = 1,000,000,000 tokens!
|
|
320
|
+
|
|
321
|
+
# Good: Shared variables
|
|
322
|
+
r.conditions = [
|
|
323
|
+
KBS::Condition.new(:a, { id: :id? }),
|
|
324
|
+
KBS::Condition.new(:b, { a_id: :id? }),
|
|
325
|
+
KBS::Condition.new(:c, { a_id: :id? })
|
|
326
|
+
]
|
|
327
|
+
# Beta memory: ~1000 tokens (assuming 1:1:1 relationship)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Optimization Tips
|
|
331
|
+
|
|
332
|
+
**1. Use specific bindings:**
|
|
333
|
+
|
|
334
|
+
```ruby
|
|
335
|
+
# Good: Binds sensor to specific readings
|
|
336
|
+
KBS::Condition.new(:sensor, { id: :id? })
|
|
337
|
+
KBS::Condition.new(:reading, { sensor_id: :id? })
|
|
338
|
+
|
|
339
|
+
# Bad: No binding (cross product)
|
|
340
|
+
KBS::Condition.new(:sensor, {})
|
|
341
|
+
KBS::Condition.new(:reading, {})
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**2. Order by selectivity:**
|
|
345
|
+
|
|
346
|
+
```ruby
|
|
347
|
+
# Good: Specific first
|
|
348
|
+
r.conditions = [
|
|
349
|
+
KBS::Condition.new(:critical_alert, { id: :id? }), # 1 fact
|
|
350
|
+
KBS::Condition.new(:sensor, { id: :id? }) # 1000 facts
|
|
351
|
+
]
|
|
352
|
+
# Beta memory: 1 token
|
|
353
|
+
|
|
354
|
+
# Bad: General first
|
|
355
|
+
r.conditions = [
|
|
356
|
+
KBS::Condition.new(:sensor, { id: :id? }), # 1000 facts
|
|
357
|
+
KBS::Condition.new(:critical_alert, { id: :id? }) # 1 fact
|
|
358
|
+
]
|
|
359
|
+
# Beta memory: 1000 tokens
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**3. Minimize cross products:**
|
|
363
|
+
|
|
364
|
+
```ruby
|
|
365
|
+
# Bad: No shared variables between first two conditions
|
|
366
|
+
r.conditions = [
|
|
367
|
+
KBS::Condition.new(:a, { x: :v1? }),
|
|
368
|
+
KBS::Condition.new(:b, { y: :v2? }), # No :v1?!
|
|
369
|
+
KBS::Condition.new(:c, { p: :v1?, q: :v2? })
|
|
370
|
+
]
|
|
371
|
+
# Creates a × b cross product
|
|
372
|
+
|
|
373
|
+
# Good: Progressive joining
|
|
374
|
+
r.conditions = [
|
|
375
|
+
KBS::Condition.new(:a, { x: :v1? }),
|
|
376
|
+
KBS::Condition.new(:c, { p: :v1?, q: :v2? }),
|
|
377
|
+
KBS::Condition.new(:b, { y: :v2? })
|
|
378
|
+
]
|
|
379
|
+
# Each condition reduces search space
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## Common Patterns
|
|
383
|
+
|
|
384
|
+
### One-to-Many Relationship
|
|
385
|
+
|
|
386
|
+
```ruby
|
|
387
|
+
# One customer, many orders
|
|
388
|
+
r.conditions = [
|
|
389
|
+
KBS::Condition.new(:customer, {
|
|
390
|
+
id: :cust_id?,
|
|
391
|
+
status: "active"
|
|
392
|
+
}),
|
|
393
|
+
|
|
394
|
+
KBS::Condition.new(:order, {
|
|
395
|
+
customer_id: :cust_id?,
|
|
396
|
+
status: "pending"
|
|
397
|
+
})
|
|
398
|
+
]
|
|
399
|
+
|
|
400
|
+
# Fires once per pending order for active customers
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Many-to-Many Relationship
|
|
404
|
+
|
|
405
|
+
```ruby
|
|
406
|
+
# Students enrolled in courses
|
|
407
|
+
r.conditions = [
|
|
408
|
+
KBS::Condition.new(:student, { id: :student_id? }),
|
|
409
|
+
KBS::Condition.new(:enrollment, {
|
|
410
|
+
student_id: :student_id?,
|
|
411
|
+
course_id: :course_id?
|
|
412
|
+
}),
|
|
413
|
+
KBS::Condition.new(:course, { id: :course_id? })
|
|
414
|
+
]
|
|
415
|
+
|
|
416
|
+
# Fires for each student-course pair
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Hierarchical Join
|
|
420
|
+
|
|
421
|
+
```ruby
|
|
422
|
+
# Parent → Child → Grandchild
|
|
423
|
+
r.conditions = [
|
|
424
|
+
KBS::Condition.new(:category, { id: :cat_id? }),
|
|
425
|
+
KBS::Condition.new(:product, {
|
|
426
|
+
category_id: :cat_id?,
|
|
427
|
+
id: :prod_id?
|
|
428
|
+
}),
|
|
429
|
+
KBS::Condition.new(:review, {
|
|
430
|
+
product_id: :prod_id?,
|
|
431
|
+
rating: :rating?
|
|
432
|
+
})
|
|
433
|
+
]
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Debugging Bindings
|
|
437
|
+
|
|
438
|
+
### Print Bindings
|
|
439
|
+
|
|
440
|
+
```ruby
|
|
441
|
+
r.action = lambda do |facts, bindings|
|
|
442
|
+
puts "Bindings: #{bindings.inspect}"
|
|
443
|
+
puts "Facts:"
|
|
444
|
+
facts.each_with_index do |fact, i|
|
|
445
|
+
puts " #{i}: #{fact.type} #{fact.attributes}"
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Trace Join Tests
|
|
451
|
+
|
|
452
|
+
```ruby
|
|
453
|
+
class DebugJoinNode < KBS::JoinNode
|
|
454
|
+
def perform_join_tests(token, fact)
|
|
455
|
+
result = super
|
|
456
|
+
puts "Join test: #{@tests.inspect}"
|
|
457
|
+
puts " Token: #{token.inspect}"
|
|
458
|
+
puts " Fact: #{fact.inspect}"
|
|
459
|
+
puts " Result: #{result}"
|
|
460
|
+
result
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Validate Bindings
|
|
466
|
+
|
|
467
|
+
```ruby
|
|
468
|
+
r.action = lambda do |facts, bindings|
|
|
469
|
+
# Ensure expected bindings exist
|
|
470
|
+
required = [:sensor_id?, :temp?, :max?]
|
|
471
|
+
missing = required - bindings.keys
|
|
472
|
+
|
|
473
|
+
if missing.any?
|
|
474
|
+
raise "Missing bindings: #{missing}"
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
# Proceed with action
|
|
478
|
+
# ...
|
|
479
|
+
end
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## Next Steps
|
|
483
|
+
|
|
484
|
+
- **[Pattern Matching](pattern-matching.md)** - How facts match conditions
|
|
485
|
+
- **[Negation](negation.md)** - Negated conditions and binding
|
|
486
|
+
- **[Network Structure](../architecture/network-structure.md)** - How joins compile into networks
|
|
487
|
+
- **[Performance Guide](../advanced/performance.md)** - Optimizing join performance
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
*Variable binding is the glue that connects facts. Master bindings, master rule performance.*
|