Spaces:
Sleeping
Sleeping
Upload 30 files
Browse files- .gitattributes +2 -0
- Dockerfile +11 -0
- app/__init__.py +0 -0
- app/__pycache__/__init__.cpython-311.pyc +0 -0
- app/__pycache__/database.cpython-311.pyc +0 -0
- app/__pycache__/main.cpython-311.pyc +0 -0
- app/__pycache__/ml_models.cpython-311.pyc +0 -0
- app/__pycache__/models.cpython-311.pyc +0 -0
- app/database.py +15 -0
- app/main.py +101 -0
- app/ml_models.py +137 -0
- app/models.py +61 -0
- app/routes/__init__.py +0 -0
- app/routes/__pycache__/__init__.cpython-311.pyc +0 -0
- app/routes/__pycache__/customers.cpython-311.pyc +0 -0
- app/routes/__pycache__/predictions.cpython-311.pyc +0 -0
- app/routes/customers.py +119 -0
- app/routes/predictions.py +50 -0
- app/upload_to_neon.py +31 -0
- data/OnlineRetail.csv +3 -0
- data/clv_predictions.csv +0 -0
- data/customer_features_complete.csv +0 -0
- data/customer_segments.csv +0 -0
- data/features_daily_sales.csv +306 -0
- data/features_products.csv +0 -0
- data/features_rfm.csv +0 -0
- data/online_retail_cleaned.csv +3 -0
- models/clv_model.pkl +3 -0
- models/kmeans_model.pkl +3 -0
- models/scaler.pkl +3 -0
- requirements.txt +8 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
data/online_retail_cleaned.csv filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
data/OnlineRetail.csv filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY . .
|
| 9 |
+
|
| 10 |
+
EXPOSE 7860
|
| 11 |
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
app/__init__.py
ADDED
|
File without changes
|
app/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (164 Bytes). View file
|
|
|
app/__pycache__/database.cpython-311.pyc
ADDED
|
Binary file (1.02 kB). View file
|
|
|
app/__pycache__/main.cpython-311.pyc
ADDED
|
Binary file (3.86 kB). View file
|
|
|
app/__pycache__/ml_models.cpython-311.pyc
ADDED
|
Binary file (5.3 kB). View file
|
|
|
app/__pycache__/models.cpython-311.pyc
ADDED
|
Binary file (4.11 kB). View file
|
|
|
app/database.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import create_engine
|
| 2 |
+
from sqlalchemy.orm import sessionmaker
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:samadhi@localhost:5432/ecommerce_db")
|
| 6 |
+
|
| 7 |
+
engine = create_engine(DATABASE_URL)
|
| 8 |
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
| 9 |
+
|
| 10 |
+
def get_db():
|
| 11 |
+
db = SessionLocal()
|
| 12 |
+
try:
|
| 13 |
+
yield db
|
| 14 |
+
finally:
|
| 15 |
+
db.close()
|
app/main.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
from app.database import engine
|
| 4 |
+
from app.ml_models import load_models
|
| 5 |
+
from app.routes import customers, predictions
|
| 6 |
+
from sqlalchemy import text
|
| 7 |
+
|
| 8 |
+
# load ML models on startup
|
| 9 |
+
models_loaded = load_models()
|
| 10 |
+
|
| 11 |
+
# create fastapi app
|
| 12 |
+
app = FastAPI(
|
| 13 |
+
title="E-Commerce Customer Intelligence API",
|
| 14 |
+
description="""
|
| 15 |
+
This API provides customer insights from your e-commerce data.
|
| 16 |
+
|
| 17 |
+
## Features
|
| 18 |
+
* **Customer Segmentation** - Predict customer segments using K-Means
|
| 19 |
+
* **CLV Prediction** - Predict Customer Lifetime Value
|
| 20 |
+
* **Customer Data** - Access customer information from database
|
| 21 |
+
|
| 22 |
+
## Models
|
| 23 |
+
* K-Means Clustering (4 segments)
|
| 24 |
+
* Random Forest CLV Predictor (95% accuracy)
|
| 25 |
+
""",
|
| 26 |
+
version="1.0.0",
|
| 27 |
+
contact={
|
| 28 |
+
"name": "Your Name",
|
| 29 |
+
"email": "your.email@example.com",
|
| 30 |
+
},
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
#add CORS middleware (allows frontend apps to call your API)
|
| 34 |
+
app.add_middleware(
|
| 35 |
+
CORSMiddleware,
|
| 36 |
+
allow_origins=["*"], # In production, specify actual domains
|
| 37 |
+
allow_credentials=True,
|
| 38 |
+
allow_methods=["*"],
|
| 39 |
+
allow_headers=["*"],
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
#include routers
|
| 43 |
+
app.include_router(customers.router)
|
| 44 |
+
app.include_router(predictions.router)
|
| 45 |
+
|
| 46 |
+
@app.get("/", tags=["Root"])
|
| 47 |
+
def root():
|
| 48 |
+
"""Welcome endpoint"""
|
| 49 |
+
return {
|
| 50 |
+
"message": "Welcome to E-Commerce Customer Intelligence API",
|
| 51 |
+
"docs": "/docs",
|
| 52 |
+
"version": "1.0.0",
|
| 53 |
+
"status": "operational"
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
@app.get("/health", tags=["Health"])
|
| 57 |
+
def health_check():
|
| 58 |
+
"""Check if API and database are working"""
|
| 59 |
+
db_status = "unknown"
|
| 60 |
+
|
| 61 |
+
try:
|
| 62 |
+
#test database connection
|
| 63 |
+
with engine.connect() as conn:
|
| 64 |
+
conn.execute(text("SELECT 1"))
|
| 65 |
+
db_status = "connected"
|
| 66 |
+
except Exception as e:
|
| 67 |
+
db_status = f"error: {str(e)}"
|
| 68 |
+
|
| 69 |
+
return {
|
| 70 |
+
"status": "healthy",
|
| 71 |
+
"models_loaded": {
|
| 72 |
+
"kmeans": models_loaded,
|
| 73 |
+
"clv": models_loaded
|
| 74 |
+
},
|
| 75 |
+
"database": db_status,
|
| 76 |
+
"timestamp": "2024-01-01T00:00:00Z"
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
@app.get("/info", tags=["Info"])
|
| 80 |
+
def api_info():
|
| 81 |
+
"""Get API information and available endpoints"""
|
| 82 |
+
return {
|
| 83 |
+
"name": "E-Commerce Customer Intelligence API",
|
| 84 |
+
"version": "1.0.0",
|
| 85 |
+
"endpoints": {
|
| 86 |
+
"GET /": "Welcome message",
|
| 87 |
+
"GET /health": "Health check",
|
| 88 |
+
"GET /info": "This information",
|
| 89 |
+
"GET /customers": "List all customers",
|
| 90 |
+
"GET /customers/{id}": "Get customer by ID",
|
| 91 |
+
"GET /customers/{id}/transactions": "Get customer transactions",
|
| 92 |
+
"POST /predict/segment": "Predict customer segment from RFM",
|
| 93 |
+
"POST /predict/clv": "Predict Customer Lifetime Value"
|
| 94 |
+
},
|
| 95 |
+
"models": {
|
| 96 |
+
"customer_segmentation": "K-Means (4 clusters)",
|
| 97 |
+
"clv_prediction": "Random Forest (95% accuracy)"
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
#run with: uvicorn app.main:app --reload
|
app/ml_models.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import joblib
|
| 2 |
+
import os
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
#global variables for models
|
| 7 |
+
kmeans_model = None
|
| 8 |
+
clv_model = None
|
| 9 |
+
scaler = None
|
| 10 |
+
|
| 11 |
+
def load_models():
|
| 12 |
+
"""Load all trained ML models"""
|
| 13 |
+
global kmeans_model, clv_model, scaler
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
| 17 |
+
project_root = os.path.dirname(current_dir)
|
| 18 |
+
models_path = os.path.join(project_root, "models")
|
| 19 |
+
|
| 20 |
+
print(f"Looking for models in: {models_path}")
|
| 21 |
+
|
| 22 |
+
if not os.path.exists(models_path):
|
| 23 |
+
print(f"Models folder not found at: {models_path}")
|
| 24 |
+
return False
|
| 25 |
+
|
| 26 |
+
try:
|
| 27 |
+
#load K-Means model for customer segmentation
|
| 28 |
+
kmeans_path = os.path.join(models_path, "kmeans_model.pkl")
|
| 29 |
+
if os.path.exists(kmeans_path):
|
| 30 |
+
kmeans_model = joblib.load(kmeans_path)
|
| 31 |
+
print("K-Means model loaded")
|
| 32 |
+
else:
|
| 33 |
+
print(f"File not found: {kmeans_path}")
|
| 34 |
+
kmeans_model = None
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print(f"Could not load K-Means model: {e}")
|
| 37 |
+
kmeans_model = None
|
| 38 |
+
|
| 39 |
+
try:
|
| 40 |
+
#load CLV prediction model (FIX 2: Changed 'csv' to 'clv')
|
| 41 |
+
clv_path = os.path.join(models_path, "clv_model.pkl")
|
| 42 |
+
if os.path.exists(clv_path):
|
| 43 |
+
clv_model = joblib.load(clv_path)
|
| 44 |
+
print("CLV model loaded")
|
| 45 |
+
else:
|
| 46 |
+
print(f"File not found: {clv_path}")
|
| 47 |
+
clv_model = None
|
| 48 |
+
except Exception as e:
|
| 49 |
+
print(f"Could not load CLV model: {e}")
|
| 50 |
+
clv_model = None
|
| 51 |
+
|
| 52 |
+
try:
|
| 53 |
+
#load scaler
|
| 54 |
+
scaler_path = os.path.join(models_path, "scaler.pkl")
|
| 55 |
+
if os.path.exists(scaler_path):
|
| 56 |
+
scaler = joblib.load(scaler_path)
|
| 57 |
+
print("Scaler loaded")
|
| 58 |
+
else:
|
| 59 |
+
print(f"File not found: {scaler_path}")
|
| 60 |
+
scaler = None
|
| 61 |
+
except Exception as e:
|
| 62 |
+
print(f"Could not load scaler: {e}")
|
| 63 |
+
scaler = None
|
| 64 |
+
|
| 65 |
+
return kmeans_model is not None or clv_model is not None
|
| 66 |
+
|
| 67 |
+
def predict_segment(recency, frequency, monetary):
|
| 68 |
+
"""Predict customer segment using K-Means model"""
|
| 69 |
+
if kmeans_model is None or scaler is None:
|
| 70 |
+
return {"error": "Models not loaded"}
|
| 71 |
+
|
| 72 |
+
#create dataframe with correct feature order
|
| 73 |
+
customer_data = pd.DataFrame({
|
| 74 |
+
'Recency': [recency],
|
| 75 |
+
'Frequency': [frequency],
|
| 76 |
+
'Monetary': [monetary]
|
| 77 |
+
})
|
| 78 |
+
|
| 79 |
+
#scale the features
|
| 80 |
+
scaled_data = scaler.transform(customer_data)
|
| 81 |
+
|
| 82 |
+
#predict cluster
|
| 83 |
+
cluster = kmeans_model.predict(scaled_data)[0]
|
| 84 |
+
|
| 85 |
+
#map cluster to segment name
|
| 86 |
+
segment_map = {
|
| 87 |
+
0: "At-Risk Customers", # 2,396 customers - high recency, low frequency
|
| 88 |
+
1: "VIP Customers", # 1,024 customers - very high recency (lost customers)
|
| 89 |
+
2: "Loyal Regulars", # 145 customers - low recency, high frequency (YOUR BEST!)
|
| 90 |
+
3: "New/Occasional" # 723 customers - medium recency, medium frequency
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
return {
|
| 94 |
+
"cluster": int(cluster),
|
| 95 |
+
"segment": segment_map.get(cluster, "Unknown")
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
def predict_clv(features_dict):
|
| 99 |
+
"""
|
| 100 |
+
Predict Customer Lifetime Value
|
| 101 |
+
features_dict should contain all 9 features
|
| 102 |
+
"""
|
| 103 |
+
if clv_model is None:
|
| 104 |
+
return {"error": "CLV model not loaded"}
|
| 105 |
+
|
| 106 |
+
#expected feature order from your notebook
|
| 107 |
+
feature_columns = [
|
| 108 |
+
'frequency', 'recency', 'avg_quantity', 'avg_unit_price',
|
| 109 |
+
'avg_transaction', 'lifespan_days', 'avg_days_between_purchases',
|
| 110 |
+
'purchases_per_month', 'total_quantity'
|
| 111 |
+
]
|
| 112 |
+
|
| 113 |
+
#create list of features in correct order
|
| 114 |
+
features = []
|
| 115 |
+
for col in feature_columns:
|
| 116 |
+
features.append(features_dict.get(col, 0))
|
| 117 |
+
|
| 118 |
+
#reshape for prediction (1 sample with 9 features)
|
| 119 |
+
features_array = np.array(features).reshape(1, -1)
|
| 120 |
+
|
| 121 |
+
#predict
|
| 122 |
+
prediction = clv_model.predict(features_array)[0]
|
| 123 |
+
|
| 124 |
+
#determine value category (adjust bins based on model)
|
| 125 |
+
if prediction < 500:
|
| 126 |
+
category = "Low Value"
|
| 127 |
+
elif prediction < 2000:
|
| 128 |
+
category = "Medium Value"
|
| 129 |
+
elif prediction < 5000:
|
| 130 |
+
category = "High Value"
|
| 131 |
+
else:
|
| 132 |
+
category = "VIP"
|
| 133 |
+
|
| 134 |
+
return {
|
| 135 |
+
"predicted_clv": round(prediction, 2),
|
| 136 |
+
"value_category": category
|
| 137 |
+
}
|
app/models.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field
|
| 2 |
+
from typing import Optional, List
|
| 3 |
+
|
| 4 |
+
#request models
|
| 5 |
+
class CustomerFeatures(BaseModel):
|
| 6 |
+
"""Features for customer segmentation"""
|
| 7 |
+
recency: int = Field(..., description="Days since last purchase", ge=0)
|
| 8 |
+
frequency: int = Field(..., description="Number of purchases", ge=1)
|
| 9 |
+
monetary: float = Field(..., description="Total amount spent", ge=0)
|
| 10 |
+
|
| 11 |
+
class CLVFeatures(BaseModel):
|
| 12 |
+
"""Features for CLV prediction"""
|
| 13 |
+
frequency: int = Field(..., ge=1)
|
| 14 |
+
recency: int = Field(..., ge=0)
|
| 15 |
+
avg_quantity: float = Field(..., ge=0)
|
| 16 |
+
avg_unit_price: float = Field(..., ge=0)
|
| 17 |
+
avg_transaction: float = Field(..., ge=0)
|
| 18 |
+
lifespan_days: int = Field(..., ge=0)
|
| 19 |
+
avg_days_between_purchases: float = Field(..., ge=0)
|
| 20 |
+
purchases_per_month: float = Field(..., ge=0)
|
| 21 |
+
total_quantity: int = Field(..., ge=0)
|
| 22 |
+
|
| 23 |
+
class Config:
|
| 24 |
+
schema_extra = {
|
| 25 |
+
"example": {
|
| 26 |
+
"frequency": 12,
|
| 27 |
+
"recency": 7,
|
| 28 |
+
"avg_quantity": 3.5,
|
| 29 |
+
"avg_unit_price": 25.0,
|
| 30 |
+
"avg_transaction": 87.5,
|
| 31 |
+
"lifespan_days": 180,
|
| 32 |
+
"avg_days_between_purchases": 15,
|
| 33 |
+
"purchases_per_month": 2,
|
| 34 |
+
"total_quantity": 42
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
#response models
|
| 39 |
+
class SegmentResponse(BaseModel):
|
| 40 |
+
customer_id: Optional[int] = None
|
| 41 |
+
cluster: int
|
| 42 |
+
segment: str
|
| 43 |
+
recency: Optional[int] = None
|
| 44 |
+
frequency: Optional[int] = None
|
| 45 |
+
monetary: Optional[float] = None
|
| 46 |
+
|
| 47 |
+
class CLVResponse(BaseModel):
|
| 48 |
+
predicted_clv: float
|
| 49 |
+
value_category: str
|
| 50 |
+
|
| 51 |
+
class CustomerInfo(BaseModel):
|
| 52 |
+
customer_id: int
|
| 53 |
+
segment: str
|
| 54 |
+
value_category: str
|
| 55 |
+
total_orders: Optional[int] = None
|
| 56 |
+
total_revenue: Optional[float] = None
|
| 57 |
+
|
| 58 |
+
class HealthResponse(BaseModel):
|
| 59 |
+
status: str
|
| 60 |
+
models_loaded: dict
|
| 61 |
+
database: str
|
app/routes/__init__.py
ADDED
|
File without changes
|
app/routes/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (171 Bytes). View file
|
|
|
app/routes/__pycache__/customers.cpython-311.pyc
ADDED
|
Binary file (5.35 kB). View file
|
|
|
app/routes/__pycache__/predictions.cpython-311.pyc
ADDED
|
Binary file (2.36 kB). View file
|
|
|
app/routes/customers.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, Depends, HTTPException
|
| 2 |
+
from sqlalchemy.orm import Session
|
| 3 |
+
from sqlalchemy import text
|
| 4 |
+
from typing import List
|
| 5 |
+
from app.database import get_db
|
| 6 |
+
from app.models import CustomerInfo
|
| 7 |
+
|
| 8 |
+
router = APIRouter(prefix="/customers", tags=["Customers"])
|
| 9 |
+
|
| 10 |
+
@router.get("/", response_model=List[CustomerInfo])
|
| 11 |
+
def get_all_customers(
|
| 12 |
+
skip: int = 0,
|
| 13 |
+
limit: int = 100,
|
| 14 |
+
db: Session = Depends(get_db)
|
| 15 |
+
):
|
| 16 |
+
"""Get list of all customers with their segments"""
|
| 17 |
+
|
| 18 |
+
query = text("""
|
| 19 |
+
SELECT
|
| 20 |
+
t."CustomerID" as customer_id,
|
| 21 |
+
s."Segment" as segment,
|
| 22 |
+
v."value_category" as value_category,
|
| 23 |
+
COUNT(DISTINCT t."InvoiceNo") as total_orders,
|
| 24 |
+
SUM(t."TotalPrice") as total_revenue
|
| 25 |
+
FROM transactions t
|
| 26 |
+
LEFT JOIN customer_segments s ON t."CustomerID" = s."CustomerID"
|
| 27 |
+
LEFT JOIN clv_predictions v ON t."CustomerID" = v."CustomerID"
|
| 28 |
+
GROUP BY t."CustomerID", s."Segment", v."value_category"
|
| 29 |
+
ORDER BY total_revenue DESC
|
| 30 |
+
LIMIT :limit OFFSET :skip
|
| 31 |
+
""")
|
| 32 |
+
|
| 33 |
+
result = db.execute(query, {"limit": limit, "skip": skip}).fetchall()
|
| 34 |
+
|
| 35 |
+
customers = []
|
| 36 |
+
for row in result:
|
| 37 |
+
customers.append({
|
| 38 |
+
"customer_id": row[0],
|
| 39 |
+
"segment": row[1] or "Unknown",
|
| 40 |
+
"value_category": row[2] or "Unknown",
|
| 41 |
+
"total_orders": row[3],
|
| 42 |
+
"total_revenue": float(row[4]) if row[4] else 0
|
| 43 |
+
})
|
| 44 |
+
|
| 45 |
+
return customers
|
| 46 |
+
|
| 47 |
+
@router.get("/{customer_id}", response_model=CustomerInfo)
|
| 48 |
+
def get_customer_by_id(customer_id: int, db: Session = Depends(get_db)):
|
| 49 |
+
"""Get customer details by ID"""
|
| 50 |
+
|
| 51 |
+
query = text("""
|
| 52 |
+
SELECT
|
| 53 |
+
t."CustomerID" as customer_id,
|
| 54 |
+
s."Segment" as segment,
|
| 55 |
+
v."value_category" as value_category,
|
| 56 |
+
COUNT(DISTINCT t."InvoiceNo") as total_orders,
|
| 57 |
+
SUM(t."TotalPrice") as total_revenue
|
| 58 |
+
FROM transactions t
|
| 59 |
+
LEFT JOIN customer_segments s ON t."CustomerID" = s."CustomerID"
|
| 60 |
+
LEFT JOIN clv_predictions v ON t."CustomerID" = v."CustomerID"
|
| 61 |
+
WHERE t."CustomerID" = :customer_id
|
| 62 |
+
GROUP BY t."CustomerID", s."Segment", v."value_category"
|
| 63 |
+
""")
|
| 64 |
+
|
| 65 |
+
result = db.execute(query, {"customer_id": customer_id}).fetchone()
|
| 66 |
+
|
| 67 |
+
if not result:
|
| 68 |
+
raise HTTPException(status_code=404, detail="Customer not found")
|
| 69 |
+
|
| 70 |
+
return {
|
| 71 |
+
"customer_id": result[0],
|
| 72 |
+
"segment": result[1] or "Unknown",
|
| 73 |
+
"value_category": result[2] or "Unknown",
|
| 74 |
+
"total_orders": result[3],
|
| 75 |
+
"total_revenue": float(result[4]) if result[4] else 0
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
@router.get("/{customer_id}/transactions")
|
| 79 |
+
def get_customer_transactions(
|
| 80 |
+
customer_id: int,
|
| 81 |
+
limit: int = 50,
|
| 82 |
+
db: Session = Depends(get_db)
|
| 83 |
+
):
|
| 84 |
+
"""Get transaction history for a customer"""
|
| 85 |
+
|
| 86 |
+
query = text("""
|
| 87 |
+
SELECT
|
| 88 |
+
"InvoiceNo",
|
| 89 |
+
"InvoiceDate",
|
| 90 |
+
"StockCode",
|
| 91 |
+
"Description",
|
| 92 |
+
"Quantity",
|
| 93 |
+
"UnitPrice",
|
| 94 |
+
"TotalPrice"
|
| 95 |
+
FROM transactions
|
| 96 |
+
WHERE "CustomerID" = :customer_id
|
| 97 |
+
ORDER BY "InvoiceDate" DESC
|
| 98 |
+
LIMIT :limit
|
| 99 |
+
""")
|
| 100 |
+
|
| 101 |
+
result = db.execute(query, {"customer_id": customer_id, "limit": limit}).fetchall()
|
| 102 |
+
|
| 103 |
+
transactions = []
|
| 104 |
+
for row in result:
|
| 105 |
+
transactions.append({
|
| 106 |
+
"invoice_no": row[0],
|
| 107 |
+
"date": str(row[1]),
|
| 108 |
+
"stock_code": row[2],
|
| 109 |
+
"description": row[3],
|
| 110 |
+
"quantity": row[4],
|
| 111 |
+
"unit_price": float(row[5]),
|
| 112 |
+
"total_price": float(row[6])
|
| 113 |
+
})
|
| 114 |
+
|
| 115 |
+
return {
|
| 116 |
+
"customer_id": customer_id,
|
| 117 |
+
"transaction_count": len(transactions),
|
| 118 |
+
"transactions": transactions
|
| 119 |
+
}
|
app/routes/predictions.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, HTTPException
|
| 2 |
+
from app.models import CustomerFeatures, CLVFeatures, SegmentResponse, CLVResponse
|
| 3 |
+
from app.ml_models import predict_segment, predict_clv
|
| 4 |
+
|
| 5 |
+
router = APIRouter(prefix="/predict", tags=["Predictions"])
|
| 6 |
+
|
| 7 |
+
@router.post("/segment", response_model=SegmentResponse)
|
| 8 |
+
def segment_customer(features: CustomerFeatures):
|
| 9 |
+
"""Predict customer segment based on RFM features"""
|
| 10 |
+
|
| 11 |
+
result = predict_segment(
|
| 12 |
+
features.recency,
|
| 13 |
+
features.frequency,
|
| 14 |
+
features.monetary
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
if "error" in result:
|
| 18 |
+
raise HTTPException(status_code=500, detail=result["error"])
|
| 19 |
+
|
| 20 |
+
return {
|
| 21 |
+
"cluster": result["cluster"],
|
| 22 |
+
"segment": result["segment"],
|
| 23 |
+
"recency": features.recency,
|
| 24 |
+
"frequency": features.frequency,
|
| 25 |
+
"monetary": features.monetary
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
@router.post("/clv", response_model=CLVResponse)
|
| 29 |
+
def predict_customer_value(features: CLVFeatures):
|
| 30 |
+
"""Predict Customer Lifetime Value"""
|
| 31 |
+
|
| 32 |
+
# convert features to dictionary
|
| 33 |
+
features_dict = features.dict()
|
| 34 |
+
|
| 35 |
+
result = predict_clv(features_dict)
|
| 36 |
+
|
| 37 |
+
if "error" in result:
|
| 38 |
+
raise HTTPException(status_code=500, detail=result["error"])
|
| 39 |
+
|
| 40 |
+
return result
|
| 41 |
+
|
| 42 |
+
@router.get("/segment/{customer_id}")
|
| 43 |
+
def get_customer_segment_from_db(customer_id: int):
|
| 44 |
+
"""
|
| 45 |
+
Get pre-computed segment for a customer from database
|
| 46 |
+
This endpoint connects to PostgreSQL to get the segment
|
| 47 |
+
"""
|
| 48 |
+
# this will be implemented with database dependency
|
| 49 |
+
# for now, return a placeholder
|
| 50 |
+
return {"customer_id": customer_id, "message": "To be implemented with database"}
|
app/upload_to_neon.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
from sqlalchemy import create_engine
|
| 3 |
+
|
| 4 |
+
DATABASE_URL = "postgresql://neondb_owner:npg_meVF3arI6qWv@ep-gentle-frog-a170mdjt-pooler.ap-southeast-1.aws.neon.tech/neondb?sslmode=require&channel_binding=require"
|
| 5 |
+
|
| 6 |
+
print("Connecting to Neon...")
|
| 7 |
+
engine = create_engine(DATABASE_URL)
|
| 8 |
+
|
| 9 |
+
print("Loading CSV files...")
|
| 10 |
+
|
| 11 |
+
transactions = pd.read_csv('C:/Users/User/Desktop/ecommerce/hf_deploy/data/online_retail_cleaned.csv')
|
| 12 |
+
segments = pd.read_csv('C:/Users/User/Desktop/ecommerce/hf_deploy/data/customer_segments.csv')
|
| 13 |
+
clv = pd.read_csv('C:/Users/User/Desktop/ecommerce/hf_deploy/data/clv_predictions.csv')
|
| 14 |
+
rfm = pd.read_csv('C:/Users/User/Desktop/ecommerce/hf_deploy/data/features_rfm.csv')
|
| 15 |
+
daily = pd.read_csv('C:/Users/User/Desktop/ecommerce/hf_deploy/data/features_daily_sales.csv')
|
| 16 |
+
|
| 17 |
+
print(f"transactions: {len(transactions)} rows")
|
| 18 |
+
print(f"segments: {len(segments)} rows")
|
| 19 |
+
print(f"clv: {len(clv)} rows")
|
| 20 |
+
print(f"rfm: {len(rfm)} rows")
|
| 21 |
+
print(f"daily: {len(daily)} rows")
|
| 22 |
+
|
| 23 |
+
print("Uploading to Neon...")
|
| 24 |
+
|
| 25 |
+
transactions.to_sql('transactions', engine, if_exists='replace', index=False, chunksize=5000)
|
| 26 |
+
segments.to_sql('customer_segments', engine, if_exists='replace', index=False)
|
| 27 |
+
clv.to_sql('clv_predictions', engine, if_exists='replace', index=False)
|
| 28 |
+
rfm.to_sql('customer_rfm', engine, if_exists='replace', index=False)
|
| 29 |
+
daily.to_sql('daily_sales', engine, if_exists='replace', index=False)
|
| 30 |
+
|
| 31 |
+
print("ALL DATA UPLOADED!")
|
data/OnlineRetail.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d07aec9960083af2339975a3f9d3b26313b342dcd9f86cce0b919b1cde639a44
|
| 3 |
+
size 45580638
|
data/clv_predictions.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/customer_features_complete.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/customer_segments.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/features_daily_sales.csv
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Date,Revenue,Order,Quantity,DayOfWeek,Month,Year
|
| 2 |
+
2010-12-01,43616.49,120,23351,Wednesday,12,2010
|
| 3 |
+
2010-12-02,43252.53,136,27774,Thursday,12,2010
|
| 4 |
+
2010-12-03,22607.71,56,11719,Friday,12,2010
|
| 5 |
+
2010-12-05,31771.600000000002,87,16449,Sunday,12,2010
|
| 6 |
+
2010-12-06,28262.440000000002,94,15655,Monday,12,2010
|
| 7 |
+
2010-12-07,28955.37,71,12938,Tuesday,12,2010
|
| 8 |
+
2010-12-08,39248.82,111,21573,Wednesday,12,2010
|
| 9 |
+
2010-12-09,32523.260000000002,96,15831,Thursday,12,2010
|
| 10 |
+
2010-12-10,30671.88,72,15779,Friday,12,2010
|
| 11 |
+
2010-12-12,17305.77,43,10599,Sunday,12,2010
|
| 12 |
+
2010-12-13,27642.68,64,15335,Monday,12,2010
|
| 13 |
+
2010-12-14,28134.3,80,17496,Tuesday,12,2010
|
| 14 |
+
2010-12-15,28533.32,70,17868,Wednesday,12,2010
|
| 15 |
+
2010-12-16,43871.88,113,26413,Thursday,12,2010
|
| 16 |
+
2010-12-17,20046.56,55,11407,Friday,12,2010
|
| 17 |
+
2010-12-19,7417.39,18,3739,Sunday,12,2010
|
| 18 |
+
2010-12-20,19621.96,50,13445,Monday,12,2010
|
| 19 |
+
2010-12-21,15951.66,27,11068,Tuesday,12,2010
|
| 20 |
+
2010-12-22,4886.52,14,3074,Wednesday,12,2010
|
| 21 |
+
2010-12-23,5648.97,17,3222,Thursday,12,2010
|
| 22 |
+
2011-01-04,12125.460000000001,35,6959,Tuesday,1,2011
|
| 23 |
+
2011-01-05,25783.23,47,17291,Wednesday,1,2011
|
| 24 |
+
2011-01-06,33340.19,46,21298,Thursday,1,2011
|
| 25 |
+
2011-01-07,23797.79,47,15122,Friday,1,2011
|
| 26 |
+
2011-01-09,15778.2,48,8202,Sunday,1,2011
|
| 27 |
+
2011-01-10,15346.83,34,9387,Monday,1,2011
|
| 28 |
+
2011-01-11,20382.24,50,11653,Tuesday,1,2011
|
| 29 |
+
2011-01-12,17153.94,42,7899,Wednesday,1,2011
|
| 30 |
+
2011-01-13,15171.31,41,8312,Thursday,1,2011
|
| 31 |
+
2011-01-14,35555.22,38,20708,Friday,1,2011
|
| 32 |
+
2011-01-16,7242.06,25,4204,Sunday,1,2011
|
| 33 |
+
2011-01-17,16597.35,46,8714,Monday,1,2011
|
| 34 |
+
2011-01-18,10405.51,31,6403,Tuesday,1,2011
|
| 35 |
+
2011-01-19,21646.78,33,15902,Wednesday,1,2011
|
| 36 |
+
2011-01-20,15244.73,34,8185,Thursday,1,2011
|
| 37 |
+
2011-01-21,24279.76,34,12697,Friday,1,2011
|
| 38 |
+
2011-01-23,10400.25,27,5241,Sunday,1,2011
|
| 39 |
+
2011-01-24,17632.37,44,9601,Monday,1,2011
|
| 40 |
+
2011-01-25,24119.3,59,14519,Tuesday,1,2011
|
| 41 |
+
2011-01-26,17490.82,53,10455,Wednesday,1,2011
|
| 42 |
+
2011-01-27,22157.03,50,10950,Thursday,1,2011
|
| 43 |
+
2011-01-28,17273.5,37,9174,Friday,1,2011
|
| 44 |
+
2011-01-30,6615.75,24,3431,Sunday,1,2011
|
| 45 |
+
2011-01-31,18818.08,57,12426,Monday,1,2011
|
| 46 |
+
2011-02-01,26376.5,58,15013,Tuesday,2,2011
|
| 47 |
+
2011-02-02,17145.23,56,8931,Wednesday,2,2011
|
| 48 |
+
2011-02-03,20556.8,45,14954,Thursday,2,2011
|
| 49 |
+
2011-02-04,18114.31,45,11264,Friday,2,2011
|
| 50 |
+
2011-02-06,3457.11,11,2048,Sunday,2,2011
|
| 51 |
+
2011-02-07,13682.41,37,6362,Monday,2,2011
|
| 52 |
+
2011-02-08,14168.03,38,8693,Tuesday,2,2011
|
| 53 |
+
2011-02-09,11920.78,23,6145,Wednesday,2,2011
|
| 54 |
+
2011-02-10,14198.51,40,11784,Thursday,2,2011
|
| 55 |
+
2011-02-11,16343.460000000001,37,7179,Friday,2,2011
|
| 56 |
+
2011-02-13,5713.63,20,2756,Sunday,2,2011
|
| 57 |
+
2011-02-14,21884.63,35,11287,Monday,2,2011
|
| 58 |
+
2011-02-15,37784.65,53,22107,Tuesday,2,2011
|
| 59 |
+
2011-02-16,23499.22,62,15768,Wednesday,2,2011
|
| 60 |
+
2011-02-17,17674.79,55,12762,Thursday,2,2011
|
| 61 |
+
2011-02-18,14463.06,35,8642,Friday,2,2011
|
| 62 |
+
2011-02-20,9624.69,26,5353,Sunday,2,2011
|
| 63 |
+
2011-02-21,32801.73,33,20432,Monday,2,2011
|
| 64 |
+
2011-02-22,25957.56,49,17024,Tuesday,2,2011
|
| 65 |
+
2011-02-23,19348.07,53,12164,Wednesday,2,2011
|
| 66 |
+
2011-02-24,21472.89,53,11563,Thursday,2,2011
|
| 67 |
+
2011-02-25,17082.29,44,9835,Friday,2,2011
|
| 68 |
+
2011-02-27,9526.5,33,4870,Sunday,2,2011
|
| 69 |
+
2011-02-28,15107.02,51,8420,Monday,2,2011
|
| 70 |
+
2011-03-01,22407.87,56,11478,Tuesday,3,2011
|
| 71 |
+
2011-03-02,17044.49,41,8463,Wednesday,3,2011
|
| 72 |
+
2011-03-03,30385.05,46,18851,Thursday,3,2011
|
| 73 |
+
2011-03-04,17322.47,45,12942,Friday,3,2011
|
| 74 |
+
2011-03-06,9997.42,26,5048,Sunday,3,2011
|
| 75 |
+
2011-03-07,20285.68,58,11112,Monday,3,2011
|
| 76 |
+
2011-03-08,22230.61,44,13841,Tuesday,3,2011
|
| 77 |
+
2011-03-09,19187.87,51,10914,Wednesday,3,2011
|
| 78 |
+
2011-03-10,24431.72,52,15223,Thursday,3,2011
|
| 79 |
+
2011-03-11,17521.66,46,9248,Friday,3,2011
|
| 80 |
+
2011-03-13,4148.12,16,2749,Sunday,3,2011
|
| 81 |
+
2011-03-14,26009.25,48,16757,Monday,3,2011
|
| 82 |
+
2011-03-15,14936.43,41,7937,Tuesday,3,2011
|
| 83 |
+
2011-03-16,21820.66,50,12907,Wednesday,3,2011
|
| 84 |
+
2011-03-17,25098.94,56,14852,Thursday,3,2011
|
| 85 |
+
2011-03-18,23398.74,52,13258,Friday,3,2011
|
| 86 |
+
2011-03-20,20084.8,57,13630,Sunday,3,2011
|
| 87 |
+
2011-03-21,16051.05,48,9456,Monday,3,2011
|
| 88 |
+
2011-03-22,19064.29,41,13503,Tuesday,3,2011
|
| 89 |
+
2011-03-23,21578.8,59,13934,Wednesday,3,2011
|
| 90 |
+
2011-03-24,28728.34,68,16136,Thursday,3,2011
|
| 91 |
+
2011-03-25,21789.43,51,12002,Friday,3,2011
|
| 92 |
+
2011-03-27,9224.4,31,4529,Sunday,3,2011
|
| 93 |
+
2011-03-28,18887.66,58,11465,Monday,3,2011
|
| 94 |
+
2011-03-29,35078.52,50,22903,Tuesday,3,2011
|
| 95 |
+
2011-03-30,29587.420000000002,66,20143,Wednesday,3,2011
|
| 96 |
+
2011-03-31,25688.61,59,14984,Thursday,3,2011
|
| 97 |
+
2011-04-01,23670.98,67,17449,Friday,4,2011
|
| 98 |
+
2011-04-03,6918.5,19,5667,Sunday,4,2011
|
| 99 |
+
2011-04-04,23328.83,53,12867,Monday,4,2011
|
| 100 |
+
2011-04-05,22199.07,44,14011,Tuesday,4,2011
|
| 101 |
+
2011-04-06,12722.32,37,8025,Wednesday,4,2011
|
| 102 |
+
2011-04-07,16827.77,61,10100,Thursday,4,2011
|
| 103 |
+
2011-04-08,20852.85,64,12297,Friday,4,2011
|
| 104 |
+
2011-04-10,9913.98,32,5632,Sunday,4,2011
|
| 105 |
+
2011-04-11,20184.14,59,13133,Monday,4,2011
|
| 106 |
+
2011-04-12,24023.96,60,14607,Tuesday,4,2011
|
| 107 |
+
2011-04-13,23365.06,59,17713,Wednesday,4,2011
|
| 108 |
+
2011-04-14,34726.38,82,18238,Thursday,4,2011
|
| 109 |
+
2011-04-15,18021.481,41,11100,Friday,4,2011
|
| 110 |
+
2011-04-17,12725.5,42,8183,Sunday,4,2011
|
| 111 |
+
2011-04-18,22177.66,61,17224,Monday,4,2011
|
| 112 |
+
2011-04-19,17882.84,52,12461,Tuesday,4,2011
|
| 113 |
+
2011-04-20,25718.95,62,17856,Wednesday,4,2011
|
| 114 |
+
2011-04-21,27804.47,71,17081,Thursday,4,2011
|
| 115 |
+
2011-04-26,20666.41,58,14070,Tuesday,4,2011
|
| 116 |
+
2011-04-27,21950.7,60,17539,Wednesday,4,2011
|
| 117 |
+
2011-04-28,21323.29,55,13122,Thursday,4,2011
|
| 118 |
+
2011-05-01,6973.66,18,3819,Sunday,5,2011
|
| 119 |
+
2011-05-03,20767.66,58,11145,Tuesday,5,2011
|
| 120 |
+
2011-05-04,27532.0,62,17381,Wednesday,5,2011
|
| 121 |
+
2011-05-05,25232.670000000002,82,16231,Thursday,5,2011
|
| 122 |
+
2011-05-06,30786.7,77,18287,Friday,5,2011
|
| 123 |
+
2011-05-08,18867.4,63,10702,Sunday,5,2011
|
| 124 |
+
2011-05-09,21624.41,63,11682,Monday,5,2011
|
| 125 |
+
2011-05-10,29870.7,70,16399,Tuesday,5,2011
|
| 126 |
+
2011-05-11,30956.2,72,17091,Wednesday,5,2011
|
| 127 |
+
2011-05-12,57410.56,83,36597,Thursday,5,2011
|
| 128 |
+
2011-05-13,25885.83,67,13294,Friday,5,2011
|
| 129 |
+
2011-05-15,9680.05,30,4771,Sunday,5,2011
|
| 130 |
+
2011-05-16,32518.7,64,14655,Monday,5,2011
|
| 131 |
+
2011-05-17,45230.090000000004,72,25005,Tuesday,5,2011
|
| 132 |
+
2011-05-18,32882.87,71,18637,Wednesday,5,2011
|
| 133 |
+
2011-05-19,31553.510000000002,92,17180,Thursday,5,2011
|
| 134 |
+
2011-05-20,25489.66,69,15129,Friday,5,2011
|
| 135 |
+
2011-05-22,22531.09,61,12549,Sunday,5,2011
|
| 136 |
+
2011-05-23,27689.53,62,14831,Monday,5,2011
|
| 137 |
+
2011-05-24,20168.600000000002,60,10666,Tuesday,5,2011
|
| 138 |
+
2011-05-25,20492.93,59,11481,Wednesday,5,2011
|
| 139 |
+
2011-05-26,28310.82,61,14470,Thursday,5,2011
|
| 140 |
+
2011-05-27,18067.32,55,10539,Friday,5,2011
|
| 141 |
+
2011-05-29,7394.3,24,4124,Sunday,5,2011
|
| 142 |
+
2011-05-31,18194.65,51,10607,Tuesday,5,2011
|
| 143 |
+
2011-06-01,15390.89,37,9580,Wednesday,6,2011
|
| 144 |
+
2011-06-02,28104.56,42,13714,Thursday,6,2011
|
| 145 |
+
2011-06-03,12589.22,39,6792,Friday,6,2011
|
| 146 |
+
2011-06-05,25639.54,67,13501,Sunday,6,2011
|
| 147 |
+
2011-06-06,16290.98,56,9142,Monday,6,2011
|
| 148 |
+
2011-06-07,22908.74,70,15422,Tuesday,6,2011
|
| 149 |
+
2011-06-08,30192.13,82,18833,Wednesday,6,2011
|
| 150 |
+
2011-06-09,26422.38,81,23594,Thursday,6,2011
|
| 151 |
+
2011-06-10,19275.12,43,9773,Friday,6,2011
|
| 152 |
+
2011-06-12,12472.210000000001,38,9488,Sunday,6,2011
|
| 153 |
+
2011-06-13,18498.71,52,10304,Monday,6,2011
|
| 154 |
+
2011-06-14,22755.63,51,10896,Tuesday,6,2011
|
| 155 |
+
2011-06-15,43085.54,50,29050,Wednesday,6,2011
|
| 156 |
+
2011-06-16,31396.97,81,19373,Thursday,6,2011
|
| 157 |
+
2011-06-17,19059.23,46,11349,Friday,6,2011
|
| 158 |
+
2011-06-19,22442.18,60,15081,Sunday,6,2011
|
| 159 |
+
2011-06-20,26136.73,59,14873,Monday,6,2011
|
| 160 |
+
2011-06-21,19850.66,48,13127,Tuesday,6,2011
|
| 161 |
+
2011-06-22,21170.420000000002,57,15291,Wednesday,6,2011
|
| 162 |
+
2011-06-23,22556.81,69,14030,Thursday,6,2011
|
| 163 |
+
2011-06-24,16021.710000000001,45,9067,Friday,6,2011
|
| 164 |
+
2011-06-26,7082.49,26,3786,Sunday,6,2011
|
| 165 |
+
2011-06-27,13412.2,35,9163,Monday,6,2011
|
| 166 |
+
2011-06-28,30532.37,49,20926,Tuesday,6,2011
|
| 167 |
+
2011-06-29,12570.630000000001,39,7469,Wednesday,6,2011
|
| 168 |
+
2011-06-30,25433.96,64,14913,Thursday,6,2011
|
| 169 |
+
2011-07-01,12189.29,42,7454,Friday,7,2011
|
| 170 |
+
2011-07-03,6032.39,25,3117,Sunday,7,2011
|
| 171 |
+
2011-07-04,15003.17,35,8450,Monday,7,2011
|
| 172 |
+
2011-07-05,25992.260000000002,64,17634,Tuesday,7,2011
|
| 173 |
+
2011-07-06,22960.48,66,17816,Wednesday,7,2011
|
| 174 |
+
2011-07-07,27893.66,73,17654,Thursday,7,2011
|
| 175 |
+
2011-07-08,17558.3,46,10165,Friday,7,2011
|
| 176 |
+
2011-07-10,5993.87,24,4255,Sunday,7,2011
|
| 177 |
+
2011-07-11,20080.03,48,14139,Monday,7,2011
|
| 178 |
+
2011-07-12,16642.59,44,11029,Tuesday,7,2011
|
| 179 |
+
2011-07-13,19432.85,56,15284,Wednesday,7,2011
|
| 180 |
+
2011-07-14,30794.510000000002,69,16994,Thursday,7,2011
|
| 181 |
+
2011-07-15,11857.300000000001,37,6379,Friday,7,2011
|
| 182 |
+
2011-07-17,16958.6,50,10831,Sunday,7,2011
|
| 183 |
+
2011-07-18,22018.36,46,12712,Monday,7,2011
|
| 184 |
+
2011-07-19,46599.08,62,28409,Tuesday,7,2011
|
| 185 |
+
2011-07-20,26086.97,52,15042,Wednesday,7,2011
|
| 186 |
+
2011-07-21,29350.71,70,18885,Thursday,7,2011
|
| 187 |
+
2011-07-22,14633.77,37,8333,Friday,7,2011
|
| 188 |
+
2011-07-24,26796.920000000002,57,17578,Sunday,7,2011
|
| 189 |
+
2011-07-25,19687.31,53,14276,Monday,7,2011
|
| 190 |
+
2011-07-26,17293.001,46,12643,Tuesday,7,2011
|
| 191 |
+
2011-07-27,20623.32,44,12756,Wednesday,7,2011
|
| 192 |
+
2011-07-28,39094.69,78,26559,Thursday,7,2011
|
| 193 |
+
2011-07-29,17240.61,62,11219,Friday,7,2011
|
| 194 |
+
2011-07-31,26844.09,41,19271,Sunday,7,2011
|
| 195 |
+
2011-08-01,19808.4,39,11308,Monday,8,2011
|
| 196 |
+
2011-08-02,19027.05,41,13226,Tuesday,8,2011
|
| 197 |
+
2011-08-03,26617.77,62,16147,Wednesday,8,2011
|
| 198 |
+
2011-08-04,51621.97,79,34033,Thursday,8,2011
|
| 199 |
+
2011-08-05,19825.4,52,12230,Friday,8,2011
|
| 200 |
+
2011-08-07,7576.96,29,5185,Sunday,8,2011
|
| 201 |
+
2011-08-08,19758.62,38,12442,Monday,8,2011
|
| 202 |
+
2011-08-09,25057.12,40,15584,Tuesday,8,2011
|
| 203 |
+
2011-08-10,19861.56,43,12333,Wednesday,8,2011
|
| 204 |
+
2011-08-11,50482.57,63,35501,Thursday,8,2011
|
| 205 |
+
2011-08-12,17970.170000000002,46,11049,Friday,8,2011
|
| 206 |
+
2011-08-14,5718.57,25,3180,Sunday,8,2011
|
| 207 |
+
2011-08-15,17243.97,48,10181,Monday,8,2011
|
| 208 |
+
2011-08-16,16077.84,50,10474,Tuesday,8,2011
|
| 209 |
+
2011-08-17,37616.26,51,21520,Wednesday,8,2011
|
| 210 |
+
2011-08-18,51783.81,67,33641,Thursday,8,2011
|
| 211 |
+
2011-08-19,17339.59,55,10671,Friday,8,2011
|
| 212 |
+
2011-08-21,14566.84,38,8163,Sunday,8,2011
|
| 213 |
+
2011-08-22,25891.18,62,14341,Monday,8,2011
|
| 214 |
+
2011-08-23,22399.11,60,13707,Tuesday,8,2011
|
| 215 |
+
2011-08-24,37291.01,72,27157,Wednesday,8,2011
|
| 216 |
+
2011-08-25,22495.29,72,13175,Thursday,8,2011
|
| 217 |
+
2011-08-26,23113.24,43,16308,Friday,8,2011
|
| 218 |
+
2011-08-28,10805.03,37,6774,Sunday,8,2011
|
| 219 |
+
2011-08-30,8833.710000000001,23,4215,Tuesday,8,2011
|
| 220 |
+
2011-08-31,20540.84,42,11547,Wednesday,8,2011
|
| 221 |
+
2011-09-01,37370.15,76,27857,Thursday,9,2011
|
| 222 |
+
2011-09-02,26612.09,64,13877,Friday,9,2011
|
| 223 |
+
2011-09-04,17005.03,49,10905,Sunday,9,2011
|
| 224 |
+
2011-09-05,34810.7,67,21937,Monday,9,2011
|
| 225 |
+
2011-09-06,25495.82,61,14471,Tuesday,9,2011
|
| 226 |
+
2011-09-07,21967.420000000002,46,13731,Wednesday,9,2011
|
| 227 |
+
2011-09-08,23188.3,72,16041,Thursday,9,2011
|
| 228 |
+
2011-09-09,25142.15,58,16036,Friday,9,2011
|
| 229 |
+
2011-09-11,35511.67,75,21146,Sunday,9,2011
|
| 230 |
+
2011-09-12,27989.4,67,16438,Monday,9,2011
|
| 231 |
+
2011-09-13,48162.25,63,35029,Tuesday,9,2011
|
| 232 |
+
2011-09-14,22027.95,65,13438,Wednesday,9,2011
|
| 233 |
+
2011-09-15,43854.57,77,21965,Thursday,9,2011
|
| 234 |
+
2011-09-16,23248.98,46,14126,Friday,9,2011
|
| 235 |
+
2011-09-18,15745.73,27,8994,Sunday,9,2011
|
| 236 |
+
2011-09-19,45087.42,68,27472,Monday,9,2011
|
| 237 |
+
2011-09-20,40861.91,63,20444,Tuesday,9,2011
|
| 238 |
+
2011-09-21,37624.51,67,19632,Wednesday,9,2011
|
| 239 |
+
2011-09-22,57869.36,111,32816,Thursday,9,2011
|
| 240 |
+
2011-09-23,31781.100000000002,58,19800,Friday,9,2011
|
| 241 |
+
2011-09-25,31372.661,75,19504,Sunday,9,2011
|
| 242 |
+
2011-09-26,29329.841,65,15312,Monday,9,2011
|
| 243 |
+
2011-09-27,29274.36,75,19533,Tuesday,9,2011
|
| 244 |
+
2011-09-28,36767.89,80,23555,Wednesday,9,2011
|
| 245 |
+
2011-09-29,44729.37,103,26974,Thursday,9,2011
|
| 246 |
+
2011-09-30,36265.44,68,20064,Friday,9,2011
|
| 247 |
+
2011-10-02,11582.95,34,8375,Sunday,10,2011
|
| 248 |
+
2011-10-03,54053.0,62,23895,Monday,10,2011
|
| 249 |
+
2011-10-04,35497.2,74,20978,Tuesday,10,2011
|
| 250 |
+
2011-10-05,64121.43,94,44188,Wednesday,10,2011
|
| 251 |
+
2011-10-06,53076.4,116,30407,Thursday,10,2011
|
| 252 |
+
2011-10-07,40788.15,85,24218,Friday,10,2011
|
| 253 |
+
2011-10-09,12466.81,38,7534,Sunday,10,2011
|
| 254 |
+
2011-10-10,41700.32,90,24814,Monday,10,2011
|
| 255 |
+
2011-10-11,41067.01,78,23189,Tuesday,10,2011
|
| 256 |
+
2011-10-12,27731.37,82,16198,Wednesday,10,2011
|
| 257 |
+
2011-10-13,33039.18,74,17897,Thursday,10,2011
|
| 258 |
+
2011-10-14,32124.68,70,17215,Friday,10,2011
|
| 259 |
+
2011-10-16,22010.96,35,8770,Sunday,10,2011
|
| 260 |
+
2011-10-17,47005.24,85,30141,Monday,10,2011
|
| 261 |
+
2011-10-18,37302.4,77,26900,Tuesday,10,2011
|
| 262 |
+
2011-10-19,31265.46,76,18264,Wednesday,10,2011
|
| 263 |
+
2011-10-20,59819.9,85,39169,Thursday,10,2011
|
| 264 |
+
2011-10-21,38211.26,61,22304,Friday,10,2011
|
| 265 |
+
2011-10-23,12339.16,42,7073,Sunday,10,2011
|
| 266 |
+
2011-10-24,35887.840000000004,72,22633,Monday,10,2011
|
| 267 |
+
2011-10-25,33523.95,72,24944,Tuesday,10,2011
|
| 268 |
+
2011-10-26,30710.52,91,18769,Wednesday,10,2011
|
| 269 |
+
2011-10-27,41515.020000000004,101,25355,Thursday,10,2011
|
| 270 |
+
2011-10-28,34223.85,64,20120,Friday,10,2011
|
| 271 |
+
2011-10-30,34571.23,96,20068,Sunday,10,2011
|
| 272 |
+
2011-10-31,32146.99,63,15170,Monday,10,2011
|
| 273 |
+
2011-11-01,29132.81,76,16577,Tuesday,11,2011
|
| 274 |
+
2011-11-02,37774.7,82,23671,Wednesday,11,2011
|
| 275 |
+
2011-11-03,45865.61,98,27170,Thursday,11,2011
|
| 276 |
+
2011-11-04,54605.24,86,33248,Friday,11,2011
|
| 277 |
+
2011-11-06,42941.340000000004,101,23305,Sunday,11,2011
|
| 278 |
+
2011-11-07,28779.24,90,16439,Monday,11,2011
|
| 279 |
+
2011-11-08,38295.12,99,21670,Tuesday,11,2011
|
| 280 |
+
2011-11-09,57203.98,118,35122,Wednesday,11,2011
|
| 281 |
+
2011-11-10,67815.13,124,37067,Thursday,11,2011
|
| 282 |
+
2011-11-11,37081.37,93,23939,Friday,11,2011
|
| 283 |
+
2011-11-13,28607.78,83,19764,Sunday,11,2011
|
| 284 |
+
2011-11-14,56253.35,104,31846,Monday,11,2011
|
| 285 |
+
2011-11-15,44627.48,104,25470,Tuesday,11,2011
|
| 286 |
+
2011-11-16,48439.76,124,29156,Wednesday,11,2011
|
| 287 |
+
2011-11-17,54760.3,136,30090,Thursday,11,2011
|
| 288 |
+
2011-11-18,36751.25,103,20299,Friday,11,2011
|
| 289 |
+
2011-11-20,30190.920000000002,98,18765,Sunday,11,2011
|
| 290 |
+
2011-11-21,45333.13,96,24567,Monday,11,2011
|
| 291 |
+
2011-11-22,46388.89,129,30635,Tuesday,11,2011
|
| 292 |
+
2011-11-23,68279.87,130,37763,Wednesday,11,2011
|
| 293 |
+
2011-11-24,38579.11,109,22923,Thursday,11,2011
|
| 294 |
+
2011-11-25,25047.46,73,13736,Friday,11,2011
|
| 295 |
+
2011-11-27,17300.96,56,10537,Sunday,11,2011
|
| 296 |
+
2011-11-28,46714.91,114,26703,Monday,11,2011
|
| 297 |
+
2011-11-29,43356.58,124,23087,Tuesday,11,2011
|
| 298 |
+
2011-11-30,41481.23,99,24454,Wednesday,11,2011
|
| 299 |
+
2011-12-01,44533.99,118,24857,Thursday,12,2011
|
| 300 |
+
2011-12-02,40841.49,112,23011,Friday,12,2011
|
| 301 |
+
2011-12-04,20375.96,62,11435,Sunday,12,2011
|
| 302 |
+
2011-12-05,55647.45,116,37937,Monday,12,2011
|
| 303 |
+
2011-12-06,43842.44,110,27459,Tuesday,12,2011
|
| 304 |
+
2011-12-07,51918.71,100,34408,Wednesday,12,2011
|
| 305 |
+
2011-12-08,39896.75,112,23745,Thursday,12,2011
|
| 306 |
+
2011-12-09,15879.68,40,9587,Friday,12,2011
|
data/features_products.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/features_rfm.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/online_retail_cleaned.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:163b13e0c447277f6134967c6f1fc6b9bd9862e8028f3c5affbdd9e4ecb164df
|
| 3 |
+
size 47685118
|
models/clv_model.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1cedd59c79e59a346915e0930778475acbf92fce26e74634f5d3e2992ea86476
|
| 3 |
+
size 10266689
|
models/kmeans_model.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9a705465318e98090a96563bf874299fb9cc8fe60c9a74664c58e7e0e0b22484
|
| 3 |
+
size 17975
|
models/scaler.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:54b59ba462d0214c5673895b419bfba08f73ecfa9a3f98b668c17a4a8d124ec2
|
| 3 |
+
size 927
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.104.1
|
| 2 |
+
uvicorn==0.24.0
|
| 3 |
+
sqlalchemy==2.0.23
|
| 4 |
+
psycopg2-binary==2.9.9
|
| 5 |
+
pandas==2.2.0
|
| 6 |
+
numpy==1.26.4
|
| 7 |
+
joblib==1.3.2
|
| 8 |
+
scikit-learn==1.8.0
|