Skip to content

Deploying Predictive Analytics: Food Price Forecasting

In this guide, we will deploy a time-series forecasting model designed to predict agricultural commodities and food prices.

Time-series models (whether traditional like ARIMA/Prophet, or machine learning models using lag features like XGBoost) differ from standard predictive models because they rely on historical context. To predict tomorrow's price, the API cannot just receive static features; it must receive a sequence (or "window") of past prices.

We will build a FastAPI application that accepts an array of historical data points and returns the forecasted future value.


Prerequisites

Before starting, ensure you have:

  • A trained time-series model saved as a serialized file (e.g., wheat_price_model.joblib).
  • Docker installed locally.
  • An understanding of the "lookback window" your model requires (e.g., it needs exactly 30 days of past data to make a prediction).

The Inference API (app.py)

We will use Pydantic to strictly enforce the length of the historical data array. If your model was trained on a 14-day lookback window, passing 13 or 15 days will cause it to crash. Pydantic prevents this before the data ever touches the model.

Create a file named app.py:

import joblib
import numpy as np
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI(title="Food Price Forecasting API")

# Define the expected lookback window (e.g., 14 days of historical prices)
LOOKBACK_WINDOW = 14

# 1. Define the schema for the incoming historical data
class TimeSeriesData(BaseModel):
    commodity: str = Field(..., description="Name of the crop or commodity (e.g., 'Wheat', 'Corn')")
    historical_prices: list[float] = Field(
        ..., 
        description=f"An array of exactly {LOOKBACK_WINDOW} historical daily prices."
    )

# 2. Load the forecasting model globally
try:
    # Replace with your actual model filename
    model = joblib.load('wheat_price_model.joblib')
except Exception as e:
    print(f"Error loading model: {e}")
    model = None

@app.post("/forecast")
def forecast_price(data: TimeSeriesData):
    if model is None:
        raise HTTPException(status_code=500, detail="Model is not loaded.")

    # 3. Strictly validate the window size
    if len(data.historical_prices) != LOOKBACK_WINDOW:
        raise HTTPException(
            status_code=400, 
            detail=f"Expected exactly {LOOKBACK_WINDOW} data points, got {len(data.historical_prices)}."
        )

    try:
        # Reshape the 1D list into a 2D array for Scikit-Learn/XGBoost
        features = np.array([data.historical_prices])

        # Run inference to predict the next time step (e.g., day 15)
        predicted_price = float(model.predict(features)[0])

        return {
            "commodity": data.commodity,
            "forecasted_price": round(predicted_price, 2),
            "currency": "USD",
            "model_version": "1.0"
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/health")
def health_check():
    return {"status": "healthy", "model_loaded": model is not None}

Managing Dependencies (requirements.txt)

Create your requirements.txt:

fastapi==0.103.2
uvicorn==0.23.2
pydantic==2.4.2
scikit-learn==1.3.1
joblib==1.3.2
numpy==1.26.0

The Dockerfile

Because this is a standard tabular/numerical model, the Dockerfile remains highly optimized and lightweight.

FROM python:3.10-slim

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the application code and model weights
COPY . .

EXPOSE 8000

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

The .dockerignore file

__pycache__/
*.pyc
.venv/
venv/
.git/
*.ipynb
data/
*.csv

Deployment Steps

Time-series forecasting models often have unique usage patterns on our platform.

Build the Docker Image:

docker build -t your-registry/food-price-api:v1 .

Push to your Container Registry:

docker push your-registry/food-price-api:v1

Deploy on the Platform:

  • Visit Crane Cloud and create a project to deploy the image your-registry/food-price-api:v1
  • Scale to Zero: Because this API might only be hit a few times a day, Crane Cloud is able to scale this deployment down to zero pods when inactive to save heavily on cloud costs.

Testing the Endpoint

Test your API by sending a JSON payload containing the required array of historical prices:

curl -X POST "https://food-price-api.ahumain.cranecloud.io/forecast" \
  -H "Content-Type: application/json" \
  -d '{
        "commodity": "Wheat",
        "historical_prices": [
          210.5, 212.0, 211.5, 209.0, 208.5, 210.0, 213.5, 
          215.0, 216.5, 218.0, 217.5, 219.0, 220.5, 222.0
        ]
      }'

Expected Response

{
  "commodity": "Wheat",
  "forecasted_price": 224.3,
  "currency": "USD",
  "model_version": "1.0"
}