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.
Files changed (26) hide show
  1. api_logic_server_cli/add_cust/add_cust.py +6 -2
  2. api_logic_server_cli/api_logic_server.py +2 -1
  3. api_logic_server_cli/database/basic_demo.sqlite +0 -0
  4. api_logic_server_cli/prototypes/base/.github/.copilot-instructions.md +228 -76
  5. api_logic_server_cli/prototypes/base/docs/training/OVERVIEW.md +64 -0
  6. api_logic_server_cli/prototypes/base/docs/training/README.md +140 -0
  7. api_logic_server_cli/prototypes/base/docs/training/genai_logic_patterns.md +443 -0
  8. api_logic_server_cli/prototypes/base/docs/training/logic_bank_api.prompt +22 -0
  9. api_logic_server_cli/prototypes/base/docs/training/logic_bank_patterns.prompt +445 -0
  10. api_logic_server_cli/prototypes/base/docs/training/probabilistic_logic.prompt +1074 -0
  11. api_logic_server_cli/prototypes/base/docs/training/probabilistic_logic_guide.md +444 -0
  12. api_logic_server_cli/prototypes/base/docs/training/probabilistic_template.py +326 -0
  13. api_logic_server_cli/prototypes/base/logic/logic_discovery/auto_discovery.py +8 -9
  14. api_logic_server_cli/prototypes/basic_demo/.github/.copilot-instructions.md +326 -142
  15. api_logic_server_cli/prototypes/basic_demo/.github/welcome.md +15 -1
  16. api_logic_server_cli/prototypes/basic_demo/customizations/database/db.sqlite +0 -0
  17. api_logic_server_cli/prototypes/basic_demo/iteration/database/db.sqlite +0 -0
  18. api_logic_server_cli/prototypes/manager/.github/.copilot-instructions.md +61 -155
  19. api_logic_server_cli/prototypes/manager/.github/welcome.md +43 -0
  20. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/.github/.copilot-instructions.md +502 -76
  21. {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.0.dist-info}/METADATA +1 -1
  22. {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.0.dist-info}/RECORD +26 -18
  23. {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.0.dist-info}/WHEEL +0 -0
  24. {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.0.dist-info}/entry_points.txt +0 -0
  25. {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.0.dist-info}/licenses/LICENSE +0 -0
  26. {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,444 @@
1
+ # Probabilistic Logic Guide - Pattern Implementation
2
+
3
+ **Scope:** Implementing probabilistic (AI-powered) logic rules in ApiLogicServer projects.
4
+ **Prerequisites:** Understanding of LogicBank deterministic rules (constraints, sums, formulas).
5
+ **Related:** See `genai_logic_patterns.md` for framework-level patterns.
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ Probabilistic logic extends deterministic rules with AI-powered decision making. The key pattern:
12
+ - **Deterministic rules:** Constraints, sums, copy - always produce same result
13
+ - **Probabilistic rules:** AI-powered formulas - results depend on data, context, external factors
14
+
15
+ **Architecture:**
16
+ ```
17
+ logic/
18
+ declare_logic.py # Main rule declarations (loads auto-discovery)
19
+ logic_discovery/ # Use case logic
20
+ check_credit.py # Deterministic + conditional AI
21
+ ai_requests/ # Reusable AI handlers
22
+ supplier_selection.py # AI handler with Request Pattern
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Pattern: Conditional Formula with AI
28
+
29
+ ### Basic Structure
30
+
31
+ ```python
32
+ def ConditionalFormula(row, old_row, logic_row):
33
+ """
34
+ Conditional logic:
35
+ - IF condition met → use deterministic calculation
36
+ - ELSE → call AI for probabilistic computation
37
+ """
38
+ if simple_case(row):
39
+ return deterministic_value(row)
40
+ else:
41
+ return probabilistic_value(row, logic_row)
42
+
43
+ Rule.formula(derive=models.MyTable.field, calling=ConditionalFormula)
44
+ ```
45
+
46
+ ### Real Example (Supplier Selection)
47
+
48
+ ```python
49
+ from logic.ai_requests.supplier_selection import get_supplier_price_from_ai
50
+
51
+ def ItemUnitPriceFromSupplier(row: models.Item, old_row, logic_row):
52
+ """
53
+ Determine Item.unit_price:
54
+ - IF Product has NO suppliers → copy from Product.unit_price (deterministic)
55
+ - IF Product has suppliers → call AI to select optimal supplier (probabilistic)
56
+ """
57
+ if row.product.count_suppliers == 0:
58
+ logic_row.log("Product has no suppliers, using product.unit_price")
59
+ return row.product.unit_price # Deterministic
60
+
61
+ # Probabilistic - AI selects best supplier
62
+ return get_supplier_price_from_ai(
63
+ row=row,
64
+ logic_row=logic_row,
65
+ candidates='product.ProductSupplierList',
66
+ optimize_for='fastest reliable delivery while keeping costs reasonable',
67
+ fallback='min:unit_cost'
68
+ )
69
+
70
+ Rule.formula(derive=models.Item.unit_price, calling=ItemUnitPriceFromSupplier)
71
+ ```
72
+
73
+ **Key Points:**
74
+ - Formula handles BOTH deterministic and probabilistic cases
75
+ - Clear condition determines which path to take
76
+ - AI handler encapsulates complexity
77
+ - Fallback strategy always provided
78
+
79
+ ---
80
+
81
+ ## Request Pattern for Audit Trail
82
+
83
+ Every AI computation creates an audit record capturing:
84
+ - Request details (what was asked)
85
+ - Response details (what AI decided)
86
+ - Reasoning (why AI made that choice)
87
+ - Timestamp (when decision was made)
88
+
89
+ ### Audit Table Structure
90
+
91
+ ```python
92
+ class SysSupplierReq(Base):
93
+ """Audit table for AI supplier selection"""
94
+ __tablename__ = 'sys_supplier_req'
95
+
96
+ id = Column(Integer, primary_key=True, autoincrement=True)
97
+ item_id = Column(ForeignKey('item.id'))
98
+ product_id = Column(ForeignKey('product.id'), nullable=False)
99
+
100
+ # AI Response
101
+ chosen_supplier_id = Column(ForeignKey('supplier.id'))
102
+ chosen_unit_price = Column(DECIMAL)
103
+ reason = Column(String)
104
+
105
+ # Metadata
106
+ request = Column(String) # Optional: stores AI prompt
107
+ created_on = Column(DateTime, server_default=func.now())
108
+
109
+ # Relationships for navigation
110
+ item = relationship("Item", back_populates="SysSupplierReqList")
111
+ product = relationship("Product", back_populates="SysSupplierReqList")
112
+ supplier = relationship("Supplier", back_populates="SysSupplierReqList")
113
+ ```
114
+
115
+ ### Implementation Pattern
116
+
117
+ **AI Handler (returns value):**
118
+ ```python
119
+ def get_supplier_price_from_ai(row, logic_row, candidates, optimize_for, fallback):
120
+ """
121
+ Creates audit record and returns computed value.
122
+ Event handler populates audit fields DURING this call.
123
+ """
124
+ # Create audit record using LogicBank triggered insert
125
+ audit_logic_row = logic_row.new_logic_row(models.SysSupplierReq)
126
+ audit_record = audit_logic_row.row
127
+ audit_logic_row.link(to_parent=logic_row)
128
+
129
+ # Set request context
130
+ audit_record.product_id = row.product_id
131
+ audit_record.item_id = row.id
132
+
133
+ # Insert triggers event handler which populates chosen_* fields
134
+ audit_logic_row.insert(reason="Supplier AI Request")
135
+
136
+ # Return value populated by event handler
137
+ return audit_record.chosen_unit_price
138
+ ```
139
+
140
+ **Event Handler (populates audit):**
141
+ ```python
142
+ def supplier_id_from_ai(row: models.SysSupplierReq, old_row, logic_row):
143
+ """
144
+ Fires DURING audit record insert.
145
+ Computes AI decision and populates audit fields.
146
+ """
147
+ if not logic_row.is_inserted():
148
+ return
149
+
150
+ # Get candidates (suppliers for this product)
151
+ suppliers = row.product.ProductSupplierList
152
+
153
+ # AI computation (or fallback)
154
+ if not has_api_key():
155
+ min_supplier = min(suppliers, key=lambda s: s.unit_cost)
156
+ else:
157
+ # Call AI service
158
+ result = call_ai_service(
159
+ candidates=suppliers,
160
+ optimize_for='fastest reliable delivery',
161
+ context=load_test_context()
162
+ )
163
+ min_supplier = result.chosen_supplier
164
+
165
+ # Populate audit fields
166
+ row.chosen_supplier_id = min_supplier.supplier_id
167
+ row.chosen_unit_price = min_supplier.unit_cost
168
+ row.reason = f"Selected supplier {min_supplier.supplier_id}: lowest cost at {min_supplier.unit_cost}"
169
+
170
+ def declare_logic():
171
+ """Auto-discovery calls this to register event handler"""
172
+ Rule.early_row_event(on_class=models.SysSupplierReq, calling=supplier_id_from_ai)
173
+ ```
174
+
175
+ **Why This Works:**
176
+ 1. Formula calls `get_supplier_price_from_ai()`
177
+ 2. Handler creates audit record using `logic_row.insert()`
178
+ 3. Insert triggers event handler `supplier_id_from_ai()`
179
+ 4. Event handler computes AI decision and populates fields
180
+ 5. Formula returns `audit_record.chosen_unit_price` (now populated)
181
+ 6. All audit details captured in database
182
+
183
+ ---
184
+
185
+ ## Integration with Deterministic Rules
186
+
187
+ Probabilistic rules work seamlessly with deterministic rules:
188
+
189
+ ```python
190
+ # Deterministic rules execute first
191
+ Rule.sum(derive=models.Order.amount_total, as_sum_of=models.Item.amount)
192
+ Rule.formula(derive=models.Item.amount, as_expression=lambda row: row.quantity * row.unit_price)
193
+ Rule.count(derive=models.Product.count_suppliers, as_count_of=models.ProductSupplier)
194
+
195
+ # Probabilistic rule depends on count_suppliers (deterministic)
196
+ def ItemUnitPriceFromSupplier(row, old_row, logic_row):
197
+ if row.product.count_suppliers == 0: # Uses deterministic count
198
+ return row.product.unit_price
199
+ return get_supplier_price_from_ai(...) # Probabilistic
200
+
201
+ Rule.formula(derive=models.Item.unit_price, calling=ItemUnitPriceFromSupplier)
202
+
203
+ # Constraint applies to final result (regardless of how computed)
204
+ Rule.constraint(validate=models.Customer,
205
+ as_condition=lambda row: row.balance <= row.credit_limit,
206
+ error_msg="Balance exceeds credit limit")
207
+ ```
208
+
209
+ **Execution Flow:**
210
+ 1. Item inserted with product_id and quantity
211
+ 2. count_suppliers computed (deterministic sum)
212
+ 3. unit_price computed (conditional: deterministic OR probabilistic)
213
+ 4. amount computed (deterministic: quantity * unit_price)
214
+ 5. amount_total computed (deterministic sum)
215
+ 6. balance updated (deterministic sum)
216
+ 7. credit_limit constraint checked (deterministic validation)
217
+
218
+ **Key Insight:** AI decision (step 3) is just one step in the chain. All other rules remain deterministic.
219
+
220
+ ---
221
+
222
+ ## Testing Probabilistic Rules
223
+
224
+ ### Test Without AI (Deterministic Path)
225
+ ```python
226
+ def test_no_suppliers_uses_product_price():
227
+ """When product has no suppliers, should use product.unit_price"""
228
+ session = create_session()
229
+
230
+ # Setup: Product with NO suppliers
231
+ product = Product(id=1, name="Test", unit_price=100.00)
232
+ product.count_suppliers = 0 # Deterministic
233
+
234
+ # Test: Insert item
235
+ item = Item(product_id=1, quantity=5)
236
+ session.add(item)
237
+ session.flush()
238
+
239
+ # Verify: Used product.unit_price (deterministic path)
240
+ assert item.unit_price == 100.00
241
+ assert item.amount == 500.00
242
+ ```
243
+
244
+ ### Test With AI (Probabilistic Path)
245
+ ```python
246
+ def test_with_suppliers_uses_ai():
247
+ """When product has suppliers, should call AI"""
248
+ session = create_session()
249
+
250
+ # Setup: Product WITH suppliers
251
+ product = Product(id=2, name="Test", unit_price=100.00)
252
+ supplier1 = Supplier(id=1, name="Fast Inc")
253
+ supplier2 = Supplier(id=2, name="Cheap Co")
254
+ ProductSupplier(product_id=2, supplier_id=1, unit_cost=110.00)
255
+ ProductSupplier(product_id=2, supplier_id=2, unit_cost=95.00)
256
+ product.count_suppliers = 2 # Deterministic
257
+
258
+ # Test: Insert item
259
+ item = Item(product_id=2, quantity=5)
260
+ session.add(item)
261
+ session.flush()
262
+
263
+ # Verify: Used AI (probabilistic path)
264
+ assert item.unit_price in [110.00, 95.00] # One of the suppliers
265
+
266
+ # Verify: Audit record created
267
+ audit = session.query(SysSupplierReq).filter_by(item_id=item.id).one()
268
+ assert audit.chosen_supplier_id in [1, 2]
269
+ assert audit.chosen_unit_price in [110.00, 95.00]
270
+ assert audit.reason is not None
271
+ ```
272
+
273
+ ### Test AI Logic Independently
274
+ ```python
275
+ @patch('logic.ai_requests.supplier_selection.call_ai_service')
276
+ def test_ai_handler_with_mock(mock_ai):
277
+ """Test AI handler in isolation"""
278
+ mock_ai.return_value = {
279
+ 'supplier_id': 2,
280
+ 'unit_cost': 95.00,
281
+ 'reason': 'Lowest cost'
282
+ }
283
+
284
+ # Create test context
285
+ row = create_test_item()
286
+ logic_row = create_test_logic_row(row)
287
+
288
+ # Call handler
289
+ result = get_supplier_price_from_ai(row, logic_row, ...)
290
+
291
+ # Verify
292
+ assert result == 95.00
293
+ assert mock_ai.called
294
+ ```
295
+
296
+ ---
297
+
298
+ ## Common Patterns
299
+
300
+ ### Pattern 1: Simple Conditional AI
301
+ ```python
302
+ def my_formula(row, old_row, logic_row):
303
+ if has_simple_answer(row):
304
+ return simple_calculation(row)
305
+ return get_ai_value(row, logic_row, ...)
306
+
307
+ Rule.formula(derive=models.MyTable.field, calling=my_formula)
308
+ ```
309
+
310
+ ### Pattern 2: AI with Fallback Strategy
311
+ ```python
312
+ def robust_formula(row, old_row, logic_row):
313
+ try:
314
+ return get_ai_value(row, logic_row, fallback='min:cost')
315
+ except AIError:
316
+ return row.default_value
317
+ ```
318
+
319
+ ### Pattern 3: Multiple AI Decisions
320
+ ```python
321
+ def complex_formula(row, old_row, logic_row):
322
+ # First decision: supplier
323
+ supplier_price = get_supplier_price_from_ai(...)
324
+
325
+ # Second decision: route
326
+ shipping_cost = get_route_cost_from_ai(...)
327
+
328
+ return supplier_price + shipping_cost
329
+ ```
330
+
331
+ ### Pattern 4: AI Influences Multiple Fields
332
+ ```python
333
+ def ai_event_populates_multiple_fields(row, old_row, logic_row):
334
+ if logic_row.is_inserted():
335
+ result = call_ai_service(...)
336
+ row.field1 = result.value1
337
+ row.field2 = result.value2
338
+ row.field3 = result.value3
339
+ row.audit_reason = result.reasoning
340
+ ```
341
+
342
+ ---
343
+
344
+ ## Best Practices
345
+
346
+ 1. **Always provide fallback:** Never let AI failure break business logic
347
+ 2. **Use conditional formulas:** Check simple cases before calling AI
348
+ 3. **Create audit records:** Capture every AI decision for observability
349
+ 4. **Test both paths:** Test deterministic path AND AI path independently
350
+ 5. **Document AI goals:** What is AI optimizing for? Make it explicit
351
+ 6. **Handle errors gracefully:** Catch exceptions, log details, use fallback
352
+ 7. **Use early_row_event:** Populate audit fields before other rules need them
353
+ 8. **Leverage relationships:** Navigate to candidates via SQLAlchemy relationships
354
+ 9. **Keep handlers reusable:** One AI handler can serve multiple use cases
355
+ 10. **Monitor audit tables:** Review AI decisions periodically for quality
356
+
357
+ ---
358
+
359
+ ## Troubleshooting
360
+
361
+ ### Issue: AI not called when expected
362
+ **Check:**
363
+ - Is conditional logic correct? (`if count > 0` vs `if count == 0`)
364
+ - Are relationships loaded? (Use `row.product.ProductSupplierList`)
365
+ - Is count_suppliers computed before formula runs?
366
+
367
+ ### Issue: Audit record empty or missing fields
368
+ **Check:**
369
+ - Is event handler registered? (Check `declare_logic()` called)
370
+ - Is event checking `logic_row.is_inserted()`?
371
+ - Is event handler using `early_row_event` (not `row_event`)?
372
+
373
+ ### Issue: `request` and `reason` fields contain generic/incomplete data
374
+ **Problem:** Audit trail lacks actionable details for debugging/compliance
375
+ **Solution:** Populate fields in AI handler (where data exists) with complete information:
376
+ ```python
377
+ # ✅ CORRECT - In AI handler, populate with full context
378
+ candidate_summary = ', '.join([f"{s.supplier.name}(${s.unit_cost})" for s in suppliers])
379
+ row.request = f"Select supplier for {product.name}: Candidates=[{candidate_summary}], World={world}"
380
+ row.reason = f"AI: {supplier_name} (${price}) - {ai_explanation}"
381
+
382
+ # ❌ WRONG - Generic constants with no business context
383
+ row.request = "Select supplier"
384
+ row.reason = "AI selection"
385
+ ```
386
+ **Key:** Include supplier names, prices, world conditions - not just IDs
387
+
388
+ ### Issue: "AttributeError: 'NoneType' object has no attribute 'product_id'" on delete
389
+ **Problem:** Early events fire on delete but `old_row` is None
390
+ **Solution:** Check `is_deleted()` FIRST before accessing `old_row`:
391
+ ```python
392
+ def my_early_event(row, old_row, logic_row):
393
+ # ✅ CORRECT - Check delete first
394
+ if logic_row.is_deleted():
395
+ return
396
+
397
+ # Now safe to access old_row
398
+ if row.product_id != old_row.product_id:
399
+ # handle change
400
+ pass
401
+ ```
402
+ **Rule:** ALL early events that access `old_row` MUST check `is_deleted()` first
403
+
404
+ ### Issue: "Session is already flushing" error
405
+ **Solution:** Use LogicBank triggered insert pattern:
406
+ ```python
407
+ # ❌ WRONG
408
+ session.add(audit)
409
+ session.flush()
410
+
411
+ # ✅ CORRECT
412
+ audit_logic_row = logic_row.new_logic_row(models.Audit)
413
+ audit_logic_row.insert(reason="AI")
414
+ ```
415
+
416
+ ### Issue: Audit record created but value not returned
417
+ **Check:**
418
+ - Does event handler populate fields BEFORE formula returns?
419
+ - Is event handler using `early_row_event` (runs during insert)?
420
+ - Does formula return value from audit record? (`return audit_record.field`)
421
+
422
+ ---
423
+
424
+ ## Summary
425
+
426
+ **Probabilistic Logic Pattern:**
427
+ 1. Conditional formula decides deterministic vs AI
428
+ 2. AI handler creates audit record using triggered insert
429
+ 3. Event handler populates audit fields with AI decision
430
+ 4. Formula returns value from audit record
431
+ 5. All audit details captured for observability
432
+
433
+ **Key Benefits:**
434
+ - Seamless integration with deterministic rules
435
+ - Full audit trail of AI decisions
436
+ - Graceful fallback when AI unavailable
437
+ - Testable at multiple levels
438
+ - Reusable AI handlers across use cases
439
+
440
+ **Remember:**
441
+ - Probabilistic rules are still rules - they participate in multi-table transactions
442
+ - AI decisions are atomic with database updates
443
+ - Audit trails enable debugging and quality monitoring
444
+ - Fallback strategies ensure business continuity