Files
bakery-ia/shared/demo/fixtures/professional/fix_procurement_structure.py

209 lines
6.7 KiB
Python

#!/usr/bin/env python3
"""
Fix Procurement Data Structure and Add Realistic Price Trends
Issues to fix:
1. Remove nested 'items' arrays from purchase_orders (wrong structure)
2. Use existing purchase_order_items table structure at root level
3. Add price trends to existing PO items
4. Align PO items with actual inventory stock conditions
"""
import json
from pathlib import Path
import random
# Set seed for reproducibility
random.seed(42)
# Price trend data (realistic price movements over time)
# These match the 6 ingredients we track in inventory
PRICE_TRENDS = {
"10000000-0000-0000-0000-000000000001": { # Harina T55
"name": "Harina de Trigo T55",
"base_price": 0.85,
"current_price": 0.92, # +8% over 90 days
"trend": 0.08
},
"10000000-0000-0000-0000-000000000002": { # Harina T65
"name": "Harina de Trigo T65",
"base_price": 0.95,
"current_price": 1.01, # +6%
"trend": 0.06
},
"10000000-0000-0000-0000-000000000011": { # Mantequilla
"name": "Mantequilla sin Sal",
"base_price": 6.50,
"current_price": 7.28, # +12% (highest increase)
"trend": 0.12
},
"10000000-0000-0000-0000-000000000012": { # Leche
"name": "Leche Entera Fresca",
"base_price": 0.95,
"current_price": 0.92, # -3% (seasonal surplus)
"trend": -0.03
},
"10000000-0000-0000-0000-000000000021": { # Levadura
"name": "Levadura Fresca",
"base_price": 4.20,
"current_price": 4.37, # +4%
"trend": 0.04
},
"10000000-0000-0000-0000-000000000032": { # Azúcar
"name": "Azúcar Blanco",
"base_price": 1.10,
"current_price": 1.12, # +2% (stable)
"trend": 0.02
}
}
def calculate_price_for_date(ingredient_id: str, days_ago: int) -> float:
"""Calculate historical price based on trend"""
if ingredient_id not in PRICE_TRENDS:
return None
trend_data = PRICE_TRENDS[ingredient_id]
base = trend_data["base_price"]
total_trend = trend_data["trend"]
# Apply trend proportionally
# If 90 days trend is +8%, then 45 days ago had +4% from base
trend_factor = 1 + (total_trend * (90 - days_ago) / 90)
# Add small variability (±2%)
variability = random.uniform(-0.02, 0.02)
price = base * trend_factor * (1 + variability)
return round(price, 2)
def parse_days_ago(date_str: str) -> int:
"""Parse BASE_TS marker to extract days ago"""
if not date_str or 'BASE_TS' not in date_str:
return 30
if '- ' in date_str:
parts = date_str.split('- ')[1].strip()
if 'd' in parts:
try:
return int(parts.split('d')[0])
except:
pass
elif 'h' in parts:
return 0 # Same day
elif '+ ' in date_str:
return 0 # Future order, use current price
return 0 # BASE_TS alone = today
def main():
fixture_path = Path(__file__).parent / "07-procurement.json"
print("🔧 Fixing Procurement Data Structure...")
print()
# Load existing data
with open(fixture_path, 'r', encoding='utf-8') as f:
data = json.load(f)
purchase_orders = data.get('purchase_orders', [])
po_items = data.get('purchase_order_items', [])
print(f"📦 Found {len(purchase_orders)} purchase orders")
print(f"📋 Found {len(po_items)} PO items")
print()
# Step 1: Remove nested 'items' arrays from POs (wrong structure)
items_removed = 0
for po in purchase_orders:
if 'items' in po:
items_removed += len(po['items'])
del po['items']
if items_removed > 0:
print(f"✓ Removed {items_removed} nested items arrays (wrong structure)")
print()
# Step 2: Update existing PO items with realistic price trends
items_updated = 0
for item in po_items:
ingredient_id = item.get('inventory_product_id')
if ingredient_id in PRICE_TRENDS:
# Find the PO to get order date
po_id = item.get('purchase_order_id')
po = next((p for p in purchase_orders if p['id'] == po_id), None)
if po:
order_date = po.get('order_date', 'BASE_TS')
days_ago = parse_days_ago(order_date)
# Calculate price for that date
historical_price = calculate_price_for_date(ingredient_id, days_ago)
if historical_price:
# Update item with historical price
ordered_qty = float(item.get('ordered_quantity', 0))
item['unit_price'] = historical_price
item['line_total'] = round(ordered_qty * historical_price, 2)
items_updated += 1
print(f"✓ Updated {items_updated} PO items with price trends")
print()
# Step 3: Recalculate PO totals based on updated items
for po in purchase_orders:
po_id = po['id']
po_items_for_this_po = [item for item in po_items if item.get('purchase_order_id') == po_id]
if po_items_for_this_po:
# Calculate subtotal from items
subtotal = sum(float(item.get('line_total', 0)) for item in po_items_for_this_po)
tax_rate = 0.21 # 21% IVA in Spain
tax = subtotal * tax_rate
# Keep existing shipping cost or default
shipping = float(po.get('shipping_cost', 15.0 if subtotal < 500 else 20.0))
discount = float(po.get('discount_amount', 0.0))
total = subtotal + tax + shipping - discount
po['subtotal'] = round(subtotal, 2)
po['tax_amount'] = round(tax, 2)
po['shipping_cost'] = round(shipping, 2)
po['discount_amount'] = round(discount, 2)
po['total_amount'] = round(total, 2)
print(f"✓ Recalculated totals for {len(purchase_orders)} purchase orders")
print()
# Save fixed data
with open(fixture_path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
print("=" * 60)
print("✅ PROCUREMENT STRUCTURE FIXED")
print("=" * 60)
print()
print("🎯 Changes Applied:")
print(f" • Removed {items_removed} incorrectly nested items")
print(f" • Updated {items_updated} PO items with price trends")
print(f" • Recalculated {len(purchase_orders)} PO totals")
print()
print("📊 Price Trends Applied:")
for ing_id, data in PRICE_TRENDS.items():
direction = "" if data["trend"] > 0 else ""
print(f" {direction} {data['name']}: {data['trend']*100:+.1f}%")
print()
print("✅ Data structure now matches PurchaseOrderItem model")
print("✅ Price trends enable procurement AI insights")
print()
if __name__ == "__main__":
main()