diff --git a/services/forecasting/app/api/forecasts.py b/services/forecasting/app/api/forecasts.py index 1a9b6ca9..c260b667 100644 --- a/services/forecasting/app/api/forecasts.py +++ b/services/forecasting/app/api/forecasts.py @@ -33,26 +33,19 @@ forecasting_service = ForecastingService() async def create_single_forecast( request: ForecastRequest, db: AsyncSession = Depends(get_db), - tenant_id: str = Path(..., description="Tenant ID"), - current_user: dict = Depends(get_current_user_dep) + tenant_id: str = Path(..., description="Tenant ID") ): """Generate a single product forecast""" try: - # Verify tenant access - if str(request.tenant_id) != tenant_id: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="Access denied to this tenant" - ) # Generate forecast - forecast = await forecasting_service.generate_forecast(request, db) + forecast = await forecasting_service.generate_forecast(tenant_id, request, db) # Convert to response model return ForecastResponse( id=str(forecast.id), - tenant_id=str(forecast.tenant_id), + tenant_id=tenant_id, product_name=forecast.product_name, location=forecast.location, forecast_date=forecast.forecast_date, diff --git a/services/forecasting/app/services/forecasting_service.py b/services/forecasting/app/services/forecasting_service.py index b6e8758d..ce6c8075 100644 --- a/services/forecasting/app/services/forecasting_service.py +++ b/services/forecasting/app/services/forecasting_service.py @@ -38,19 +38,19 @@ class ForecastingService: self.model_client = ModelClient() self.data_client = DataClient() - async def generate_forecast(self, request: ForecastRequest, db: AsyncSession) -> Forecast: + async def generate_forecast(self, tenant_id: str, request: ForecastRequest, db: AsyncSession) -> Forecast: """Generate a single forecast for a product""" start_time = datetime.now() try: logger.info("Generating forecast", - tenant_id=request.tenant_id, + tenant_id=tenant_id, product=request.product_name, date=request.forecast_date) # Get the latest trained model for this tenant/product model_info = await self._get_latest_model( - request.tenant_id, + tenant_id, request.product_name, ) @@ -58,7 +58,7 @@ class ForecastingService: raise ValueError(f"No trained model found for {request.product_name}") # Prepare features for prediction - features = await self._prepare_forecast_features(request) + features = await self._prepare_forecast_features(tenant_id, request) # Generate prediction using ML service prediction_result = await self.prediction_service.predict( @@ -69,7 +69,7 @@ class ForecastingService: # Create forecast record forecast = Forecast( - tenant_id=uuid.UUID(request.tenant_id), + tenant_id=uuid.UUID(tenant_id), product_name=request.product_name, forecast_date=datetime.combine(request.forecast_date, datetime.min.time()), @@ -115,7 +115,7 @@ class ForecastingService: # Publish event await publish_forecast_completed({ "forecast_id": str(forecast.id), - "tenant_id": request.tenant_id, + "tenant_id": tenant_id, "product_name": request.product_name, "predicted_demand": forecast.predicted_demand }) @@ -256,7 +256,7 @@ class ForecastingService: logger.error("Error getting latest model", error=str(e)) raise - async def _prepare_forecast_features(self, request: ForecastRequest) -> Dict[str, Any]: + async def _prepare_forecast_features(self, tenant_id: str, request: ForecastRequest) -> Dict[str, Any]: """Prepare features for forecasting model""" features = { @@ -269,7 +269,7 @@ class ForecastingService: features["is_holiday"] = await self._is_spanish_holiday(request.forecast_date) - weather_data = await self._get_weather_forecast(request.tenant_id, 1) + weather_data = await self._get_weather_forecast(tenant_id, 1) features.update(weather_data) return features diff --git a/tests/test_onboarding_flow.sh b/tests/test_onboarding_flow.sh index 98eef10a..6fd09cec 100755 --- a/tests/test_onboarding_flow.sh +++ b/tests/test_onboarding_flow.sh @@ -563,31 +563,73 @@ echo "" # STEP 5: ONBOARDING COMPLETION (DASHBOARD ACCESS) # ================================================================= -echo -e "${STEP_ICONS[4]} ${PURPLE}STEP 5: ONBOARDING COMPLETION${NC}" -echo "Simulating completion and dashboard access" -echo "" - log_step "5.1. Testing basic dashboard functionality" -# Use a real product name from our CSV for forecasting -FIRST_PRODUCT=$(echo "$REAL_PRODUCTS" | sed 's/"//g' | cut -d',' -f1) - -FORECAST_RESPONSE=$(curl -s -X POST "$API_BASE/api/v1/tenants/$TENANT_ID/forecasts/single" \ +# Get a real product name from CSV (fix the product extraction) +FIRST_PRODUCT=$(head -2 "$REAL_CSV_FILE" | tail -1 | cut -d',' -f3 | sed 's/"//g' | head -1) + +if [ -z "$FIRST_PRODUCT" ]; then + FIRST_PRODUCT="Pan Integral" # Fallback product name + log_warning "Could not extract product from CSV, using fallback: $FIRST_PRODUCT" +else + log_success "Using product from CSV: $FIRST_PRODUCT" +fi + +# CORRECTED forecast request with proper schema +FORECAST_REQUEST="{ + \"product_name\": \"$FIRST_PRODUCT\", + \"forecast_date\": \"2025-07-30\", + \"forecast_days\": 1, + \"location\": \"madrid_centro\", + \"confidence_level\": 0.85 +}" + +echo "Forecast Request:" +echo "$FORECAST_REQUEST" | python3 -m json.tool + +# Make the API call +FORECAST_RESPONSE=$(curl -s -w "\nHTTP_CODE:%{http_code}" -X POST "$API_BASE/api/v1/tenants/$TENANT_ID/forecasts/single" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ - -d "{ - \"product_name\": [\"$FIRST_PRODUCT\"], - \"forecast_date\": "2025-11-30", - \"forecast_days\": 1, - \"date\": \"2025-09-15\", - \"location\": \"madrid_centro\", - \"confidence_level\": 0.85 - }") - -if echo "$FORECAST_RESPONSE" | grep -q '"predictions"\|"forecast"'; then - log_success "Forecasting service is accessible" + -d "$FORECAST_REQUEST") + +# Extract HTTP code and response +HTTP_CODE=$(echo "$FORECAST_RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) +FORECAST_RESPONSE=$(echo "$FORECAST_RESPONSE" | sed '/HTTP_CODE:/d') + +echo "Forecast HTTP Status: $HTTP_CODE" +echo "Forecast Response:" +echo "$FORECAST_RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$FORECAST_RESPONSE" + +# Validate response +if [ "$HTTP_CODE" = "200" ]; then + if echo "$FORECAST_RESPONSE" | grep -q '"predicted_demand"\|"id"'; then + log_success "Forecasting service is working correctly" + + # Extract key values for validation + PREDICTED_DEMAND=$(extract_json_field "$FORECAST_RESPONSE" "predicted_demand") + CONFIDENCE_LOWER=$(extract_json_field "$FORECAST_RESPONSE" "confidence_lower") + CONFIDENCE_UPPER=$(extract_json_field "$FORECAST_RESPONSE" "confidence_upper") + + if [ -n "$PREDICTED_DEMAND" ]; then + echo " Predicted Demand: $PREDICTED_DEMAND" + echo " Confidence Range: [$CONFIDENCE_LOWER, $CONFIDENCE_UPPER]" + fi + else + log_error "Forecast response missing expected fields" + echo "Response: $FORECAST_RESPONSE" + fi +elif [ "$HTTP_CODE" = "422" ]; then + log_error "Forecast request validation failed" + echo "Validation errors: $FORECAST_RESPONSE" +elif [ "$HTTP_CODE" = "404" ]; then + log_warning "Forecast endpoint not found - check API routing" +elif [ "$HTTP_CODE" = "500" ]; then + log_error "Internal server error in forecasting service" + echo "Error details: $FORECAST_RESPONSE" else - log_warning "Forecasting may not be ready yet (model training required)" + log_warning "Forecasting may not be ready yet (HTTP $HTTP_CODE)" + echo "Response: $FORECAST_RESPONSE" fi echo ""