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.
Files changed (27) 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 +229 -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 +23 -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 +1081 -0
  11. api_logic_server_cli/prototypes/base/docs/training/probabilistic_logic_guide.md +483 -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 +327 -141
  15. api_logic_server_cli/prototypes/basic_demo/.github/welcome.md +21 -7
  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/.vscode/settings.json +1 -0
  21. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/.github/.copilot-instructions.md +502 -76
  22. {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.2.dist-info}/METADATA +1 -1
  23. {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.2.dist-info}/RECORD +27 -19
  24. {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.2.dist-info}/WHEEL +0 -0
  25. {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.2.dist-info}/entry_points.txt +0 -0
  26. {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.2.dist-info}/licenses/LICENSE +0 -0
  27. {apilogicserver-15.4.3.dist-info → apilogicserver-16.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,483 @@
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
+ # Load test context for INPUT conditions (world_conditions)
158
+ test_context = load_test_context()
159
+ world_conditions = test_context.get('world_conditions', 'normal conditions')
160
+
161
+ # Call AI service with world conditions
162
+ result = call_ai_service(
163
+ candidates=suppliers,
164
+ optimize_for='fastest reliable delivery',
165
+ world_conditions=world_conditions # Test context provides conditions, not outputs
166
+ )
167
+ min_supplier = result.chosen_supplier
168
+
169
+ # Populate audit fields
170
+ row.chosen_supplier_id = min_supplier.supplier_id
171
+ row.chosen_unit_price = min_supplier.unit_cost
172
+ row.reason = f"Selected supplier {min_supplier.supplier_id}: lowest cost at {min_supplier.unit_cost}"
173
+
174
+ def declare_logic():
175
+ """Auto-discovery calls this to register event handler"""
176
+ Rule.early_row_event(on_class=models.SysSupplierReq, calling=supplier_id_from_ai)
177
+ ```
178
+
179
+ **Why This Works:**
180
+ 1. Formula calls `get_supplier_price_from_ai()`
181
+ 2. Handler creates audit record using `logic_row.insert()`
182
+ 3. Insert triggers event handler `supplier_id_from_ai()`
183
+ 4. Event handler computes AI decision and populates fields
184
+ 5. Formula returns `audit_record.chosen_unit_price` (now populated)
185
+ 6. All audit details captured in database
186
+
187
+ ---
188
+
189
+ ## Integration with Deterministic Rules
190
+
191
+ Probabilistic rules work seamlessly with deterministic rules:
192
+
193
+ ```python
194
+ # Deterministic rules execute first
195
+ Rule.sum(derive=models.Order.amount_total, as_sum_of=models.Item.amount)
196
+ Rule.formula(derive=models.Item.amount, as_expression=lambda row: row.quantity * row.unit_price)
197
+ Rule.count(derive=models.Product.count_suppliers, as_count_of=models.ProductSupplier)
198
+
199
+ # Probabilistic rule depends on count_suppliers (deterministic)
200
+ def ItemUnitPriceFromSupplier(row, old_row, logic_row):
201
+ if row.product.count_suppliers == 0: # Uses deterministic count
202
+ return row.product.unit_price
203
+ return get_supplier_price_from_ai(...) # Probabilistic
204
+
205
+ Rule.formula(derive=models.Item.unit_price, calling=ItemUnitPriceFromSupplier)
206
+
207
+ # Constraint applies to final result (regardless of how computed)
208
+ Rule.constraint(validate=models.Customer,
209
+ as_condition=lambda row: row.balance <= row.credit_limit,
210
+ error_msg="Balance exceeds credit limit")
211
+ ```
212
+
213
+ **Execution Flow:**
214
+ 1. Item inserted with product_id and quantity
215
+ 2. count_suppliers computed (deterministic sum)
216
+ 3. unit_price computed (conditional: deterministic OR probabilistic)
217
+ 4. amount computed (deterministic: quantity * unit_price)
218
+ 5. amount_total computed (deterministic sum)
219
+ 6. balance updated (deterministic sum)
220
+ 7. credit_limit constraint checked (deterministic validation)
221
+
222
+ **Key Insight:** AI decision (step 3) is just one step in the chain. All other rules remain deterministic.
223
+
224
+ ---
225
+
226
+ ## Testing Probabilistic Rules
227
+
228
+ ### Test Without AI (Deterministic Path)
229
+ ```python
230
+ def test_no_suppliers_uses_product_price():
231
+ """When product has no suppliers, should use product.unit_price"""
232
+ session = create_session()
233
+
234
+ # Setup: Product with NO suppliers
235
+ product = Product(id=1, name="Test", unit_price=100.00)
236
+ product.count_suppliers = 0 # Deterministic
237
+
238
+ # Test: Insert item
239
+ item = Item(product_id=1, quantity=5)
240
+ session.add(item)
241
+ session.flush()
242
+
243
+ # Verify: Used product.unit_price (deterministic path)
244
+ assert item.unit_price == 100.00
245
+ assert item.amount == 500.00
246
+ ```
247
+
248
+ ### Test With AI (Probabilistic Path)
249
+ ```python
250
+ def test_with_suppliers_uses_ai():
251
+ """When product has suppliers, should call AI"""
252
+ session = create_session()
253
+
254
+ # Setup: Product WITH suppliers
255
+ product = Product(id=2, name="Test", unit_price=100.00)
256
+ supplier1 = Supplier(id=1, name="Fast Inc")
257
+ supplier2 = Supplier(id=2, name="Cheap Co")
258
+ ProductSupplier(product_id=2, supplier_id=1, unit_cost=110.00)
259
+ ProductSupplier(product_id=2, supplier_id=2, unit_cost=95.00)
260
+ product.count_suppliers = 2 # Deterministic
261
+
262
+ # Test: Insert item
263
+ item = Item(product_id=2, quantity=5)
264
+ session.add(item)
265
+ session.flush()
266
+
267
+ # Verify: Used AI (probabilistic path)
268
+ assert item.unit_price in [110.00, 95.00] # One of the suppliers
269
+
270
+ # Verify: Audit record created
271
+ audit = session.query(SysSupplierReq).filter_by(item_id=item.id).one()
272
+ assert audit.chosen_supplier_id in [1, 2]
273
+ assert audit.chosen_unit_price in [110.00, 95.00]
274
+ assert audit.reason is not None
275
+ ```
276
+
277
+ ### Test AI Logic Independently
278
+ ```python
279
+ @patch('logic.ai_requests.supplier_selection.call_ai_service')
280
+ def test_ai_handler_with_mock(mock_ai):
281
+ """Test AI handler in isolation"""
282
+ mock_ai.return_value = {
283
+ 'supplier_id': 2,
284
+ 'unit_cost': 95.00,
285
+ 'reason': 'Lowest cost'
286
+ }
287
+
288
+ # Create test context with INPUT conditions (not predetermined outputs)
289
+ # Example: test_context = {'world_conditions': 'Suez Canal blocked'}
290
+ row = create_test_item()
291
+ logic_row = create_test_logic_row(row)
292
+
293
+ # Call handler (AI will make decision based on world conditions)
294
+ result = get_supplier_price_from_ai(row, logic_row, ...)
295
+
296
+ # Verify AI was called with proper conditions
297
+ assert result == 95.00
298
+ assert mock_ai.called
299
+ ```
300
+
301
+ ---
302
+
303
+ ## Common Patterns
304
+
305
+ ### Pattern 1: Simple Conditional AI
306
+ ```python
307
+ def my_formula(row, old_row, logic_row):
308
+ if has_simple_answer(row):
309
+ return simple_calculation(row)
310
+ return get_ai_value(row, logic_row, ...)
311
+
312
+ Rule.formula(derive=models.MyTable.field, calling=my_formula)
313
+ ```
314
+
315
+ ### Pattern 2: AI with Fallback Strategy
316
+ ```python
317
+ def robust_formula(row, old_row, logic_row):
318
+ try:
319
+ return get_ai_value(row, logic_row, fallback='min:cost')
320
+ except AIError:
321
+ return row.default_value
322
+ ```
323
+
324
+ ### Pattern 3: Multiple AI Decisions
325
+ ```python
326
+ def complex_formula(row, old_row, logic_row):
327
+ # First decision: supplier
328
+ supplier_price = get_supplier_price_from_ai(...)
329
+
330
+ # Second decision: route
331
+ shipping_cost = get_route_cost_from_ai(...)
332
+
333
+ return supplier_price + shipping_cost
334
+ ```
335
+
336
+ ### Pattern 4: AI Influences Multiple Fields
337
+ ```python
338
+ def ai_event_populates_multiple_fields(row, old_row, logic_row):
339
+ if logic_row.is_inserted():
340
+ result = call_ai_service(...)
341
+ row.field1 = result.value1
342
+ row.field2 = result.value2
343
+ row.field3 = result.value3
344
+ row.audit_reason = result.reasoning
345
+ ```
346
+
347
+ ---
348
+
349
+ ## Best Practices
350
+
351
+ 1. **Always provide fallback:** Never let AI failure break business logic
352
+ 2. **Use conditional formulas:** Check simple cases before calling AI
353
+ 3. **Create audit records:** Capture every AI decision for observability
354
+ 4. **Test both paths:** Test deterministic path AND AI path independently
355
+ 5. **Document AI goals:** What is AI optimizing for? Make it explicit
356
+ 6. **Handle errors gracefully:** Catch exceptions, log details, use fallback
357
+ 7. **Use early_row_event:** Populate audit fields before other rules need them
358
+ 8. **Leverage relationships:** Navigate to candidates via SQLAlchemy relationships
359
+ 9. **Keep handlers reusable:** One AI handler can serve multiple use cases
360
+ 10. **Monitor audit tables:** Review AI decisions periodically for quality
361
+
362
+ ---
363
+
364
+ ## Troubleshooting
365
+
366
+ ### Issue: AI not called when expected
367
+ **Check:**
368
+ - Is conditional logic correct? (`if count > 0` vs `if count == 0`)
369
+ - Are relationships loaded? (Use `row.product.ProductSupplierList`)
370
+ - Is count_suppliers computed before formula runs?
371
+
372
+ ### Issue: Audit record empty or missing fields
373
+ **Check:**
374
+ - Is event handler registered? (Check `declare_logic()` called)
375
+ - Is event checking `logic_row.is_inserted()`?
376
+ - Is event handler using `early_row_event` (not `row_event`)?
377
+
378
+ ### Issue: `request` and `reason` fields contain generic/incomplete data
379
+ **Problem:** Audit trail lacks actionable details for debugging/compliance
380
+ **Solution:** Populate fields in AI handler (where data exists) with complete information:
381
+ ```python
382
+ # ✅ CORRECT - In AI handler, populate with full context
383
+ candidate_summary = ', '.join([f"{s.supplier.name}(${s.unit_cost})" for s in suppliers])
384
+ row.request = f"Select supplier for {product.name}: Candidates=[{candidate_summary}], World={world}"
385
+ row.reason = f"AI: {supplier_name} (${price}) - {ai_explanation}"
386
+
387
+ # ❌ WRONG - Generic constants with no business context
388
+ row.request = "Select supplier"
389
+ row.reason = "AI selection"
390
+ ```
391
+ **Key:** Include supplier names, prices, world conditions - not just IDs
392
+
393
+ ### Issue: "AttributeError: 'NoneType' object has no attribute 'product_id'" on delete
394
+ **Problem:** Early events fire on delete but `old_row` is None
395
+ **Solution:** Check `is_deleted()` FIRST before accessing `old_row`:
396
+ ```python
397
+ def my_early_event(row, old_row, logic_row):
398
+ # ✅ CORRECT - Check delete first
399
+ if logic_row.is_deleted():
400
+ return
401
+
402
+ # Now safe to access old_row
403
+ if row.product_id != old_row.product_id:
404
+ # handle change
405
+ pass
406
+ ```
407
+ **Rule:** ALL early events that access `old_row` MUST check `is_deleted()` first
408
+
409
+ ### Issue: "Session is already flushing" error
410
+ **Solution:** Use LogicBank triggered insert pattern:
411
+ ```python
412
+ # ❌ WRONG
413
+ session.add(audit)
414
+ session.flush()
415
+
416
+ # ✅ CORRECT
417
+ audit_logic_row = logic_row.new_logic_row(models.Audit)
418
+ audit_logic_row.insert(reason="AI")
419
+ ```
420
+
421
+ ### Issue: Audit record created but value not returned
422
+ **Check:**
423
+ - Does event handler populate fields BEFORE formula returns?
424
+ - Is event handler using `early_row_event` (runs during insert)?
425
+ - Does formula return value from audit record? (`return audit_record.field`)
426
+
427
+ ---
428
+
429
+ ## Test Context: Input Conditions vs Output Mocking
430
+
431
+ **CRITICAL DISTINCTION:** Test context provides INPUT CONDITIONS for AI, NOT predetermined outputs.
432
+
433
+ **Purpose:**
434
+ - Test how AI responds to different scenarios (e.g., "Suez Canal blocked")
435
+ - Verify AI considers world conditions in its decision-making
436
+ - Enable repeatable testing with varying conditions
437
+
438
+ **Example Test Context (config/ai_test_context.yaml):**
439
+ ```yaml
440
+ # ✅ CORRECT - Provides input conditions
441
+ world_conditions: "Suez Canal blocked, alternate routes required"
442
+
443
+ # ❌ WRONG - Predetermines outputs (defeats AI testing)
444
+ # selected_supplier_id: 2 # Don't do this!
445
+ ```
446
+
447
+ **How It Works:**
448
+ 1. Load test context for `world_conditions`
449
+ 2. Pass conditions to AI prompt
450
+ 3. AI makes decision based on those conditions
451
+ 4. Verify AI selected appropriate supplier given the conditions
452
+
453
+ **Testing Strategy:**
454
+ - **Normal conditions:** AI should optimize for cost
455
+ - **Disrupted conditions:** AI should prioritize reliability/alternate routes
456
+ - **No API key:** System uses fallback (min cost)
457
+
458
+ **Key Insight:** Test context lets you verify AI adapts to different scenarios WITHOUT mocking the AI itself.
459
+
460
+ ---
461
+
462
+ ## Summary
463
+
464
+ **Probabilistic Logic Pattern:**
465
+ 1. Conditional formula decides deterministic vs AI
466
+ 2. AI handler creates audit record using triggered insert
467
+ 3. Event handler populates audit fields with AI decision
468
+ 4. Formula returns value from audit record
469
+ 5. All audit details captured for observability
470
+
471
+ **Key Benefits:**
472
+ - Seamless integration with deterministic rules
473
+ - Full audit trail of AI decisions
474
+ - Test context for scenario-based testing
475
+ - Graceful fallback when AI unavailable
476
+ - Testable at multiple levels
477
+ - Reusable AI handlers across use cases
478
+
479
+ **Remember:**
480
+ - Probabilistic rules are still rules - they participate in multi-table transactions
481
+ - AI decisions are atomic with database updates
482
+ - Audit trails enable debugging and quality monitoring
483
+ - Fallback strategies ensure business continuity