ApiLogicServer 15.4.3__py3-none-any.whl → 16.0.2__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 +229 -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 +23 -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 +1081 -0
- api_logic_server_cli/prototypes/base/docs/training/probabilistic_logic_guide.md +483 -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 +327 -141
- api_logic_server_cli/prototypes/basic_demo/.github/welcome.md +21 -7
- 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/.vscode/settings.json +1 -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.2.dist-info}/METADATA +1 -1
- {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.2.dist-info}/RECORD +27 -19
- {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.2.dist-info}/WHEEL +0 -0
- {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.2.dist-info}/entry_points.txt +0 -0
- {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.2.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Training Documentation Structure
|
|
2
|
+
|
|
3
|
+
This directory contains training materials for ApiLogicServer with GenAI integration.
|
|
4
|
+
|
|
5
|
+
## Documentation Organization
|
|
6
|
+
|
|
7
|
+
### Universal Framework Patterns
|
|
8
|
+
**File:** `genai_logic_patterns.md`
|
|
9
|
+
**Scope:** Framework-level patterns applicable to ANY ApiLogicServer project using AI
|
|
10
|
+
**Topics:**
|
|
11
|
+
- Critical imports (Rule vs RuleBank)
|
|
12
|
+
- LogicBank triggered insert pattern
|
|
13
|
+
- AI value computation architecture
|
|
14
|
+
- Auto-discovery system
|
|
15
|
+
- Formula patterns with AI
|
|
16
|
+
- Event handler patterns
|
|
17
|
+
- Testing patterns
|
|
18
|
+
- Error handling
|
|
19
|
+
- Best practices
|
|
20
|
+
|
|
21
|
+
**Use this when:** You need to understand core GenAI-LogicBank integration patterns regardless of project.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
### Probabilistic Logic Implementation
|
|
26
|
+
**File:** `probabilistic_logic_guide.md`
|
|
27
|
+
**Scope:** Implementing probabilistic (AI-powered) business rules
|
|
28
|
+
**Topics:**
|
|
29
|
+
- Conditional formulas (deterministic vs AI)
|
|
30
|
+
- Request Pattern for audit trails
|
|
31
|
+
- Integration with deterministic rules
|
|
32
|
+
- Execution flow
|
|
33
|
+
- Testing probabilistic rules
|
|
34
|
+
- Common implementation patterns
|
|
35
|
+
- Troubleshooting AI integration
|
|
36
|
+
|
|
37
|
+
**Use this when:** You're implementing AI-powered rules in your project.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
### Code Templates
|
|
42
|
+
**File:** `template_probabilistic_rules.py`
|
|
43
|
+
**Scope:** Working code examples for copy/paste
|
|
44
|
+
**Topics:**
|
|
45
|
+
- Complete working implementation
|
|
46
|
+
- Deterministic rules
|
|
47
|
+
- Conditional formula with AI
|
|
48
|
+
- Reusable AI handler pattern
|
|
49
|
+
- Event handler registration
|
|
50
|
+
|
|
51
|
+
**Use this when:** You need code to copy and adapt for your project.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Quick Navigation
|
|
56
|
+
|
|
57
|
+
**I want to...**
|
|
58
|
+
|
|
59
|
+
- **Understand LogicBank + AI patterns** → `genai_logic_patterns.md`
|
|
60
|
+
- **Implement AI rules in my project** → `probabilistic_logic_guide.md`
|
|
61
|
+
- **Copy working code** → `template_probabilistic_rules.py`
|
|
62
|
+
- **Fix import errors** → `genai_logic_patterns.md` (Section 1)
|
|
63
|
+
- **Fix "Session is already flushing"** → `genai_logic_patterns.md` (Section 2)
|
|
64
|
+
- **Add audit trails** → `probabilistic_logic_guide.md` (Section 2)
|
|
65
|
+
- **Test AI rules** → `probabilistic_logic_guide.md` (Section 4)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Other Training Materials
|
|
70
|
+
|
|
71
|
+
This directory also contains:
|
|
72
|
+
|
|
73
|
+
- **API Documentation:**
|
|
74
|
+
- `logic_bank_api.prompt` - LogicBank API reference
|
|
75
|
+
- `logic_bank_api_probabilistic.prompt` - Probabilistic extensions
|
|
76
|
+
- `logic_bank_patterns.prompt` - Pattern library
|
|
77
|
+
|
|
78
|
+
- **Admin App Documentation:**
|
|
79
|
+
- `admin_app_1_context.prompt.md` - Admin app context
|
|
80
|
+
- `admin_app_2_functionality.prompt.md` - Functionality guide
|
|
81
|
+
- `admin_app_3_architecture.prompt.md` - Architecture overview
|
|
82
|
+
- `react_map.prompt.md` - React component mapping
|
|
83
|
+
- `react_tree.prompt.md` - Component tree structure
|
|
84
|
+
|
|
85
|
+
- **Integration Guides:**
|
|
86
|
+
- `MCP_Copilot_Integration.md` - Model Context Protocol integration
|
|
87
|
+
- `testing.md` - Testing strategies
|
|
88
|
+
|
|
89
|
+
- **Refactoring History:**
|
|
90
|
+
- `REFACTORING_SUMMARY.md` - Summary of architecture changes
|
|
91
|
+
- `REFACTORING_v1.5_analysis.md` - v1.5 analysis
|
|
92
|
+
- `UPDATES_v1.4.md` - v1.4 updates
|
|
93
|
+
|
|
94
|
+
- **Examples:**
|
|
95
|
+
- `logic_example.py` - Example logic implementations
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Contributing
|
|
100
|
+
|
|
101
|
+
When adding new training materials:
|
|
102
|
+
|
|
103
|
+
1. **Choose the right file:**
|
|
104
|
+
- Universal patterns → `genai_logic_patterns.md`
|
|
105
|
+
- Implementation patterns → `probabilistic_logic_guide.md`
|
|
106
|
+
- Project-specific → Keep outside `docs/training/` folder
|
|
107
|
+
|
|
108
|
+
2. **Keep separation clear:**
|
|
109
|
+
- Don't mix framework patterns with project specifics
|
|
110
|
+
- Don't duplicate content across files
|
|
111
|
+
- Use cross-references when needed
|
|
112
|
+
|
|
113
|
+
3. **Update this README:**
|
|
114
|
+
- Add new files to appropriate section
|
|
115
|
+
- Update quick navigation if needed
|
|
116
|
+
|
|
117
|
+
4. **Provide examples:**
|
|
118
|
+
- Include code examples
|
|
119
|
+
- Show both correct and incorrect patterns
|
|
120
|
+
- Explain why patterns work
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Maintenance
|
|
125
|
+
|
|
126
|
+
**Regular reviews:**
|
|
127
|
+
- Check for outdated information
|
|
128
|
+
- Consolidate duplicate content
|
|
129
|
+
- Update cross-references
|
|
130
|
+
- Add new troubleshooting cases
|
|
131
|
+
|
|
132
|
+
**Version updates:**
|
|
133
|
+
- Update examples when LogicBank API changes
|
|
134
|
+
- Document breaking changes
|
|
135
|
+
- Provide migration guides
|
|
136
|
+
|
|
137
|
+
**Feedback:**
|
|
138
|
+
- Collect common questions
|
|
139
|
+
- Add FAQ sections
|
|
140
|
+
- Improve unclear explanations
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 1.0
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# GenAI Logic Patterns - Universal Guide
|
|
6
|
+
|
|
7
|
+
**Scope:** Framework-level patterns for integrating AI into business logic using LogicBank.
|
|
8
|
+
**Applies to:** Any ApiLogicServer project using AI-powered rules.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Critical Imports - AVOID CIRCULAR IMPORTS
|
|
13
|
+
|
|
14
|
+
### ⚠️ CRITICAL: Import LogicBank ONLY Inside Functions
|
|
15
|
+
|
|
16
|
+
**Problem:** Importing `LogicRow`, `Rule` at module level causes circular import errors during auto-discovery.
|
|
17
|
+
|
|
18
|
+
### ❌ WRONG (causes circular import):
|
|
19
|
+
```python
|
|
20
|
+
# At module level - DO NOT DO THIS
|
|
21
|
+
from logic_bank.exec_row_logic.logic_row import LogicRow
|
|
22
|
+
from logic_bank.logic_bank import Rule
|
|
23
|
+
from database import models
|
|
24
|
+
|
|
25
|
+
def declare_logic():
|
|
26
|
+
Rule.formula(...) # ❌ Circular import error
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### ✅ CORRECT (import inside functions):
|
|
30
|
+
```python
|
|
31
|
+
# At module level - only non-LogicBank imports
|
|
32
|
+
import database.models as models
|
|
33
|
+
|
|
34
|
+
def declare_logic():
|
|
35
|
+
from logic_bank.logic_bank import Rule # ✅ Import inside function
|
|
36
|
+
|
|
37
|
+
Rule.formula(derive=models.Item.unit_price, calling=my_formula)
|
|
38
|
+
|
|
39
|
+
def my_formula(row, old_row, logic_row): # ✅ No type hints on logic_row
|
|
40
|
+
from logic.ai_requests.handler import get_ai_value # ✅ Import when needed
|
|
41
|
+
return get_ai_value(row, logic_row)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Why:** LogicBank's auto-discovery imports modules during initialization. Module-level LogicBank imports create circular dependencies that fail with "cannot import name 'LogicRow' from partially initialized module".
|
|
45
|
+
|
|
46
|
+
**Pattern for All Logic Files:**
|
|
47
|
+
1. ✅ Import `database.models as models` at module level
|
|
48
|
+
2. ✅ Import `Rule` inside `declare_logic()` function
|
|
49
|
+
3. ✅ Import other logic modules inside functions where used
|
|
50
|
+
4. ✅ No type hints on `logic_row` parameters (avoid `LogicRow` import)
|
|
51
|
+
5. ✅ Import external libraries (OpenAI, yaml, etc.) inside functions that use them
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 2. LogicBank Triggered Insert Pattern
|
|
56
|
+
|
|
57
|
+
**Problem:** Cannot use `session.add()` + `session.flush()` inside formulas because formulas execute DURING SQLAlchemy's flush cycle (nested flush not allowed).
|
|
58
|
+
|
|
59
|
+
### ❌ WRONG (causes "Session is already flushing" error):
|
|
60
|
+
```python
|
|
61
|
+
def my_formula(row, old_row, logic_row):
|
|
62
|
+
audit_record = models.AuditTable(data=...)
|
|
63
|
+
logic_row.session.add(audit_record)
|
|
64
|
+
logic_row.session.flush() # ❌ ERROR: Session is already flushing
|
|
65
|
+
return audit_record.computed_value
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### ✅ CORRECT (LogicBank triggered insert):
|
|
69
|
+
```python
|
|
70
|
+
def my_formula(row, old_row, logic_row):
|
|
71
|
+
# Use LogicBank API instead of session
|
|
72
|
+
audit_logic_row = logic_row.new_logic_row(models.AuditTable)
|
|
73
|
+
audit_record = audit_logic_row.row
|
|
74
|
+
audit_logic_row.link(to_parent=logic_row)
|
|
75
|
+
audit_record.data = ...
|
|
76
|
+
audit_logic_row.insert(reason="AI computation")
|
|
77
|
+
return audit_record.computed_value # Populated by event handler
|
|
78
|
+
|
|
79
|
+
def populate_audit_fields(row, old_row, logic_row):
|
|
80
|
+
if logic_row.is_inserted():
|
|
81
|
+
# Event handler fires DURING formula execution
|
|
82
|
+
row.computed_value = ...
|
|
83
|
+
row.audit_details = ...
|
|
84
|
+
|
|
85
|
+
Rule.early_row_event(on_class=models.AuditTable, calling=populate_audit_fields)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Key Points:**
|
|
89
|
+
- `logic_row.new_logic_row()` creates row without session
|
|
90
|
+
- `logic_row.insert()` uses LogicBank's insert mechanism
|
|
91
|
+
- Event handler fires DURING formula execution
|
|
92
|
+
- Formula returns value populated by event handler
|
|
93
|
+
|
|
94
|
+
**Reference:** https://apilogicserver.github.io/Docs/Logic-Use/#in-logic
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 3. AI Value Computation Architecture
|
|
99
|
+
|
|
100
|
+
### Pattern: Reusable AI Handlers
|
|
101
|
+
|
|
102
|
+
**Structure:**
|
|
103
|
+
```
|
|
104
|
+
logic/
|
|
105
|
+
logic_discovery/ # Use case logic
|
|
106
|
+
check_credit.py # Business rule that calls AI
|
|
107
|
+
ai_requests/ # Reusable AI handlers
|
|
108
|
+
supplier_selection.py # AI handler module
|
|
109
|
+
system/ # Framework utilities
|
|
110
|
+
ai_value_computation.py # Shared utilities
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Use Case Logic (check_credit.py):**
|
|
114
|
+
```python
|
|
115
|
+
from logic.logic_discovery.ai_requests.supplier_selection import get_supplier_price_from_ai
|
|
116
|
+
|
|
117
|
+
def ItemUnitPriceFromSupplier(row, old_row, logic_row):
|
|
118
|
+
"""Conditional formula - use AI when suppliers exist"""
|
|
119
|
+
if row.product.count_suppliers == 0:
|
|
120
|
+
return row.product.unit_price # Fallback
|
|
121
|
+
|
|
122
|
+
# Call reusable AI handler (encapsulates Request Pattern)
|
|
123
|
+
logic_row.log(f"Item - Product has {row.product.count_suppliers} suppliers, invoking AI")
|
|
124
|
+
return get_supplier_price_from_ai(row=row, logic_row=logic_row)
|
|
125
|
+
|
|
126
|
+
Rule.formula(derive=models.Item.unit_price, calling=ItemUnitPriceFromSupplier)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**AI Handler (ai_requests/supplier_selection.py):**
|
|
130
|
+
```python
|
|
131
|
+
def get_supplier_price_from_ai(row, logic_row):
|
|
132
|
+
"""
|
|
133
|
+
Returns optimal supplier price using AI selection.
|
|
134
|
+
Encapsulates Request Pattern - creates audit record, triggers AI, returns computed value.
|
|
135
|
+
"""
|
|
136
|
+
# Create audit record using LogicBank triggered insert (Request Pattern)
|
|
137
|
+
supplier_req_logic_row = logic_row.new_logic_row(models.SysSupplierReq)
|
|
138
|
+
supplier_req = supplier_req_logic_row.row
|
|
139
|
+
supplier_req_logic_row.link(to_parent=logic_row)
|
|
140
|
+
supplier_req.product_id = row.product_id
|
|
141
|
+
supplier_req.item_id = row.id
|
|
142
|
+
|
|
143
|
+
# Insert triggers supplier_id_from_ai event handler which populates chosen_* fields
|
|
144
|
+
supplier_req_logic_row.insert(reason="AI supplier selection request")
|
|
145
|
+
|
|
146
|
+
# Return value populated by event handler
|
|
147
|
+
return supplier_req.chosen_unit_price
|
|
148
|
+
|
|
149
|
+
def supplier_id_from_ai(row, old_row, logic_row):
|
|
150
|
+
"""Event handler - fires on SysSupplierReq insert, populates audit fields"""
|
|
151
|
+
if not logic_row.is_inserted():
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
# Get candidates, call AI, populate row fields
|
|
155
|
+
# (Full implementation in actual file)
|
|
156
|
+
row.chosen_supplier_id = ai_result['chosen_supplier_id']
|
|
157
|
+
row.chosen_unit_price = Decimal(str(ai_result['chosen_unit_price']))
|
|
158
|
+
row.reason = ai_result['reason']
|
|
159
|
+
|
|
160
|
+
def declare_logic():
|
|
161
|
+
"""Self-register event handler for auto-discovery"""
|
|
162
|
+
from logic_bank.logic_bank import Rule
|
|
163
|
+
Rule.early_row_event(on_class=models.SysSupplierReq, calling=supplier_id_from_ai)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Benefits:**
|
|
167
|
+
- Separation of concerns (use case vs AI handler vs utilities)
|
|
168
|
+
- Reusability (multiple use cases can call same AI handler)
|
|
169
|
+
- Testability (each layer independently testable)
|
|
170
|
+
- Encapsulation (Request Pattern details hidden from use case)
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 4. Auto-Discovery System - RECURSIVE SCANNING REQUIRED
|
|
175
|
+
|
|
176
|
+
### ⚠️ CRITICAL: Auto-Discovery Must Scan Subdirectories
|
|
177
|
+
|
|
178
|
+
**Problem:** Default auto_discovery.py only scans immediate directory, not subdirectories like `ai_requests/`.
|
|
179
|
+
|
|
180
|
+
### ❌ WRONG (misses subdirectories):
|
|
181
|
+
```python
|
|
182
|
+
def discover_logic():
|
|
183
|
+
for root, dirs, files in os.walk(logic_path):
|
|
184
|
+
for file in files:
|
|
185
|
+
spec = importlib.util.spec_from_file_location("module.name", logic_path.joinpath(file))
|
|
186
|
+
# ❌ Uses logic_path instead of actual file location
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### ✅ CORRECT (recursive with proper paths):
|
|
190
|
+
```python
|
|
191
|
+
def discover_logic():
|
|
192
|
+
"""Discover additional logic in this directory and subdirectories"""
|
|
193
|
+
import os
|
|
194
|
+
logic = []
|
|
195
|
+
logic_path = Path(__file__).parent
|
|
196
|
+
for root, dirs, files in os.walk(logic_path):
|
|
197
|
+
root_path = Path(root) # ✅ Use actual subdirectory path
|
|
198
|
+
for file in files:
|
|
199
|
+
if file.endswith(".py") and not file.endswith("auto_discovery.py") and not file.startswith("__"):
|
|
200
|
+
file_path = root_path / file # ✅ Build complete path
|
|
201
|
+
spec = importlib.util.spec_from_file_location("module.name", file_path)
|
|
202
|
+
logic.append(str(file_path))
|
|
203
|
+
try:
|
|
204
|
+
each_logic_file = importlib.util.module_from_spec(spec)
|
|
205
|
+
spec.loader.exec_module(each_logic_file)
|
|
206
|
+
if hasattr(each_logic_file, 'declare_logic'):
|
|
207
|
+
each_logic_file.declare_logic()
|
|
208
|
+
except Exception as e:
|
|
209
|
+
app_logger.error(f"Error loading logic from {file_path}: {e}")
|
|
210
|
+
raise
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Key Fixes:**
|
|
214
|
+
1. ✅ Use `root_path = Path(root)` to get actual subdirectory path
|
|
215
|
+
2. ✅ Build complete path: `file_path = root_path / file`
|
|
216
|
+
3. ✅ Check for `declare_logic` existence with `hasattr()`
|
|
217
|
+
4. ✅ Skip `__init__.py` and `auto_discovery.py` files
|
|
218
|
+
5. ✅ Wrap in try/except to catch import errors with context
|
|
219
|
+
|
|
220
|
+
**Requirements:**
|
|
221
|
+
1. Module must be in `logic/logic_discovery/` or subfolder
|
|
222
|
+
2. Module must have `declare_logic()` function
|
|
223
|
+
3. Function registers rules when called
|
|
224
|
+
|
|
225
|
+
**Example Structure:**
|
|
226
|
+
```
|
|
227
|
+
logic/logic_discovery/
|
|
228
|
+
check_credit.py # ✅ Discovered
|
|
229
|
+
app_integration.py # ✅ Discovered
|
|
230
|
+
ai_requests/ # ✅ Subdirectory scanned
|
|
231
|
+
__init__.py # ✅ Skipped
|
|
232
|
+
supplier_selection.py # ✅ Discovered
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## 5. Formula Pattern with AI
|
|
238
|
+
|
|
239
|
+
**Basic Pattern:**
|
|
240
|
+
```python
|
|
241
|
+
def MyFormula(row, old_row, logic_row):
|
|
242
|
+
"""Formula computes value, optionally calling AI"""
|
|
243
|
+
if some_condition:
|
|
244
|
+
return simple_calculation()
|
|
245
|
+
else:
|
|
246
|
+
return ai_computation(...)
|
|
247
|
+
|
|
248
|
+
Rule.formula(derive=models.MyTable.my_field, calling=MyFormula)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Conditional Pattern:**
|
|
252
|
+
```python
|
|
253
|
+
def ConditionalFormula(row, old_row, logic_row):
|
|
254
|
+
"""Use default value OR call AI based on data availability"""
|
|
255
|
+
if not has_enough_data(row):
|
|
256
|
+
return row.default_value
|
|
257
|
+
return get_ai_value(row, logic_row, ...)
|
|
258
|
+
|
|
259
|
+
Rule.formula(derive=models.MyTable.computed_field, calling=ConditionalFormula)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Key Points:**
|
|
263
|
+
- Formula function returns computed value
|
|
264
|
+
- Can call reusable AI handlers
|
|
265
|
+
- AI handler encapsulates audit/request pattern
|
|
266
|
+
- Formula remains clean and readable
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 6. Event Handler Patterns
|
|
271
|
+
|
|
272
|
+
### Early Row Event (for audit population):
|
|
273
|
+
```python
|
|
274
|
+
def populate_audit_fields(row, old_row, logic_row):
|
|
275
|
+
"""Fires DURING insert, before other rules"""
|
|
276
|
+
if logic_row.is_inserted():
|
|
277
|
+
row.field1 = compute_value1()
|
|
278
|
+
row.field2 = compute_value2()
|
|
279
|
+
# NO return value needed
|
|
280
|
+
|
|
281
|
+
Rule.early_row_event(on_class=models.AuditTable, calling=populate_audit_fields)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Row Event (for side effects):
|
|
285
|
+
```python
|
|
286
|
+
def notify_on_change(row, old_row, logic_row):
|
|
287
|
+
"""Fires after all rules complete"""
|
|
288
|
+
if logic_row.nest_level == 0: # Top-level transaction
|
|
289
|
+
send_notification(row)
|
|
290
|
+
# NO return value
|
|
291
|
+
|
|
292
|
+
Rule.row_event(on_class=models.MyTable, calling=notify_on_change)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**When to use:**
|
|
296
|
+
- **early_row_event:** Populate fields that other rules depend on
|
|
297
|
+
- **row_event:** Side effects after all rules complete
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 7. Common Patterns Summary
|
|
302
|
+
|
|
303
|
+
### Pattern 1: Simple AI Formula
|
|
304
|
+
```python
|
|
305
|
+
def ai_formula(row, old_row, logic_row):
|
|
306
|
+
return call_ai_service(row.data)
|
|
307
|
+
|
|
308
|
+
Rule.formula(derive=models.MyTable.field, calling=ai_formula)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Pattern 2: Conditional AI Formula
|
|
312
|
+
```python
|
|
313
|
+
def conditional_formula(row, old_row, logic_row):
|
|
314
|
+
if condition:
|
|
315
|
+
return default_value
|
|
316
|
+
return get_ai_value(...)
|
|
317
|
+
|
|
318
|
+
Rule.formula(derive=models.MyTable.field, calling=conditional_formula)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Pattern 3: AI with Audit Trail
|
|
322
|
+
```python
|
|
323
|
+
def formula_with_audit(row, old_row, logic_row):
|
|
324
|
+
audit_logic_row = logic_row.new_logic_row(models.AuditTable)
|
|
325
|
+
audit_logic_row.link(to_parent=logic_row)
|
|
326
|
+
audit_logic_row.insert(reason="AI")
|
|
327
|
+
return audit_logic_row.row.computed_value # From event handler
|
|
328
|
+
|
|
329
|
+
def audit_event(row, old_row, logic_row):
|
|
330
|
+
if logic_row.is_inserted():
|
|
331
|
+
row.computed_value = call_ai_service(...)
|
|
332
|
+
row.details = ...
|
|
333
|
+
|
|
334
|
+
Rule.formula(derive=models.MyTable.field, calling=formula_with_audit)
|
|
335
|
+
Rule.early_row_event(on_class=models.AuditTable, calling=audit_event)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Pattern 4: Reusable AI Handler
|
|
339
|
+
```python
|
|
340
|
+
# ai_requests/my_handler.py
|
|
341
|
+
def get_ai_value(row, logic_row, ...):
|
|
342
|
+
"""Reusable across use cases"""
|
|
343
|
+
audit_logic_row = logic_row.new_logic_row(models.Audit)
|
|
344
|
+
audit_logic_row.insert(reason="AI")
|
|
345
|
+
return audit_logic_row.row.value
|
|
346
|
+
|
|
347
|
+
def declare_logic():
|
|
348
|
+
Rule.early_row_event(on_class=models.Audit, calling=populate_audit)
|
|
349
|
+
|
|
350
|
+
# check_credit.py
|
|
351
|
+
from logic.ai_requests.my_handler import get_ai_value
|
|
352
|
+
|
|
353
|
+
def my_formula(row, old_row, logic_row):
|
|
354
|
+
return get_ai_value(row, logic_row, ...)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## 8. Testing Patterns
|
|
360
|
+
|
|
361
|
+
### Test AI Handler Independently:
|
|
362
|
+
```python
|
|
363
|
+
def test_ai_handler():
|
|
364
|
+
session = create_test_session()
|
|
365
|
+
row = create_test_item()
|
|
366
|
+
logic_row = LogicRow(row, old_row=None, ins_upd_dlt="ins", nest_level=0, a_session=session, row_sets=None)
|
|
367
|
+
|
|
368
|
+
result = get_ai_value(row, logic_row, ...)
|
|
369
|
+
assert result == expected_value
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Test with Mock AI:
|
|
373
|
+
```python
|
|
374
|
+
@patch('logic.ai_requests.handler.call_ai_service')
|
|
375
|
+
def test_formula_with_mock_ai(mock_ai):
|
|
376
|
+
mock_ai.return_value = {"value": 100}
|
|
377
|
+
result = my_formula(row, old_row, logic_row)
|
|
378
|
+
assert result == 100
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## 9. Error Handling
|
|
384
|
+
|
|
385
|
+
### Graceful Fallback:
|
|
386
|
+
```python
|
|
387
|
+
def safe_ai_formula(row, old_row, logic_row):
|
|
388
|
+
try:
|
|
389
|
+
return get_ai_value(row, logic_row, ...)
|
|
390
|
+
except APIKeyMissing:
|
|
391
|
+
logic_row.log("API key missing, using fallback")
|
|
392
|
+
return row.fallback_value
|
|
393
|
+
except Exception as e:
|
|
394
|
+
logic_row.log(f"AI error: {e}, using fallback")
|
|
395
|
+
return row.fallback_value
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Audit Error Details:
|
|
399
|
+
```python
|
|
400
|
+
def ai_event_with_error_handling(row, old_row, logic_row):
|
|
401
|
+
if logic_row.is_inserted():
|
|
402
|
+
try:
|
|
403
|
+
result = call_ai_service(...)
|
|
404
|
+
row.value = result.value
|
|
405
|
+
row.status = "success"
|
|
406
|
+
except Exception as e:
|
|
407
|
+
row.value = fallback_value
|
|
408
|
+
row.status = "error"
|
|
409
|
+
row.error_message = str(e)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## 10. Best Practices
|
|
415
|
+
|
|
416
|
+
1. **Imports:** Always use `from logic_bank.logic_bank import Rule`
|
|
417
|
+
2. **Triggered Insert:** Use `logic_row.insert()` not `session.flush()` inside formulas
|
|
418
|
+
3. **Reusability:** Put AI handlers in `ai_requests/` subfolder
|
|
419
|
+
4. **Separation:** Use case logic separate from AI handlers separate from utilities
|
|
420
|
+
5. **Auto-discovery:** Every module with rules needs `declare_logic()`
|
|
421
|
+
6. **Testing:** Test AI handlers independently with mocks
|
|
422
|
+
7. **Error Handling:** Always provide fallback values
|
|
423
|
+
8. **Audit Trail:** Use Request Pattern for observability
|
|
424
|
+
9. **Documentation:** Document what AI optimizes for
|
|
425
|
+
10. **Logging:** Use `logic_row.log()` for visibility
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## Summary
|
|
430
|
+
|
|
431
|
+
**Core Pattern:**
|
|
432
|
+
- Formula calls reusable AI handler
|
|
433
|
+
- AI handler uses triggered insert to create audit record
|
|
434
|
+
- Event handler populates audit fields DURING formula
|
|
435
|
+
- Formula returns value from audit record
|
|
436
|
+
- Everything auto-discovered from `logic/logic_discovery/`
|
|
437
|
+
|
|
438
|
+
**Key Insights:**
|
|
439
|
+
- AI is just value computation with audit trail
|
|
440
|
+
- LogicBank triggered insert avoids nested flush errors
|
|
441
|
+
- Reusable handlers improve maintainability
|
|
442
|
+
- Separation of concerns enables testing
|
|
443
|
+
- Auto-discovery enables modularity
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
# LogicBank API Reference
|
|
3
|
+
# Version: 1.0.1
|
|
4
|
+
# Last Updated: November 17, 2025
|
|
5
|
+
# Description: The Logic Rosetta Stone: simplified API for creating declarative business logic rules
|
|
6
|
+
---
|
|
7
|
+
|
|
1
8
|
Here is the simplified API for LogicBank:
|
|
2
9
|
|
|
10
|
+
PREREQUISITE: For general patterns (event handler signatures, logging, request pattern, anti-patterns),
|
|
11
|
+
see docs/training/logic_bank_patterns.prompt
|
|
12
|
+
|
|
3
13
|
Translate the user prompt into a series of calls to Rule methods, described here.
|
|
4
14
|
|
|
5
15
|
Do not generate import statements.
|
|
@@ -10,6 +20,19 @@ Use only the methods provided below.
|
|
|
10
20
|
|
|
11
21
|
IMPORTANT: Keep it simple! Use the built-in Rule methods with their parameters (like if_condition) rather than creating custom functions. The Rule methods are designed to handle common patterns directly.
|
|
12
22
|
|
|
23
|
+
CRITICAL: Keep simple rules on ONE LINE (no exceptions). Goal: Visual scannability - see rule count at a glance.
|
|
24
|
+
|
|
25
|
+
✅ CORRECT - One rule per line:
|
|
26
|
+
Rule.sum(derive=Customer.balance, as_sum_of=Order.amount_total, where=lambda row: row.date_shipped is None)
|
|
27
|
+
Rule.constraint(validate=Customer, as_condition=lambda row: row.balance <= row.credit_limit, error_msg="balance exceeds credit")
|
|
28
|
+
Rule.formula(derive=Item.amount, as_expression=lambda row: row.quantity * row.unit_price)
|
|
29
|
+
|
|
30
|
+
❌ WRONG - Don't split simple rules:
|
|
31
|
+
Rule.sum(
|
|
32
|
+
derive=Customer.balance,
|
|
33
|
+
as_sum_of=Order.amount_total
|
|
34
|
+
)
|
|
35
|
+
|
|
13
36
|
|
|
14
37
|
class Rule:
|
|
15
38
|
""" Invoke these functions to declare rules """
|