ApiLogicServer 15.4.3__py3-none-any.whl → 16.0.0__py3-none-any.whl
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.
- api_logic_server_cli/add_cust/add_cust.py +6 -2
- api_logic_server_cli/api_logic_server.py +2 -1
- api_logic_server_cli/database/basic_demo.sqlite +0 -0
- api_logic_server_cli/prototypes/base/.github/.copilot-instructions.md +228 -76
- api_logic_server_cli/prototypes/base/docs/training/OVERVIEW.md +64 -0
- api_logic_server_cli/prototypes/base/docs/training/README.md +140 -0
- api_logic_server_cli/prototypes/base/docs/training/genai_logic_patterns.md +443 -0
- api_logic_server_cli/prototypes/base/docs/training/logic_bank_api.prompt +22 -0
- api_logic_server_cli/prototypes/base/docs/training/logic_bank_patterns.prompt +445 -0
- api_logic_server_cli/prototypes/base/docs/training/probabilistic_logic.prompt +1074 -0
- api_logic_server_cli/prototypes/base/docs/training/probabilistic_logic_guide.md +444 -0
- api_logic_server_cli/prototypes/base/docs/training/probabilistic_template.py +326 -0
- api_logic_server_cli/prototypes/base/logic/logic_discovery/auto_discovery.py +8 -9
- api_logic_server_cli/prototypes/basic_demo/.github/.copilot-instructions.md +326 -142
- api_logic_server_cli/prototypes/basic_demo/.github/welcome.md +15 -1
- api_logic_server_cli/prototypes/basic_demo/customizations/database/db.sqlite +0 -0
- api_logic_server_cli/prototypes/basic_demo/iteration/database/db.sqlite +0 -0
- api_logic_server_cli/prototypes/manager/.github/.copilot-instructions.md +61 -155
- api_logic_server_cli/prototypes/manager/.github/welcome.md +43 -0
- api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/.github/.copilot-instructions.md +502 -76
- {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.0.dist-info}/METADATA +1 -1
- {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.0.dist-info}/RECORD +26 -18
- {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.0.dist-info}/WHEEL +0 -0
- {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.0.dist-info}/entry_points.txt +0 -0
- {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.0.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: LogicBank Patterns - The Hitchhiker's Guide
|
|
3
|
+
description: General patterns for working with LogicBank rules (deterministic and probabilistic)
|
|
4
|
+
source: Generic training for ApiLogicServer projects with GenAI integration
|
|
5
|
+
usage: AI assistants read this for general LogicBank patterns across ALL rule types
|
|
6
|
+
version: 1.0
|
|
7
|
+
date: Nov 14, 2025
|
|
8
|
+
related:
|
|
9
|
+
- logic_bank_api.prompt (deterministic rule API)
|
|
10
|
+
- logic_bank_api_probabilistic.prompt (AI/probabilistic rule API)
|
|
11
|
+
changelog:
|
|
12
|
+
- 1.0 (Nov 14, 2025): Extracted general patterns from probabilistic prompt for reuse
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# LogicBank Patterns - The Hitchhiker's Guide
|
|
16
|
+
|
|
17
|
+
This document contains general patterns for working with LogicBank rules.
|
|
18
|
+
These patterns apply to ALL rule types (deterministic and probabilistic).
|
|
19
|
+
|
|
20
|
+
For specific rule APIs, see:
|
|
21
|
+
- `docs/training/logic_bank_api.prompt` - Deterministic rules (sum, count, formula, constraint, etc.)
|
|
22
|
+
- `docs/training/logic_bank_api_probabilistic.prompt` - Probabilistic rules (AI value computation)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
=============================================================================
|
|
27
|
+
PATTERN 1: Event Handler Signature
|
|
28
|
+
=============================================================================
|
|
29
|
+
|
|
30
|
+
ALL event handlers (early_row_event, commit_row_event, row_event) receive THREE parameters.
|
|
31
|
+
|
|
32
|
+
✅ REQUIRED SIGNATURE:
|
|
33
|
+
```python
|
|
34
|
+
def my_handler(row: models.MyTable, old_row: models.MyTable, logic_row: LogicRow):
|
|
35
|
+
"""
|
|
36
|
+
Event handler signature - ALL THREE PARAMETERS REQUIRED
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
row: Current state of the row (with changes)
|
|
40
|
+
old_row: Previous state before changes (for detecting what changed)
|
|
41
|
+
logic_row: LogicBank's wrapper with rule execution methods
|
|
42
|
+
"""
|
|
43
|
+
logic_row.log(f"Processing {row.__class__.__name__}")
|
|
44
|
+
# Your logic here
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
❌ WRONG: Trying to "get" logic_row
|
|
48
|
+
```python
|
|
49
|
+
def my_handler(row: models.MyTable):
|
|
50
|
+
logic_row = LogicRow.get_logic_row(row) # ❌ This method does NOT exist!
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
❌ WRONG: Missing parameters
|
|
54
|
+
```python
|
|
55
|
+
def my_handler(row: models.MyTable, logic_row: LogicRow): # ❌ Missing old_row
|
|
56
|
+
pass
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
REGISTRATION:
|
|
60
|
+
```python
|
|
61
|
+
# Option 1: Direct registration (LogicBank passes all three params)
|
|
62
|
+
Rule.early_row_event(on_class=models.MyTable, calling=my_handler)
|
|
63
|
+
|
|
64
|
+
# Option 2: Lambda wrapper (if you need to pass additional args)
|
|
65
|
+
Rule.early_row_event(
|
|
66
|
+
on_class=models.MyTable,
|
|
67
|
+
calling=lambda row, old_row, logic_row: my_handler(row, old_row, logic_row, extra_arg)
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
WHY THREE PARAMETERS:
|
|
72
|
+
- `row` - Access current values, make changes
|
|
73
|
+
- `old_row` - Detect what changed (if row.price != old_row.price)
|
|
74
|
+
- `logic_row` - Access LogicBank methods (.log(), .new_logic_row(), .insert(), etc.)
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
=============================================================================
|
|
79
|
+
PATTERN 2: Logging with logic_row.log()
|
|
80
|
+
=============================================================================
|
|
81
|
+
|
|
82
|
+
ALWAYS use logic_row.log() for rule execution logging (not app_logger).
|
|
83
|
+
|
|
84
|
+
✅ CORRECT: Use logic_row.log()
|
|
85
|
+
```python
|
|
86
|
+
def my_handler(row: models.Item, old_row, logic_row: LogicRow):
|
|
87
|
+
logic_row.log(f"Processing Item - quantity={row.quantity}")
|
|
88
|
+
|
|
89
|
+
if row.product.count_suppliers > 0:
|
|
90
|
+
logic_row.log(f"Product has {row.product.count_suppliers} suppliers")
|
|
91
|
+
else:
|
|
92
|
+
logic_row.log("No suppliers available, using default price")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
❌ WRONG: Using app_logger for rule logic
|
|
96
|
+
```python
|
|
97
|
+
def my_handler(row: models.Item, old_row, logic_row: LogicRow):
|
|
98
|
+
app_logger.info(f"Item {row.id} - Product has suppliers") # ❌ Wrong!
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
BENEFITS of logic_row.log():
|
|
102
|
+
- ✅ Automatic indentation showing rule cascade depth
|
|
103
|
+
- ✅ Grouped with related logic execution in trace output
|
|
104
|
+
- ✅ Visible in logic trace (helps debugging)
|
|
105
|
+
- ✅ No need to import logging module
|
|
106
|
+
- ✅ Shows execution context (which rule fired)
|
|
107
|
+
|
|
108
|
+
WHEN TO USE app_logger:
|
|
109
|
+
- System startup messages
|
|
110
|
+
- Configuration loading
|
|
111
|
+
- Errors outside rule execution
|
|
112
|
+
- Non-rule application logic
|
|
113
|
+
|
|
114
|
+
EXAMPLE OUTPUT:
|
|
115
|
+
```
|
|
116
|
+
Logic Phase: ROW LOGIC (sqlalchemy before_flush) - 2025-11-14 06:19:03,372 - logic_logger - INF
|
|
117
|
+
..Item[None] {Insert - client} Id: None, order_id: 1, product_id: 6, quantity: 10, unit_price: None, amount: None row: 0x107e4a950 session: 0x107e4a8d0 ins_upd_dlt: ins - 2025-11-14 06:19:03,373 - logic_logger - INF
|
|
118
|
+
....Processing Item - quantity=10 - 2025-11-14 06:19:03,373 - logic_logger - INF
|
|
119
|
+
....Product has 2 suppliers - 2025-11-14 06:19:03,374 - logic_logger - INF
|
|
120
|
+
......Creating SysSupplierReq for AI selection - 2025-11-14 06:19:03,375 - logic_logger - INF
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Note the indentation (dots) showing call depth!
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
=============================================================================
|
|
128
|
+
PATTERN 3: Request Pattern with new_logic_row()
|
|
129
|
+
=============================================================================
|
|
130
|
+
|
|
131
|
+
Use the Request Pattern for audit trails, workflows, and AI integration.
|
|
132
|
+
|
|
133
|
+
✅ CORRECT: Pass MODEL CLASS to new_logic_row
|
|
134
|
+
```python
|
|
135
|
+
def create_audit_trail(row: models.Order, old_row, logic_row: LogicRow):
|
|
136
|
+
"""Create audit request object using Request Pattern"""
|
|
137
|
+
|
|
138
|
+
# Step 1: Create request object (pass CLASS not instance)
|
|
139
|
+
request_logic_row = logic_row.new_logic_row(models.OrderAuditReq)
|
|
140
|
+
|
|
141
|
+
# Step 2: Get the instance from .row property
|
|
142
|
+
request = request_logic_row.row
|
|
143
|
+
|
|
144
|
+
# Step 3: Set attributes on the instance
|
|
145
|
+
request.order_id = row.id
|
|
146
|
+
request.customer_id = row.customer_id
|
|
147
|
+
request.action = "order_created"
|
|
148
|
+
request.request_data = {"amount": float(row.amount_total)}
|
|
149
|
+
|
|
150
|
+
# Step 4: Insert using logic_row (triggers any events on request table)
|
|
151
|
+
request_logic_row.insert(reason="Order audit trail")
|
|
152
|
+
|
|
153
|
+
# Step 5: Access results if needed
|
|
154
|
+
logic_row.log(f"Audit created with ID {request.id}")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
❌ WRONG: Creating instance first
|
|
158
|
+
```python
|
|
159
|
+
def create_audit_trail(row: models.Order, old_row, logic_row: LogicRow):
|
|
160
|
+
# ❌ Don't create instance yourself
|
|
161
|
+
request = models.OrderAuditReq()
|
|
162
|
+
|
|
163
|
+
# ❌ This will fail with TypeError: object is not callable
|
|
164
|
+
request_logic_row = logic_row.new_logic_row(request)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
THE METHOD SIGNATURE:
|
|
168
|
+
```python
|
|
169
|
+
logic_row.new_logic_row(a_class: type) -> LogicRow
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
WHAT IT RETURNS:
|
|
173
|
+
- Returns a LogicRow wrapper (not the instance directly)
|
|
174
|
+
- Access instance via `.row` property
|
|
175
|
+
- Use returned logic_row for .insert(), .link(), etc.
|
|
176
|
+
|
|
177
|
+
WHY THIS PATTERN:
|
|
178
|
+
- LogicBank needs to track the new row in the session
|
|
179
|
+
- Enables rule execution on the new row
|
|
180
|
+
- Maintains parent-child relationships
|
|
181
|
+
- Supports cascading logic across related objects
|
|
182
|
+
|
|
183
|
+
COMMON USE CASES:
|
|
184
|
+
1. **Audit trails** - Track who did what when
|
|
185
|
+
2. **Workflows** - Create approval requests, notifications
|
|
186
|
+
3. **AI integration** - Create request objects for AI to populate
|
|
187
|
+
4. **Derived objects** - Generate summary records, reports
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
=============================================================================
|
|
192
|
+
PATTERN 4: Rule API Syntax Reference
|
|
193
|
+
=============================================================================
|
|
194
|
+
|
|
195
|
+
Always consult docs/training/logic_bank_api.prompt for complete API details.
|
|
196
|
+
|
|
197
|
+
COMMON PARAMETERS BY RULE TYPE:
|
|
198
|
+
|
|
199
|
+
Rule.sum() - NO 'calling' parameter
|
|
200
|
+
Rule.sum(derive: Column, as_sum_of: any, where: Callable = None, insert_parent: bool = False)
|
|
201
|
+
✅ Use 'where' for filtering
|
|
202
|
+
❌ NO 'calling' parameter
|
|
203
|
+
|
|
204
|
+
Rule.count() - NO 'calling' parameter
|
|
205
|
+
Rule.count(derive: Column, as_count_of: type, where: Callable = None, insert_parent: bool = False)
|
|
206
|
+
✅ Use 'where' for filtering
|
|
207
|
+
❌ NO 'calling' parameter
|
|
208
|
+
|
|
209
|
+
Rule.formula() - HAS 'calling' parameter (for functions only)
|
|
210
|
+
Rule.formula(derive: Column, as_expression: Callable = None, calling: Callable = None, no_prune: bool = False)
|
|
211
|
+
✅ Use 'as_expression' for simple expressions
|
|
212
|
+
✅ Use 'calling' for complex functions (must be callable, not bool)
|
|
213
|
+
❌ Never use calling=False or calling=True
|
|
214
|
+
|
|
215
|
+
Rule.constraint() - HAS 'calling' parameter (for functions only)
|
|
216
|
+
Rule.constraint(validate: type, as_condition: Callable = None, calling: Callable = None, error_msg: str = "")
|
|
217
|
+
✅ Use 'as_condition' for simple lambda conditions
|
|
218
|
+
✅ Use 'calling' for complex validation functions
|
|
219
|
+
❌ Never use calling=False or calling=True
|
|
220
|
+
|
|
221
|
+
Rule.copy() - NO 'calling' parameter
|
|
222
|
+
Rule.copy(derive: Column, from_parent: any)
|
|
223
|
+
|
|
224
|
+
Rule.parent_check() - NO 'calling' parameter
|
|
225
|
+
Rule.parent_check(validate: type, error_msg: str = "")
|
|
226
|
+
|
|
227
|
+
EXAMPLES:
|
|
228
|
+
|
|
229
|
+
✅ CORRECT: Rule.count with where
|
|
230
|
+
```python
|
|
231
|
+
Rule.count(
|
|
232
|
+
derive=models.Customer.unshipped_order_count,
|
|
233
|
+
as_count_of=models.Order,
|
|
234
|
+
where=lambda row: row.date_shipped is None
|
|
235
|
+
)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
❌ WRONG: Rule.count with calling
|
|
239
|
+
```python
|
|
240
|
+
Rule.count(
|
|
241
|
+
derive=models.Customer.unshipped_order_count,
|
|
242
|
+
as_count_of=models.Order,
|
|
243
|
+
calling=lambda row: row.date_shipped is None # ❌ 'calling' not valid!
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
✅ CORRECT: Rule.formula with conditional
|
|
248
|
+
```python
|
|
249
|
+
Rule.formula(
|
|
250
|
+
derive=models.Item.unit_price,
|
|
251
|
+
as_expression=lambda row: (
|
|
252
|
+
row.product.unit_price if row.product.count_suppliers == 0
|
|
253
|
+
else row.unit_price # Preserve value from event
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
❌ WRONG: Rule.formula with calling=False
|
|
259
|
+
```python
|
|
260
|
+
Rule.formula(
|
|
261
|
+
derive=models.Item.unit_price,
|
|
262
|
+
calling=False # ❌ calling must be callable or omitted!
|
|
263
|
+
)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
=============================================================================
|
|
269
|
+
PATTERN 5: Common Anti-Patterns (What NOT to Do)
|
|
270
|
+
=============================================================================
|
|
271
|
+
|
|
272
|
+
❌ DON'T: Try to "get" logic_row
|
|
273
|
+
```python
|
|
274
|
+
# This method does NOT exist
|
|
275
|
+
logic_row = LogicRow.get_logic_row(row)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
❌ DON'T: Use app_logger in rule code
|
|
279
|
+
```python
|
|
280
|
+
# Use logic_row.log() instead
|
|
281
|
+
app_logger.info("Processing item")
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
❌ DON'T: Create instances before new_logic_row
|
|
285
|
+
```python
|
|
286
|
+
# Pass CLASS to new_logic_row, not instance
|
|
287
|
+
request = models.AuditReq()
|
|
288
|
+
logic_row.new_logic_row(request) # ❌ TypeError!
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
❌ DON'T: Use wrong parameters for rules
|
|
292
|
+
```python
|
|
293
|
+
# Rule.count/sum/copy don't have 'calling'
|
|
294
|
+
Rule.count(derive=..., as_count_of=..., calling=...) # ❌ Invalid!
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
❌ DON'T: Use calling with boolean values
|
|
298
|
+
```python
|
|
299
|
+
# calling must be a function, not bool
|
|
300
|
+
Rule.formula(derive=..., calling=False) # ❌ Invalid!
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
❌ DON'T: Forget to copy AI results to target
|
|
304
|
+
```python
|
|
305
|
+
# AI populates request table, you must copy to target
|
|
306
|
+
request_logic_row.insert()
|
|
307
|
+
# ❌ Missing: row.unit_price = request.chosen_unit_price
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
❌ DON'T: Skip event handler parameters
|
|
311
|
+
```python
|
|
312
|
+
# Event handlers need all THREE parameters
|
|
313
|
+
def my_handler(row): # ❌ Missing old_row and logic_row
|
|
314
|
+
pass
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
=============================================================================
|
|
320
|
+
PATTERN 6: Type Handling for Database Fields
|
|
321
|
+
=============================================================================
|
|
322
|
+
|
|
323
|
+
When setting values in event handlers or custom code, use correct Python types for database columns.
|
|
324
|
+
|
|
325
|
+
✅ FOREIGN KEY (ID) FIELDS - Use int
|
|
326
|
+
```python
|
|
327
|
+
def my_handler(row, old_row, logic_row: LogicRow):
|
|
328
|
+
# ✅ CORRECT: Foreign keys must be int for SQLite
|
|
329
|
+
row.customer_id = 123 # int
|
|
330
|
+
row.supplier_id = int(some_value) # Ensure it's int
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
❌ WRONG: Using Decimal for foreign keys
|
|
334
|
+
```python
|
|
335
|
+
row.customer_id = Decimal('123') # ❌ SQLite error: Decimal not supported for INTEGER FK
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
✅ MONETARY FIELDS - Use Decimal for precision
|
|
339
|
+
```python
|
|
340
|
+
from decimal import Decimal
|
|
341
|
+
|
|
342
|
+
def my_handler(row, old_row, logic_row: LogicRow):
|
|
343
|
+
# ✅ CORRECT: Monetary values as Decimal
|
|
344
|
+
row.unit_price = Decimal('19.99')
|
|
345
|
+
row.amount = Decimal(str(quantity * price)) # Convert via string for precision
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
✅ PATTERN SUMMARY:
|
|
349
|
+
```python
|
|
350
|
+
# Foreign keys and IDs
|
|
351
|
+
if '_id' in field_name or field_name.endswith('_id'):
|
|
352
|
+
value = int(value) # Must be int for SQLite INTEGER columns
|
|
353
|
+
|
|
354
|
+
# Monetary fields
|
|
355
|
+
elif '_price' in field_name or '_cost' in field_name or '_amount' in field_name:
|
|
356
|
+
value = Decimal(str(value)) # Use Decimal for precision
|
|
357
|
+
|
|
358
|
+
# Other numerics
|
|
359
|
+
else:
|
|
360
|
+
value = float(value) or int(value) # Based on column type
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
WHY THIS MATTERS:
|
|
364
|
+
- SQLite INTEGER columns (foreign keys) don't support Decimal type
|
|
365
|
+
- Monetary calculations need Decimal to avoid floating-point errors
|
|
366
|
+
- Type mismatches cause "type not supported" database errors
|
|
367
|
+
|
|
368
|
+
COMMON ERRORS:
|
|
369
|
+
```python
|
|
370
|
+
# ❌ WRONG: Decimal for foreign key
|
|
371
|
+
row.order_id = Decimal('42') # Database error!
|
|
372
|
+
|
|
373
|
+
# ❌ WRONG: Float for money (precision loss)
|
|
374
|
+
row.unit_price = 19.99 # May lose precision in calculations
|
|
375
|
+
|
|
376
|
+
# ✅ CORRECT: Proper types
|
|
377
|
+
row.order_id = 42 # int for FK
|
|
378
|
+
row.unit_price = Decimal('19.99') # Decimal for money
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
=============================================================================
|
|
382
|
+
PATTERN 7: Testing and Debugging Patterns
|
|
383
|
+
=============================================================================
|
|
384
|
+
|
|
385
|
+
✅ USE logic_row.log() EXTENSIVELY during development
|
|
386
|
+
```python
|
|
387
|
+
def my_handler(row, old_row, logic_row: LogicRow):
|
|
388
|
+
logic_row.log("=== Starting my_handler ===")
|
|
389
|
+
logic_row.log(f"Row state: quantity={row.quantity}, price={row.unit_price}")
|
|
390
|
+
|
|
391
|
+
if row.quantity != old_row.quantity:
|
|
392
|
+
logic_row.log(f"Quantity changed: {old_row.quantity} -> {row.quantity}")
|
|
393
|
+
|
|
394
|
+
# ... your logic
|
|
395
|
+
|
|
396
|
+
logic_row.log(f"=== Completed my_handler, result={row.amount} ===")
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
✅ CHECK old_row to detect changes
|
|
400
|
+
```python
|
|
401
|
+
def update_handler(row, old_row, logic_row: LogicRow):
|
|
402
|
+
if row.status != old_row.status:
|
|
403
|
+
logic_row.log(f"Status changed: {old_row.status} -> {row.status}")
|
|
404
|
+
# Take action on status change
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
✅ USE logic_row.is_inserted(), is_updated(), is_deleted()
|
|
408
|
+
```python
|
|
409
|
+
def audit_handler(row, old_row, logic_row: LogicRow):
|
|
410
|
+
if logic_row.is_inserted():
|
|
411
|
+
logic_row.log("New row created")
|
|
412
|
+
elif logic_row.is_updated():
|
|
413
|
+
logic_row.log("Row updated")
|
|
414
|
+
elif logic_row.is_deleted():
|
|
415
|
+
logic_row.log("Row deleted")
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
✅ TRACE rule execution with PYTHONPATH
|
|
419
|
+
```bash
|
|
420
|
+
# Enable verbose logic logging
|
|
421
|
+
export APILOGICSERVER_VERBOSE=True
|
|
422
|
+
python api_logic_server_run.py
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
=============================================================================
|
|
428
|
+
SUMMARY: Quick Reference
|
|
429
|
+
=============================================================================
|
|
430
|
+
|
|
431
|
+
1. **Event handlers**: def handler(row, old_row, logic_row) - ALL THREE
|
|
432
|
+
2. **Logging**: Use logic_row.log() not app_logger
|
|
433
|
+
3. **Request Pattern**: new_logic_row(ModelClass) returns LogicRow with .row
|
|
434
|
+
4. **Rule APIs**: Check logic_bank_api.prompt for correct parameters
|
|
435
|
+
5. **Anti-patterns**: No get_logic_row(), no calling=False, no app_logger in rules
|
|
436
|
+
6. **Type handling**: int for FKs, Decimal for money
|
|
437
|
+
7. **Testing**: logic_row.log(), check old_row, use is_inserted/updated/deleted
|
|
438
|
+
|
|
439
|
+
For rule-specific APIs and examples:
|
|
440
|
+
- Deterministic rules → docs/training/logic_bank_api.prompt
|
|
441
|
+
- Probabilistic rules → docs/training/logic_bank_api_probabilistic.prompt
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
END OF GENERAL PATTERNS
|