From 35ae23b381b8a4a232179cc8f4340ce39670a63e Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Tue, 16 Dec 2025 07:36:03 +0100 Subject: [PATCH] Fix forecasting clone endpoint for demo sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed two critical issues preventing forecast data from being cloned: 1. **Missing batch_name field**: The fixture uses `batch_id` but the PredictionBatch model requires `batch_name` (NOT NULL constraint). Added field mapping to handle batch_id -> batch_name conversion. 2. **UUID type mismatch**: The fixture's `product_id` is a string but the Forecast model expects `inventory_product_id` as UUID type. Added conversion from string to UUID. 3. **Field mappings added**: - batch_id -> batch_name - total_forecasts -> total_products - created_at -> requested_at (fallback) - Calculated completed_products from status These fixes enable the forecasting service to successfully clone all 28 forecasts from the fixture file, unlocking demand forecasting AI insights in demo sessions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- services/forecasting/app/api/internal_demo.py | 61 ++++++++----------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/services/forecasting/app/api/internal_demo.py b/services/forecasting/app/api/internal_demo.py index 535b215f..57756899 100644 --- a/services/forecasting/app/api/internal_demo.py +++ b/services/forecasting/app/api/internal_demo.py @@ -244,7 +244,12 @@ async def clone_demo_data( # Create forecast # Map product_id to inventory_product_id if needed - inventory_product_id = forecast_data.get('inventory_product_id') or forecast_data.get('product_id') + inventory_product_id_str = forecast_data.get('inventory_product_id') or forecast_data.get('product_id') + # Convert to UUID if it's a string + if isinstance(inventory_product_id_str, str): + inventory_product_id = uuid.UUID(inventory_product_id_str) + else: + inventory_product_id = inventory_product_id_str # Map predicted_quantity to predicted_demand if needed predicted_demand = forecast_data.get('predicted_demand') or forecast_data.get('predicted_quantity') @@ -317,43 +322,31 @@ async def clone_demo_data( detail=f"Invalid UUID format in batch data: {str(e)}" ) - # Transform dates using proper parse_date_field function - for date_field in ['requested_at', 'completed_at']: - if date_field in batch_data: - try: - parsed_date = parse_date_field( - batch_data[date_field], - session_time, - date_field - ) - if parsed_date: - batch_data[date_field] = parsed_date - else: - # If parsing fails, use session_time as fallback - batch_data[date_field] = session_time - logger.warning("Using fallback date for failed parsing", - date_field=date_field, - original_value=batch_data[date_field]) - except Exception as e: - logger.warning("Failed to parse date, using fallback", - date_field=date_field, - date_value=batch_data[date_field], - error=str(e)) - batch_data[date_field] = session_time - # Create prediction batch + # Handle field mapping: batch_id -> batch_name, total_forecasts -> total_products + batch_name = batch_data.get('batch_name') or batch_data.get('batch_id') or f"Batch-{transformed_id}" + total_products = batch_data.get('total_products') or batch_data.get('total_forecasts') or 0 + completed_products = batch_data.get('completed_products') or (total_products if batch_data.get('status') == 'COMPLETED' else 0) + + # Parse dates (handle created_at or prediction_date for requested_at) + requested_at_raw = batch_data.get('requested_at') or batch_data.get('created_at') or batch_data.get('prediction_date') + requested_at = parse_date_field(requested_at_raw, session_time, 'requested_at') if requested_at_raw else session_time + + completed_at_raw = batch_data.get('completed_at') + completed_at = parse_date_field(completed_at_raw, session_time, 'completed_at') if completed_at_raw else None + new_batch = PredictionBatch( id=transformed_id, tenant_id=virtual_uuid, - batch_name=batch_data.get('batch_name'), - requested_at=batch_data.get('requested_at'), - completed_at=batch_data.get('completed_at'), - status=batch_data.get('status'), - total_products=batch_data.get('total_products'), - completed_products=batch_data.get('completed_products'), - failed_products=batch_data.get('failed_products'), - forecast_days=batch_data.get('forecast_days'), - business_type=batch_data.get('business_type'), + batch_name=batch_name, + requested_at=requested_at, + completed_at=completed_at, + status=batch_data.get('status', 'completed'), + total_products=total_products, + completed_products=completed_products, + failed_products=batch_data.get('failed_products', 0), + forecast_days=batch_data.get('forecast_days', 7), + business_type=batch_data.get('business_type', 'individual'), error_message=batch_data.get('error_message'), processing_time_ms=batch_data.get('processing_time_ms'), cancelled_by=batch_data.get('cancelled_by')