ApiLogicServer 16.0.0__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/api_logic_server.py +2 -2
- api_logic_server_cli/prototypes/base/.github/.copilot-instructions.md +3 -2
- api_logic_server_cli/prototypes/base/docs/training/logic_bank_api.prompt +8 -7
- api_logic_server_cli/prototypes/base/docs/training/probabilistic_logic.prompt +45 -38
- api_logic_server_cli/prototypes/base/docs/training/probabilistic_logic_guide.md +44 -5
- api_logic_server_cli/prototypes/base/docs/training/probabilistic_template.py +27 -27
- api_logic_server_cli/prototypes/basic_demo/.github/.copilot-instructions.md +3 -1
- api_logic_server_cli/prototypes/basic_demo/.github/welcome.md +7 -7
- api_logic_server_cli/prototypes/manager/.vscode/settings.json +1 -0
- {apilogicserver-16.0.0.dist-info → apilogicserver-16.0.2.dist-info}/METADATA +1 -1
- {apilogicserver-16.0.0.dist-info → apilogicserver-16.0.2.dist-info}/RECORD +15 -15
- {apilogicserver-16.0.0.dist-info → apilogicserver-16.0.2.dist-info}/WHEEL +0 -0
- {apilogicserver-16.0.0.dist-info → apilogicserver-16.0.2.dist-info}/entry_points.txt +0 -0
- {apilogicserver-16.0.0.dist-info → apilogicserver-16.0.2.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-16.0.0.dist-info → apilogicserver-16.0.2.dist-info}/top_level.txt +0 -0
|
@@ -12,10 +12,10 @@ ApiLogicServer CLI: given a database url, create [and run] customizable ApiLogic
|
|
|
12
12
|
Called from api_logic_server_cli.py, by instantiating the ProjectRun object.
|
|
13
13
|
'''
|
|
14
14
|
|
|
15
|
-
__version__ = "16.00.
|
|
15
|
+
__version__ = "16.00.02" # last public release: 15.04.01
|
|
16
16
|
recent_changes = \
|
|
17
17
|
f'\n\nRecent Changes:\n' +\
|
|
18
|
-
"\t11/
|
|
18
|
+
"\t11/22/2025 - 16.00.02: Probabalistic logic (w/ fallback, cand*) - see basic_demo/readme_ai_mcp.md \n"\
|
|
19
19
|
"\t11/12/2025 - 15.04.03: Copilot rules on updates, tighter basic_demo startup, allow for demo_ai_mcp/then tutorial \n"\
|
|
20
20
|
"\t11/08/2025 - 15.04.00: Nat lang Copilot data access, fix Manager/Copilot startup, finding venv in project \n"\
|
|
21
21
|
"\t10/29/2025 - 15.03.00: Stable Tutor 3.3 (working, vibe transtion) \n"\
|
|
@@ -264,12 +264,13 @@ For developers: Business logic is **typically half the system** in data-centric
|
|
|
264
264
|
|
|
265
265
|
With declarative rules, you simply state business requirements. The engine handles dependencies, ordering, and optimization automatically.
|
|
266
266
|
|
|
267
|
-
LogicBank provides **44X code reduction** (5 lines vs 220+ procedural)
|
|
267
|
+
LogicBank provides **44X code reduction** (5 lines vs 220+ procedural). **At scale, this means a typical 100-table enterprise system would require ~1,000 declarative rules vs. 40,000+ lines of procedural code.** More critically, those 40,000 lines would contain bugs in foreign key change handling that the rules engine prevents by construction.
|
|
268
|
+
|
|
269
|
+
The engine provides automatic:
|
|
268
270
|
- **Dependency tracking** - listens to SQLAlchemy ORM events at attribute level
|
|
269
271
|
- **Cascading updates** - when Order.customer_id changes, adjusts BOTH old and new Customer.balance
|
|
270
272
|
- **Transitive dependencies** - Item.quantity change cascades through Item.amount → Order.amount_total → Customer.balance
|
|
271
273
|
- **Optimized execution** - uses deltas, not re-aggregation; automatic pruning
|
|
272
|
-
|
|
273
274
|
**Why the Rules Engine is a Correctness Guarantee:**
|
|
274
275
|
|
|
275
276
|
The "2 critical bugs" that even AI-generated procedural code missed:
|
|
@@ -20,18 +20,19 @@ Use only the methods provided below.
|
|
|
20
20
|
|
|
21
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.
|
|
22
22
|
|
|
23
|
-
CRITICAL:
|
|
23
|
+
CRITICAL: Keep simple rules on ONE LINE (no exceptions). Goal: Visual scannability - see rule count at a glance.
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
- Simple rules: Single line preferred, but max about 150.
|
|
27
|
-
- Complex rules: Multi-line is fine, but keep parameters together
|
|
28
|
-
- Goal: Visual scannability - see rule count at a glance
|
|
29
|
-
|
|
30
|
-
Example:
|
|
25
|
+
✅ CORRECT - One rule per line:
|
|
31
26
|
Rule.sum(derive=Customer.balance, as_sum_of=Order.amount_total, where=lambda row: row.date_shipped is None)
|
|
32
27
|
Rule.constraint(validate=Customer, as_condition=lambda row: row.balance <= row.credit_limit, error_msg="balance exceeds credit")
|
|
33
28
|
Rule.formula(derive=Item.amount, as_expression=lambda row: row.quantity * row.unit_price)
|
|
34
29
|
|
|
30
|
+
❌ WRONG - Don't split simple rules:
|
|
31
|
+
Rule.sum(
|
|
32
|
+
derive=Customer.balance,
|
|
33
|
+
as_sum_of=Order.amount_total
|
|
34
|
+
)
|
|
35
|
+
|
|
35
36
|
|
|
36
37
|
class Rule:
|
|
37
38
|
""" Invoke these functions to declare rules """
|
|
@@ -3,7 +3,7 @@ title: LogicBank Probabilistic Rules API (AI Value Computation)
|
|
|
3
3
|
description: Training document for translating natural language into probabilistic value computation rules
|
|
4
4
|
source: Generic training for ApiLogicServer projects with probabilistic rules
|
|
5
5
|
usage: AI assistants read this to generate probabilistic + deterministic rules implementations
|
|
6
|
-
version: 3.1
|
|
6
|
+
version: 3.1
|
|
7
7
|
date: November 21, 2025
|
|
8
8
|
prerequisites:
|
|
9
9
|
- docs/training/genai_logic_patterns.md (CRITICAL import patterns, auto-discovery)
|
|
@@ -399,9 +399,9 @@ def select_supplier_via_ai(row: models.SysSupplierReq, old_row, logic_row: Logic
|
|
|
399
399
|
populating AI Results: chosen_supplier_id and chosen_unit_price.
|
|
400
400
|
|
|
401
401
|
Strategy:
|
|
402
|
-
1.
|
|
403
|
-
2.
|
|
404
|
-
3. If no API key, use fallback (min cost)
|
|
402
|
+
1. Load test context for INPUT conditions (world conditions like "Suez Canal blocked")
|
|
403
|
+
2. Always try AI with those conditions
|
|
404
|
+
3. If no API key or API fails, use fallback (min cost)
|
|
405
405
|
"""
|
|
406
406
|
if not logic_row.is_inserted():
|
|
407
407
|
return
|
|
@@ -417,7 +417,7 @@ def select_supplier_via_ai(row: models.SysSupplierReq, old_row, logic_row: Logic
|
|
|
417
417
|
row.fallback_used = True
|
|
418
418
|
return
|
|
419
419
|
|
|
420
|
-
#
|
|
420
|
+
# Load test context for world conditions (not for predetermined supplier selection)
|
|
421
421
|
from pathlib import Path
|
|
422
422
|
import yaml
|
|
423
423
|
|
|
@@ -425,25 +425,17 @@ def select_supplier_via_ai(row: models.SysSupplierReq, old_row, logic_row: Logic
|
|
|
425
425
|
project_root = current_file.parent.parent.parent.parent
|
|
426
426
|
context_file = project_root / 'config' / 'ai_test_context.yaml'
|
|
427
427
|
|
|
428
|
-
|
|
429
|
-
|
|
428
|
+
test_context = {}
|
|
430
429
|
if context_file.exists():
|
|
431
430
|
with open(str(context_file), 'r') as f:
|
|
432
|
-
test_context = yaml.safe_load(f)
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
selected_supplier = next((s for s in suppliers if s.supplier_id == supplier_id), None)
|
|
436
|
-
if selected_supplier:
|
|
437
|
-
# Build candidate summary for request field
|
|
438
|
-
candidate_summary = ', '.join([f"{s.supplier.name if s.supplier else 'Unknown'}(${s.unit_cost})" for s in suppliers])
|
|
439
|
-
world = test_context.get('world_conditions', 'normal conditions')
|
|
440
|
-
row.request = f"Select supplier for {product.name}: Candidates=[{candidate_summary}], World={world}"
|
|
441
|
-
row.reason = f"TEST MODE: Selected {selected_supplier.supplier.name if selected_supplier.supplier else 'supplier'} (${selected_supplier.unit_cost}) - world: {world}"
|
|
442
|
-
logic_row.log(f"Using test context: supplier {supplier_id}")
|
|
443
|
-
row.fallback_used = False
|
|
431
|
+
test_context = yaml.safe_load(f) or {}
|
|
432
|
+
|
|
433
|
+
world_conditions = test_context.get('world_conditions', 'normal conditions')
|
|
444
434
|
|
|
445
|
-
|
|
446
|
-
|
|
435
|
+
selected_supplier = None
|
|
436
|
+
|
|
437
|
+
# Try AI (check for API key)
|
|
438
|
+
if True: # Always try AI unless no key
|
|
447
439
|
api_key = os.getenv("APILOGICSERVER_CHATGPT_APIKEY")
|
|
448
440
|
if api_key:
|
|
449
441
|
try:
|
|
@@ -453,18 +445,22 @@ def select_supplier_via_ai(row: models.SysSupplierReq, old_row, logic_row: Logic
|
|
|
453
445
|
|
|
454
446
|
client = OpenAI(api_key=api_key)
|
|
455
447
|
|
|
456
|
-
# Build candidate data for prompt
|
|
448
|
+
# Build candidate data for prompt - include ALL supplier fields for AI decision
|
|
457
449
|
candidate_data = []
|
|
458
450
|
for supplier in suppliers:
|
|
451
|
+
supplier_obj = supplier.supplier
|
|
459
452
|
candidate_data.append({
|
|
460
453
|
'supplier_id': supplier.supplier_id,
|
|
461
|
-
'supplier_name':
|
|
454
|
+
'supplier_name': supplier_obj.name if supplier_obj else 'Unknown',
|
|
455
|
+
'supplier_region': supplier_obj.region if supplier_obj else None,
|
|
456
|
+
'supplier_contact': supplier_obj.contact_name if supplier_obj else None,
|
|
457
|
+
'supplier_phone': supplier_obj.phone if supplier_obj else None,
|
|
458
|
+
'supplier_email': supplier_obj.email if supplier_obj else None,
|
|
462
459
|
'unit_cost': float(supplier.unit_cost) if supplier.unit_cost else 0.0,
|
|
463
|
-
'lead_time_days': supplier.lead_time_days if hasattr(supplier, 'lead_time_days') else None
|
|
460
|
+
'lead_time_days': supplier.lead_time_days if hasattr(supplier, 'lead_time_days') else None,
|
|
461
|
+
'supplier_part_number': supplier.supplier_part_number if hasattr(supplier, 'supplier_part_number') else None
|
|
464
462
|
})
|
|
465
463
|
|
|
466
|
-
world_conditions = test_context.get('world_conditions', 'normal conditions') if 'test_context' in locals() else 'normal conditions'
|
|
467
|
-
|
|
468
464
|
prompt = f"""
|
|
469
465
|
You are a supply chain optimization expert. Select the best supplier from the candidates below.
|
|
470
466
|
|
|
@@ -483,11 +479,14 @@ Respond with ONLY valid JSON in this exact format (no markdown, no code blocks):
|
|
|
483
479
|
}}
|
|
484
480
|
"""
|
|
485
481
|
|
|
486
|
-
# Populate request field with actual prompt summary
|
|
487
|
-
|
|
488
|
-
|
|
482
|
+
# Populate request field with actual prompt summary including key fields
|
|
483
|
+
candidate_summary = ', '.join([
|
|
484
|
+
f"{c['supplier_name']}(${c['unit_cost']}, {c['supplier_region'] or 'unknown region'}, {c['lead_time_days'] or '?'}days)"
|
|
485
|
+
for c in candidate_data
|
|
486
|
+
])
|
|
487
|
+
row.request = f"Select supplier for {product.name}: Candidates=[{candidate_summary}], World={world_conditions}"
|
|
489
488
|
|
|
490
|
-
logic_row.log(f"Calling OpenAI API with {len(candidate_data)} candidates")
|
|
489
|
+
logic_row.log(f"Calling OpenAI API with {len(candidate_data)} candidates, world conditions: {world_conditions}")
|
|
491
490
|
|
|
492
491
|
response = client.chat.completions.create(
|
|
493
492
|
model="gpt-4o-2024-08-06",
|
|
@@ -508,7 +507,7 @@ Respond with ONLY valid JSON in this exact format (no markdown, no code blocks):
|
|
|
508
507
|
selected_supplier = next((s for s in suppliers if s.supplier_id == ai_result['chosen_supplier_id']), None)
|
|
509
508
|
if selected_supplier:
|
|
510
509
|
supplier_name = selected_supplier.supplier.name if selected_supplier.supplier else 'Unknown'
|
|
511
|
-
row.reason = f"
|
|
510
|
+
row.reason = f"Selected {supplier_name} (${selected_supplier.unit_cost}) - {ai_result.get('reason', 'No reason provided')}"
|
|
512
511
|
row.fallback_used = False
|
|
513
512
|
else:
|
|
514
513
|
logic_row.log(f"AI selected invalid supplier_id {ai_result['chosen_supplier_id']}, using fallback")
|
|
@@ -582,15 +581,21 @@ def get_supplier_selection_from_ai(product_id: int, item_id: int, logic_row: Log
|
|
|
582
581
|
|
|
583
582
|
### Key Implementation Points
|
|
584
583
|
|
|
585
|
-
**Test Context
|
|
586
|
-
-
|
|
587
|
-
-
|
|
584
|
+
**Test Context Usage:**
|
|
585
|
+
- Load test context for INPUT conditions (world_conditions like "Suez Canal blocked")
|
|
586
|
+
- Test context provides CONDITIONS for AI, NOT predetermined outputs
|
|
588
587
|
- File: `config/ai_test_context.yaml`
|
|
588
|
+
- Example: `world_conditions: "Suez Canal blocked, use alternate shipping routes"`
|
|
589
|
+
|
|
590
|
+
**AI Strategy:**
|
|
591
|
+
- Always try AI if API key exists
|
|
592
|
+
- Pass world_conditions from test context to AI prompt
|
|
593
|
+
- AI makes decision based on those conditions
|
|
589
594
|
|
|
590
595
|
**Fallback Strategy:**
|
|
591
596
|
- When no suppliers: Set `fallback_used = True`, return early
|
|
592
|
-
- When no
|
|
593
|
-
- When
|
|
597
|
+
- When no API key: Use min cost fallback
|
|
598
|
+
- When API call fails: Use min cost fallback
|
|
594
599
|
|
|
595
600
|
**Type Handling:**
|
|
596
601
|
- Foreign keys (IDs): Must be `int` not `Decimal`
|
|
@@ -789,8 +794,10 @@ class Product(Base): # Has FK from SysSupplierReq.product_id
|
|
|
789
794
|
class Item(Base): # Has FK from SysSupplierReq.item_id
|
|
790
795
|
SysSupplierReqList : Mapped[List["SysSupplierReq"]] = relationship(back_populates="item")
|
|
791
796
|
|
|
792
|
-
|
|
793
|
-
|
|
797
|
+
# ✅ DO NOT add relationship to Supplier for chosen_supplier_id
|
|
798
|
+
# - This is an AI result field (not standard parent-child relationship)
|
|
799
|
+
# - Access via SysSupplierReq.chosen_supplier (unidirectional) is sufficient
|
|
800
|
+
# - Adding reverse relationship causes NoForeignKeysError
|
|
794
801
|
|
|
795
802
|
# ✅ DO NOT add relationship to ProductSupplier (no FK exists)
|
|
796
803
|
```
|
|
@@ -154,11 +154,15 @@ def supplier_id_from_ai(row: models.SysSupplierReq, old_row, logic_row):
|
|
|
154
154
|
if not has_api_key():
|
|
155
155
|
min_supplier = min(suppliers, key=lambda s: s.unit_cost)
|
|
156
156
|
else:
|
|
157
|
-
#
|
|
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
|
|
158
162
|
result = call_ai_service(
|
|
159
163
|
candidates=suppliers,
|
|
160
164
|
optimize_for='fastest reliable delivery',
|
|
161
|
-
context
|
|
165
|
+
world_conditions=world_conditions # Test context provides conditions, not outputs
|
|
162
166
|
)
|
|
163
167
|
min_supplier = result.chosen_supplier
|
|
164
168
|
|
|
@@ -281,14 +285,15 @@ def test_ai_handler_with_mock(mock_ai):
|
|
|
281
285
|
'reason': 'Lowest cost'
|
|
282
286
|
}
|
|
283
287
|
|
|
284
|
-
# Create test context
|
|
288
|
+
# Create test context with INPUT conditions (not predetermined outputs)
|
|
289
|
+
# Example: test_context = {'world_conditions': 'Suez Canal blocked'}
|
|
285
290
|
row = create_test_item()
|
|
286
291
|
logic_row = create_test_logic_row(row)
|
|
287
292
|
|
|
288
|
-
# Call handler
|
|
293
|
+
# Call handler (AI will make decision based on world conditions)
|
|
289
294
|
result = get_supplier_price_from_ai(row, logic_row, ...)
|
|
290
295
|
|
|
291
|
-
# Verify
|
|
296
|
+
# Verify AI was called with proper conditions
|
|
292
297
|
assert result == 95.00
|
|
293
298
|
assert mock_ai.called
|
|
294
299
|
```
|
|
@@ -421,6 +426,39 @@ audit_logic_row.insert(reason="AI")
|
|
|
421
426
|
|
|
422
427
|
---
|
|
423
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
|
+
|
|
424
462
|
## Summary
|
|
425
463
|
|
|
426
464
|
**Probabilistic Logic Pattern:**
|
|
@@ -433,6 +471,7 @@ audit_logic_row.insert(reason="AI")
|
|
|
433
471
|
**Key Benefits:**
|
|
434
472
|
- Seamless integration with deterministic rules
|
|
435
473
|
- Full audit trail of AI decisions
|
|
474
|
+
- Test context for scenario-based testing
|
|
436
475
|
- Graceful fallback when AI unavailable
|
|
437
476
|
- Testable at multiple levels
|
|
438
477
|
- Reusable AI handlers across use cases
|
|
@@ -5,7 +5,7 @@ This template provides a clean reference implementation for AI value computation
|
|
|
5
5
|
alongside deterministic rules, using the Request Pattern with early events.
|
|
6
6
|
|
|
7
7
|
Pattern: Early event with wrapper function that returns populated request object
|
|
8
|
-
version: 3.
|
|
8
|
+
version: 3.0
|
|
9
9
|
date: November 21, 2025
|
|
10
10
|
source: docs/training/probabilistic_template.py
|
|
11
11
|
|
|
@@ -136,31 +136,24 @@ def select_supplier_via_ai(row: models.SysSupplierReq, old_row, logic_row: Logic
|
|
|
136
136
|
row.fallback_used = True
|
|
137
137
|
return
|
|
138
138
|
|
|
139
|
-
#
|
|
139
|
+
# Load test context for world conditions (not for predetermined supplier selection)
|
|
140
140
|
from pathlib import Path
|
|
141
141
|
import yaml
|
|
142
142
|
|
|
143
143
|
config_dir = Path(__file__).resolve().parent.parent.parent.parent / 'config'
|
|
144
144
|
context_file = config_dir / 'ai_test_context.yaml'
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
test_context = {}
|
|
148
147
|
if context_file.exists():
|
|
149
148
|
with open(str(context_file), 'r') as f:
|
|
150
|
-
test_context = yaml.safe_load(f)
|
|
151
|
-
if test_context and 'selected_supplier_id' in test_context:
|
|
152
|
-
supplier_id = test_context['selected_supplier_id']
|
|
153
|
-
selected_supplier = next((s for s in suppliers if s.supplier_id == supplier_id), None)
|
|
154
|
-
if selected_supplier:
|
|
155
|
-
candidate_summary = ', '.join([f"{s.supplier.name if s.supplier else 'Unknown'}(${s.unit_cost})" for s in suppliers])
|
|
156
|
-
world = test_context.get('world_conditions', 'normal conditions')
|
|
157
|
-
row.request = f"Select supplier for {product.name}: Candidates=[{candidate_summary}], World={world}"
|
|
158
|
-
row.reason = f"TEST MODE: Selected {selected_supplier.supplier.name if selected_supplier.supplier else 'supplier'} (${selected_supplier.unit_cost}) - world: {world}"
|
|
159
|
-
logic_row.log(f"Using test context: supplier {supplier_id}")
|
|
160
|
-
row.fallback_used = False
|
|
149
|
+
test_context = yaml.safe_load(f) or {}
|
|
161
150
|
|
|
162
|
-
|
|
163
|
-
|
|
151
|
+
world_conditions = test_context.get('world_conditions', 'normal conditions')
|
|
152
|
+
|
|
153
|
+
selected_supplier = None
|
|
154
|
+
|
|
155
|
+
# Try AI (check for API key)
|
|
156
|
+
if True: # Always try AI unless no key
|
|
164
157
|
api_key = os.getenv("APILOGICSERVER_CHATGPT_APIKEY")
|
|
165
158
|
if api_key:
|
|
166
159
|
try:
|
|
@@ -170,18 +163,22 @@ def select_supplier_via_ai(row: models.SysSupplierReq, old_row, logic_row: Logic
|
|
|
170
163
|
|
|
171
164
|
client = OpenAI(api_key=api_key)
|
|
172
165
|
|
|
173
|
-
# Build candidate data for prompt
|
|
166
|
+
# Build candidate data for prompt - include ALL supplier fields for AI decision
|
|
174
167
|
candidate_data = []
|
|
175
168
|
for supplier in suppliers:
|
|
169
|
+
supplier_obj = supplier.supplier
|
|
176
170
|
candidate_data.append({
|
|
177
171
|
'supplier_id': supplier.supplier_id,
|
|
178
|
-
'supplier_name':
|
|
172
|
+
'supplier_name': supplier_obj.name if supplier_obj else 'Unknown',
|
|
173
|
+
'supplier_region': supplier_obj.region if supplier_obj else None,
|
|
174
|
+
'supplier_contact': supplier_obj.contact_name if supplier_obj else None,
|
|
175
|
+
'supplier_phone': supplier_obj.phone if supplier_obj else None,
|
|
176
|
+
'supplier_email': supplier_obj.email if supplier_obj else None,
|
|
179
177
|
'unit_cost': float(supplier.unit_cost) if supplier.unit_cost else 0.0,
|
|
180
|
-
'lead_time_days': supplier.lead_time_days if hasattr(supplier, 'lead_time_days') else None
|
|
178
|
+
'lead_time_days': supplier.lead_time_days if hasattr(supplier, 'lead_time_days') else None,
|
|
179
|
+
'supplier_part_number': supplier.supplier_part_number if hasattr(supplier, 'supplier_part_number') else None
|
|
181
180
|
})
|
|
182
181
|
|
|
183
|
-
world_conditions = test_context.get('world_conditions', 'normal conditions') if 'test_context' in locals() else 'normal conditions'
|
|
184
|
-
|
|
185
182
|
prompt = f"""
|
|
186
183
|
You are a supply chain optimization expert. Select the best supplier from the candidates below.
|
|
187
184
|
|
|
@@ -200,11 +197,14 @@ Respond with ONLY valid JSON in this exact format (no markdown, no code blocks):
|
|
|
200
197
|
}}
|
|
201
198
|
"""
|
|
202
199
|
|
|
203
|
-
# Populate request field with actual prompt summary
|
|
204
|
-
|
|
205
|
-
|
|
200
|
+
# Populate request field with actual prompt summary including key fields
|
|
201
|
+
candidate_summary = ', '.join([
|
|
202
|
+
f"{c['supplier_name']}(${c['unit_cost']}, {c['supplier_region'] or 'unknown region'}, {c['lead_time_days'] or '?'}days)"
|
|
203
|
+
for c in candidate_data
|
|
204
|
+
])
|
|
205
|
+
row.request = f"Select supplier for {product.name}: Candidates=[{candidate_summary}], World={world_conditions}"
|
|
206
206
|
|
|
207
|
-
logic_row.log(f"Calling OpenAI API with {len(candidate_data)} candidates")
|
|
207
|
+
logic_row.log(f"Calling OpenAI API with {len(candidate_data)} candidates, world conditions: {world_conditions}")
|
|
208
208
|
|
|
209
209
|
response = client.chat.completions.create(
|
|
210
210
|
model="gpt-4o-2024-08-06",
|
|
@@ -225,7 +225,7 @@ Respond with ONLY valid JSON in this exact format (no markdown, no code blocks):
|
|
|
225
225
|
selected_supplier = next((s for s in suppliers if s.supplier_id == ai_result['chosen_supplier_id']), None)
|
|
226
226
|
if selected_supplier:
|
|
227
227
|
supplier_name = selected_supplier.supplier.name if selected_supplier.supplier else 'Unknown'
|
|
228
|
-
row.reason = f"
|
|
228
|
+
row.reason = f"Selected {supplier_name} (${selected_supplier.unit_cost}) - {ai_result.get('reason', 'No reason provided')}"
|
|
229
229
|
row.fallback_used = False
|
|
230
230
|
else:
|
|
231
231
|
logic_row.log(f"AI selected invalid supplier_id {ai_result['chosen_supplier_id']}, using fallback")
|
|
@@ -312,7 +312,9 @@ For developers: Business logic is **typically half the system** in data-centric
|
|
|
312
312
|
|
|
313
313
|
With declarative rules, you simply state business requirements. The engine handles dependencies, ordering, and optimization automatically.
|
|
314
314
|
|
|
315
|
-
LogicBank provides **44X code reduction** (5 lines vs 220+ procedural)
|
|
315
|
+
LogicBank provides **44X code reduction** (5 lines vs 220+ procedural). **At scale, this means a typical 100-table enterprise system would require ~1,000 declarative rules vs. 40,000+ lines of procedural code.** More critically, those 40,000 lines would contain bugs in foreign key change handling that the rules engine prevents by construction.
|
|
316
|
+
|
|
317
|
+
The engine provides automatic:
|
|
316
318
|
- **Dependency tracking** - listens to SQLAlchemy ORM events at attribute level
|
|
317
319
|
- **Cascading updates** - when Order.customer_id changes, adjusts BOTH old and new Customer.balance
|
|
318
320
|
- **Transitive dependencies** - Item.quantity change cascades through Item.amount → Order.amount_total → Customer.balance
|
|
@@ -3,25 +3,25 @@ use: welcome for basic_demo - please start the tour
|
|
|
3
3
|
instuctions: copy api_logic_server_cli/prototypes/base/.github/.copilot-instructions.md, then paste as the Welcome section
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
## Welcome
|
|
7
|
-
|
|
8
|
-
**Welcome! This is your basic_demo project.**
|
|
6
|
+
## Welcome to the `basic_demo` project
|
|
9
7
|
|
|
10
8
|
This is a complete, working microservice auto-generated from a database schema - **uncustomized**, so you can see what to expect when you create projects from your own databases.
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
Created from database schema introspection:
|
|
13
11
|
- **Admin Web Application** - Live at `http://localhost:5656`
|
|
14
|
-
- **REST API Endpoints** - All database tables exposed at `/api/*`
|
|
12
|
+
- **REST API Endpoints** - All database tables exposed at MCP discoverable `/api/*`
|
|
15
13
|
- **Database Models** - Complete SQLAlchemy ORM in `database/models.py`
|
|
16
14
|
- **Authentication System** - JWT-based auth framework
|
|
17
15
|
- **Business Logic Engine** - Declarative rules system
|
|
18
16
|
|
|
19
17
|
---
|
|
20
18
|
|
|
21
|
-
**
|
|
19
|
+
**Ready to explore?** Just say me "guide me through" to begin the interactive tutorial
|
|
22
20
|
|
|
21
|
+
* Brief exploration of what's already created
|
|
22
|
+
* Customizing logic, security, and the API
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
---
|
|
25
25
|
|
|
26
26
|
**Or, self-demo?** Open `readme_ai_mcp.md` to explore logic, ai and mcp.
|
|
27
27
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
api_logic_server_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
api_logic_server_cli/api_logic_server.py,sha256=
|
|
2
|
+
api_logic_server_cli/api_logic_server.py,sha256=1f9EE8Q7qgEXx1rsGggnC_5rfOfIijIhHuS42jHPONM,105173
|
|
3
3
|
api_logic_server_cli/api_logic_server_info.yaml,sha256=wzqFVXFXtbCPMFWmDG5k_cP4efr7YAhyhWIlYdpKGRc,132
|
|
4
4
|
api_logic_server_cli/cli.py,sha256=xAqTOhq-OnXU2HEQgzsGC9yKrGcAgKUt_8b9U2bV5No,87831
|
|
5
5
|
api_logic_server_cli/cli_args_base.py,sha256=7cVM6BeizwttYAwUu1FUyuLuvWufvgt0TFeA8FI6tu0,3304
|
|
@@ -423,7 +423,7 @@ api_logic_server_cli/prototypes/base/.devcontainer-option/For_VSCode.dockerfile,
|
|
|
423
423
|
api_logic_server_cli/prototypes/base/.devcontainer-option/devcontainer.json,sha256=tk-mGd4XdmbpKUqUeGmcPMzX3RDc6am9-de8c-rFmSo,2361
|
|
424
424
|
api_logic_server_cli/prototypes/base/.devcontainer-option/readme.md,sha256=-sSneMDne1fqEoox2hXUGmoO8ewgi34y7lJwGTidSpY,104
|
|
425
425
|
api_logic_server_cli/prototypes/base/.devcontainer-option/setup.sh,sha256=pOvGjZ7jgRQzFkD93mNICmcC2y66Dexrq4bCnSSVwtU,310
|
|
426
|
-
api_logic_server_cli/prototypes/base/.github/.copilot-instructions.md,sha256
|
|
426
|
+
api_logic_server_cli/prototypes/base/.github/.copilot-instructions.md,sha256=3toZhOMxjmjr5jvGfSpqDU4kQGRMWAAg1UQIU0SZCvA,50293
|
|
427
427
|
api_logic_server_cli/prototypes/base/.idea/runConfigurations/ApiLogicServer.xml,sha256=eFzhe9NH-VNjcPWbPsRQy5o-MugJR9IWklA1Fo8wtYg,1127
|
|
428
428
|
api_logic_server_cli/prototypes/base/.idea/runConfigurations/Report_Behave_Logic.xml,sha256=I3jlEf-TPzc-1NY843v6AcQIQ8QJD3z9KvxTYSZWMtY,1306
|
|
429
429
|
api_logic_server_cli/prototypes/base/.idea/runConfigurations/Run_Behave.xml,sha256=CTzF0P4w7o4FzOi-eSpru0HczSEGtJsKqkQ7VWRyxPc,1196
|
|
@@ -533,12 +533,12 @@ api_logic_server_cli/prototypes/base/docs/training/admin_app_1_context.prompt.md
|
|
|
533
533
|
api_logic_server_cli/prototypes/base/docs/training/admin_app_2_functionality.prompt.md,sha256=iWMoAkETeQjWWPrNj1AcI4HFGLlgS0-HP9oBYXhdTNI,1705
|
|
534
534
|
api_logic_server_cli/prototypes/base/docs/training/admin_app_3_architecture.prompt.md,sha256=5KtRBihPbxTQEvLJ51w104Z0HfDGFEmQc3ysek6EsXA,925
|
|
535
535
|
api_logic_server_cli/prototypes/base/docs/training/genai_logic_patterns.md,sha256=STjzKjziXvsWd7hVLn3zBQLVPJi5II25IDCI8-IMYcs,14666
|
|
536
|
-
api_logic_server_cli/prototypes/base/docs/training/logic_bank_api.prompt,sha256=
|
|
536
|
+
api_logic_server_cli/prototypes/base/docs/training/logic_bank_api.prompt,sha256=acTiWoigb7BiajAHskqgh22wLy9fsW7WdAwynAe5ji0,17019
|
|
537
537
|
api_logic_server_cli/prototypes/base/docs/training/logic_bank_patterns.prompt,sha256=qNdA0efdlZECT6DiL8o1Iw-pPPDa1KVRrtKV84x8KJA,14850
|
|
538
538
|
api_logic_server_cli/prototypes/base/docs/training/logic_example.py,sha256=yAot6uHy1gu94ufeYDx3f0iJ1_czsDywSAdm_yu4E2o,1325
|
|
539
|
-
api_logic_server_cli/prototypes/base/docs/training/probabilistic_logic.prompt,sha256=
|
|
540
|
-
api_logic_server_cli/prototypes/base/docs/training/probabilistic_logic_guide.md,sha256=
|
|
541
|
-
api_logic_server_cli/prototypes/base/docs/training/probabilistic_template.py,sha256=
|
|
539
|
+
api_logic_server_cli/prototypes/base/docs/training/probabilistic_logic.prompt,sha256=xu06ynvBbp-KkCEqMI5h_Gn4k95dZIDUdQfI6gjRFR8,42549
|
|
540
|
+
api_logic_server_cli/prototypes/base/docs/training/probabilistic_logic_guide.md,sha256=ZPV7kWjo2S6lM2M_1aesh9WtFD0ME2tyr3HUCA4OEsE,16618
|
|
541
|
+
api_logic_server_cli/prototypes/base/docs/training/probabilistic_template.py,sha256=V9zldqIpcs2CR2ysy6ZoVab7FqzeSkb9b60aanGt97c,14204
|
|
542
542
|
api_logic_server_cli/prototypes/base/docs/training/react_map.prompt.md,sha256=8B9bDjk4sL1t5LzYLKjuf78UPDmhj9assRZTIOvlwN4,891
|
|
543
543
|
api_logic_server_cli/prototypes/base/docs/training/react_tree.prompt.md,sha256=Eoi4Q3H4x-PQOjonUjQ1o6xkiFtcEA_hg8tuFP-qk80,652
|
|
544
544
|
api_logic_server_cli/prototypes/base/docs/training/readme_training.md,sha256=EilDTYaDPoGCD9ef5Efs8eDn8ZNQTgGN7_QnJ4LrO2s,120
|
|
@@ -628,8 +628,8 @@ api_logic_server_cli/prototypes/base/venv_setup/venv.sh,sha256=aWX9fa8fe6aO9ifBI
|
|
|
628
628
|
api_logic_server_cli/prototypes/basic_demo/_config.yml,sha256=KIUQQpjgj7hP_Z2Fksq90E52UnbKnyom-v9L_eIfqZo,170
|
|
629
629
|
api_logic_server_cli/prototypes/basic_demo/readme.md,sha256=Ii0WojbHMHpg6bgMlg9WyadzXVZePM2Nk89kmKHuGTM,18722
|
|
630
630
|
api_logic_server_cli/prototypes/basic_demo/tutor.md,sha256=nlIkqcqkVXBDe3Rktcr4puZxrrTFDl1s_Id8nA5GwA4,45516
|
|
631
|
-
api_logic_server_cli/prototypes/basic_demo/.github/.copilot-instructions.md,sha256=
|
|
632
|
-
api_logic_server_cli/prototypes/basic_demo/.github/welcome.md,sha256=
|
|
631
|
+
api_logic_server_cli/prototypes/basic_demo/.github/.copilot-instructions.md,sha256=F87jmsFW7M88z8Dwcy418p3SFfbU9xbmEuzqu3Wwso0,51234
|
|
632
|
+
api_logic_server_cli/prototypes/basic_demo/.github/welcome.md,sha256=c-lwKq6JDiLwMzqoRnn3bPX0GTYop9lt4mBAkfYbq6w,1553
|
|
633
633
|
api_logic_server_cli/prototypes/basic_demo/_layouts/redirect.html,sha256=-0kMPGYI88fb787IzYmdi7ySZUhgpUlP0vodrg8-NRM,457
|
|
634
634
|
api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/openapi.py,sha256=kLQ7Fn1J7tzuNJHBXF2AiwtzvQ-0JxJ6z-MfFryAtLk,3887
|
|
635
635
|
api_logic_server_cli/prototypes/basic_demo/customizations/config/default.env,sha256=-rjXJrjR4vjMr9YCVYVchaJw7qMBlbvQ3KfR_wri_XM,412
|
|
@@ -816,7 +816,7 @@ api_logic_server_cli/prototypes/manager/.github/.copilot-instructions.md,sha256=
|
|
|
816
816
|
api_logic_server_cli/prototypes/manager/.github/sync-to-dev-src.sh,sha256=hZbc5fJXnYiLWvN-OcQzYRsPBESmt6B9y581CczuwHM,1915
|
|
817
817
|
api_logic_server_cli/prototypes/manager/.github/welcome.md,sha256=9Wtufrx0wRf_5jgne8K9T6Sntm2yhyR3bnFKL_ch_0M,1841
|
|
818
818
|
api_logic_server_cli/prototypes/manager/.vscode/launch.json,sha256=B9NaDoTvJsXg1qeEuhG3RdRk4M2RpnpHBW6b8oXrBn4,33551
|
|
819
|
-
api_logic_server_cli/prototypes/manager/.vscode/settings.json,sha256=
|
|
819
|
+
api_logic_server_cli/prototypes/manager/.vscode/settings.json,sha256=Ceq5Z34iC6hXNoPLahFHKAn39wjdh2VZqshkWAOXu9g,660
|
|
820
820
|
api_logic_server_cli/prototypes/manager/samples/readme_samples.md,sha256=qSX2SpJnqK9PTu1quEw3GHJvQLNfCaJ-Fr3ljn6_MpE,3632
|
|
821
821
|
api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/.env,sha256=VCYAc9rxtOuDpv4Og6QwVV8bhzipEGu4sf--kI4Lq5k,355
|
|
822
822
|
api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/.gitignore,sha256=PAO98cVvjgAL_mvXCMS_Vfk7bT2Vd1-j9a8_nB2qxqs,190
|
|
@@ -2872,9 +2872,9 @@ api_logic_server_cli/tools/mini_skel/database/system/SAFRSBaseX.py,sha256=p8C7AF
|
|
|
2872
2872
|
api_logic_server_cli/tools/mini_skel/database/system/TestDataBase.py,sha256=U02SYqThsbY5g3DX7XGaiMxjZBuOpzvtPS6RfI1WQFg,371
|
|
2873
2873
|
api_logic_server_cli/tools/mini_skel/logic/declare_logic.py,sha256=fTrlHyqMeZsw_TyEXFa1VlYBL7fzjZab5ONSXO7aApo,175
|
|
2874
2874
|
api_logic_server_cli/tools/mini_skel/logic/load_verify_rules.py,sha256=Rr5bySJpYCZmNPF2h-phcPJ53nAOPcT_ohZpCD93-a0,7530
|
|
2875
|
-
apilogicserver-16.0.
|
|
2876
|
-
apilogicserver-16.0.
|
|
2877
|
-
apilogicserver-16.0.
|
|
2878
|
-
apilogicserver-16.0.
|
|
2879
|
-
apilogicserver-16.0.
|
|
2880
|
-
apilogicserver-16.0.
|
|
2875
|
+
apilogicserver-16.0.2.dist-info/licenses/LICENSE,sha256=67BS7VC-Z8GpaR3wijngQJkHWV04qJrwQArVgn9ldoI,1485
|
|
2876
|
+
apilogicserver-16.0.2.dist-info/METADATA,sha256=Tir3NY5PD7F8wydkj02FC3bT9opFGavrtba7-RfcEfg,26461
|
|
2877
|
+
apilogicserver-16.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
2878
|
+
apilogicserver-16.0.2.dist-info/entry_points.txt,sha256=W9EVNvf09h8n6rJChmVj2gzxVQ6BXXZa2x3wri0lFGc,259
|
|
2879
|
+
apilogicserver-16.0.2.dist-info/top_level.txt,sha256=-r0AT_GEApleihg-jIh0OMvzzc0BO1RuhhOpE91H5qI,21
|
|
2880
|
+
apilogicserver-16.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|