From d0d18927cfdbb7d393b1df5b419b043719f55d7d Mon Sep 17 00:00:00 2001 From: Daniel Rosel Date: Fri, 28 Nov 2025 18:52:05 +0100 Subject: [PATCH] chore: e2e is done with new pipeline --- backend/provider/app.py | 91 +++++++++++++++++++-- experiments/procesing/pricers/elasticity.py | 8 +- experiments/procesing/providers/supabase.py | 2 + 3 files changed, 91 insertions(+), 10 deletions(-) diff --git a/backend/provider/app.py b/backend/provider/app.py index 4e1f6a8..d2752ac 100644 --- a/backend/provider/app.py +++ b/backend/provider/app.py @@ -4,9 +4,27 @@ from pydantic import BaseModel from typing import Literal, Optional import uvicorn, os, sys from supabase import create_client, Client +from dotenv import load_dotenv +import numpy as np +import pandas as pd +load_dotenv() # Local imports of registry and pricing function +sys.path.append(os.path.dirname(os.path.abspath(__file__))+ "/../../experiments/") +from procesing.providers import SupabaseProvider, BackendAPIProvider +from procesing.pricers import ( + StaticPricer, + RandomPricer, + ElasticityBasedPricer +) +from procesing.steps import ( + StateSpace, + PredictPricesStep +) +from procesing import PipelineContext +sys.path.append(os.path.dirname(os.path.abspath(__file__))+ "/../../lib/") +from lib.model_registry import ModelRegistry # Config app = FastAPI(title="PHANTOM Pricing Provider") @@ -34,23 +52,80 @@ def get_price(mode: Literal['hotel', 'airline'], productId: str, sessionId: Opti metadata = product['metadata'] base_price = metadata.get('base_price', 100.0) + + class Provider(SupabaseProvider, BackendAPIProvider): + def __init__(self, backend_url: str): + SupabaseProvider.__init__(self) + BackendAPIProvider.__init__(self, backend_url=backend_url) + context = PipelineContext( + provider=Provider(backend_url=os.getenv("BACKEND_API_URL")), + store_mode=mode + ) + + pricing_model = registry.get_pricing_model('latest') elasticity_df = registry.get_elasticity('latest') - if not elasticity_df: - return PriceResponse(productId=productId, price=base_price, base_price=base_price, markup=1.0) + if pricing_model is None or elasticity_df is None: + # fallback to base price if no model available + return PriceResponse( + productId=productId, + price=base_price, + base_price=base_price, + markup=1.0, + elasticity=None + ) - pricing_model = registry.get_pricing_model('latest') or ElasticityBasedPricingFunction().fit(elasticity_df) - product_elasticity = elasticity_df[elasticity_df['productId'] == productId]['elasticity'].iloc[0] if (elasticity_row := elasticity_df[elasticity_df['productId'] == productId]).any().any() else None + # build full state space for all products in catalog + products = context.products + if products.empty: + raise HTTPException(500, "No products available in catalog") - state = StateSpace(np.array([0.0]), np.array([base_price]), pd.DataFrame()) - optimal_price = pricing_model.transform(state, np.array([productId]))[0] + # merge elasticity with product base prices + products_with_meta = products.copy() + products_with_meta['base_price'] = products_with_meta['metadata'].apply( + lambda m: m.get('base_price', 100.0) if isinstance(m, dict) else 100.0 + ) + + merged = products_with_meta[['id', 'base_price']].rename( + columns={'id': 'productId'} + ).merge( + elasticity_df[['productId', 'elasticity']], + on='productId', + how='left' + ).fillna({'elasticity': 0.0}) + + # use fitted pricer's mean_demand if available, else default to 10.0 + demand_values = (pricing_model.mean_demand + if hasattr(pricing_model, 'mean_demand') and pricing_model.mean_demand is not None + else np.ones(len(merged)) * 10.0) + + state = StateSpace( + demand=demand_values, + prices=merged['base_price'].values, + session_features=pd.DataFrame() + ) + + oracle = PredictPricesStep(context=context) + prices_df = oracle.transform((pricing_model, state)) + + # extract price for requested product + product_price_row = prices_df[prices_df['productId'] == productId] + if product_price_row.empty: + raise HTTPException(404, f"No pricing available for product {productId}") + + optimal_price = float(product_price_row['predicted_price'].iloc[0]) + + # extract elasticity if available + product_elasticity_row = elasticity_df[elasticity_df['productId'] == productId] + product_elasticity = (float(product_elasticity_row['elasticity'].iloc[0]) + if not product_elasticity_row.empty else None) return PriceResponse( productId=productId, - price=float(optimal_price), + price=optimal_price, base_price=base_price, markup=optimal_price/base_price, - elasticity=float(product_elasticity) if product_elasticity is not None else None + elasticity=product_elasticity ) @app.get("/models") diff --git a/experiments/procesing/pricers/elasticity.py b/experiments/procesing/pricers/elasticity.py index ea3fb00..b203159 100644 --- a/experiments/procesing/pricers/elasticity.py +++ b/experiments/procesing/pricers/elasticity.py @@ -26,8 +26,12 @@ class ElasticityBasedPricer(PricingFunction): raise ValueError("historical_data must contain 'elasticity' column") self.elasticity = historical_data['elasticity'].values - self.base_prices = historical_data.get('base_price', np.ones(len(historical_data)) * 100).values - self.mean_demand = historical_data.get('mean_demand', np.ones(len(historical_data)) * 10).values + self.base_prices = (historical_data['base_price'].values + if 'base_price' in historical_data.columns + else np.ones(len(historical_data)) * 100) + self.mean_demand = (historical_data['mean_demand'].values + if 'mean_demand' in historical_data.columns + else np.ones(len(historical_data)) * 10) return self def predict(self, state_space) -> np.ndarray: diff --git a/experiments/procesing/providers/supabase.py b/experiments/procesing/providers/supabase.py index 43ce8c0..8da01f9 100755 --- a/experiments/procesing/providers/supabase.py +++ b/experiments/procesing/providers/supabase.py @@ -4,6 +4,7 @@ import requests from typing import List from supabase import create_client, Client from procesing.providers.base import DataProvider +from dotenv import load_dotenv class SupabaseProvider(DataProvider): """Concrete Supabase + backend API implementation""" @@ -11,6 +12,7 @@ class SupabaseProvider(DataProvider): def __init__(self, supabase_url: str = None, supabase_key: str = None,): + load_dotenv() self.supabase_url = supabase_url or os.getenv("NEXT_PUBLIC_SUPABASE_URL") self.supabase_key = supabase_key or os.getenv("NEXT_PUBLIC_SUPABASE_ANON_KEY") self.supabase: Client = create_client(self.supabase_url, self.supabase_key)