#!/usr/bin/env python3 """ Utility script to clean old purchase orders and production batches with malformed reasoning_data. This script deletes pending purchase orders and production batches that were created before the fix for template variable interpolation. After running this script, trigger a new orchestration run to create fresh data with properly interpolated variables. Usage: python scripts/clean_old_dashboard_data.py --tenant-id """ import asyncio import argparse import sys import os from datetime import datetime, timedelta # Add services to path sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../services/procurement/app')) sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../services/production/app')) sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../shared')) async def clean_old_purchase_orders(tenant_id: str, dry_run: bool = True): """Clean old pending purchase orders""" try: from app.core.database import AsyncSessionLocal from sqlalchemy import text async with AsyncSessionLocal() as session: # Get count of pending POs count_result = await session.execute( text(""" SELECT COUNT(*) FROM purchase_orders WHERE tenant_id = :tenant_id AND status = 'pending_approval' """), {"tenant_id": tenant_id} ) count = count_result.scalar() print(f"Found {count} pending purchase orders for tenant {tenant_id}") if count == 0: print("No purchase orders to clean.") return 0 if dry_run: print(f"DRY RUN: Would delete {count} pending purchase orders") return count # Delete pending POs result = await session.execute( text(""" DELETE FROM purchase_orders WHERE tenant_id = :tenant_id AND status = 'pending_approval' """), {"tenant_id": tenant_id} ) await session.commit() deleted = result.rowcount print(f"✓ Deleted {deleted} pending purchase orders") return deleted except Exception as e: print(f"Error cleaning purchase orders: {e}") return 0 async def clean_old_production_batches(tenant_id: str, dry_run: bool = True): """Clean old pending production batches""" try: # Import production service dependencies sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../services/production/app')) from app.core.database import AsyncSessionLocal as ProductionSession from sqlalchemy import text async with ProductionSession() as session: # Get count of pending/scheduled batches count_result = await session.execute( text(""" SELECT COUNT(*) FROM production_batches WHERE tenant_id = :tenant_id AND status IN ('pending', 'scheduled') """), {"tenant_id": tenant_id} ) count = count_result.scalar() print(f"Found {count} pending/scheduled production batches for tenant {tenant_id}") if count == 0: print("No production batches to clean.") return 0 if dry_run: print(f"DRY RUN: Would delete {count} pending/scheduled batches") return count # Delete pending/scheduled batches result = await session.execute( text(""" DELETE FROM production_batches WHERE tenant_id = :tenant_id AND status IN ('pending', 'scheduled') """), {"tenant_id": tenant_id} ) await session.commit() deleted = result.rowcount print(f"✓ Deleted {deleted} pending/scheduled production batches") return deleted except Exception as e: print(f"Error cleaning production batches: {e}") return 0 async def main(): parser = argparse.ArgumentParser( description='Clean old dashboard data (POs and batches) with malformed reasoning_data' ) parser.add_argument( '--tenant-id', required=True, help='Tenant ID to clean data for' ) parser.add_argument( '--execute', action='store_true', help='Actually delete data (default is dry run)' ) args = parser.parse_args() dry_run = not args.execute print("=" * 60) print("Dashboard Data Cleanup Utility") print("=" * 60) print(f"Tenant ID: {args.tenant_id}") print(f"Mode: {'DRY RUN (no changes will be made)' if dry_run else 'EXECUTE (will delete data)'}") print("=" * 60) print() if not dry_run: print("⚠️ WARNING: This will permanently delete pending purchase orders and production batches!") print(" After deletion, you should trigger a new orchestration run to create fresh data.") response = input(" Are you sure you want to continue? (yes/no): ") if response.lower() != 'yes': print("Aborted.") return print() # Clean purchase orders print("1. Cleaning Purchase Orders...") po_count = await clean_old_purchase_orders(args.tenant_id, dry_run) print() # Clean production batches print("2. Cleaning Production Batches...") batch_count = await clean_old_production_batches(args.tenant_id, dry_run) print() print("=" * 60) print("Summary:") print(f" Purchase Orders: {po_count} {'would be' if dry_run else ''} deleted") print(f" Production Batches: {batch_count} {'would be' if dry_run else ''} deleted") print("=" * 60) if dry_run: print("\nTo actually delete the data, run with --execute flag:") print(f" python scripts/clean_old_dashboard_data.py --tenant-id {args.tenant_id} --execute") else: print("\n✓ Data cleaned successfully!") print("\nNext steps:") print(" 1. Restart the orchestrator service") print(" 2. Trigger a new orchestration run from the dashboard") print(" 3. The new POs and batches will have properly interpolated variables") if __name__ == '__main__': asyncio.run(main())