#!/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()