Files
bakery-ia/scripts/clean_old_dashboard_data.py
Claude 298be127d7 Fix template variable interpolation by creating params copy
Root cause: params = reasoning_data.get('parameters', {}) created a reference
to the dictionary instead of a copy. When modifying params to add
product_names_joined, the change didn't persist because the database object
was immutable/read-only.

Changes:
- dashboard_service.py:408 - Create dict copy for PO params
- dashboard_service.py:632 - Create dict copy for batch params
- Added clean_old_dashboard_data.py utility script to remove old POs/batches
  with malformed reasoning_data

The fix ensures template variables like {{supplier_name}}, {{product_names_joined}},
{{days_until_stockout}}, etc. are properly interpolated in the dashboard.
2025-11-20 19:30:12 +00:00

189 lines
6.4 KiB
Python

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