Spaces:
Sleeping
Sleeping
| from supabase_client import supabase | |
| import os | |
| import random | |
| import numpy as np | |
| import pandas as pd | |
| from datetime import datetime, timedelta | |
| from dotenv import load_dotenv | |
| from supabase import create_client, Client | |
| from sklearn.preprocessing import LabelEncoder, MinMaxScaler | |
| import tensorflow as tf | |
| from tensorflow.keras.models import Model, load_model | |
| from tensorflow.keras.layers import Input, LSTM, Dense | |
| import uuid | |
| import joblib | |
| from pathlib import Path | |
| # =============================== | |
| # 3. CONSTANTS & ENCODING | |
| # =============================== | |
| MODEL_PATH = "/tmp/global_model.h5" | |
| DATA_PATH = "/tmp/initial_data.csv" | |
| SCALER_PATH = "/tmp/scaler.pkl" | |
| REGIONS = ['urban', 'rural'] | |
| SEASONS = ['winter', 'spring', 'summer', 'autumn'] | |
| EVENTS = ['normal', 'fasting', 'guests', 'sickness', 'travel', 'meal_off'] | |
| REGION_MULTIPLIER = {'urban': 1.0, 'rural': 1.1} | |
| SEASON_MULTIPLIER = {'winter': 1.1, 'spring': 1.0, 'summer': 0.9, 'autumn': 1.0} | |
| EVENT_MULTIPLIER = {'normal': 1.0, 'fasting': 0.7, 'guests': 1.3, 'sickness': 0.5, 'travel': 0.3, 'meal_off': 0.2} | |
| BASE_CONSUMPTION = { | |
| 'rice': {'adult_male': 0.3, 'adult_female': 0.25, 'child': 0.15}, | |
| 'milk': {'adult_male': 0.2, 'adult_female': 0.18, 'child': 0.3}, | |
| 'potato': {'adult_male': 0.25, 'adult_female': 0.2, 'child': 0.15}, | |
| 'onion': {'adult_male': 0.1, 'adult_female': 0.1, 'child': 0.05} | |
| } | |
| le_region = LabelEncoder().fit(REGIONS) | |
| le_season = LabelEncoder().fit(SEASONS) | |
| le_event = LabelEncoder().fit(EVENTS) | |
| le_product = None | |
| scaler = None | |
| # =============================== | |
| # 4. UTILITIES & DATA FUNCTIONS | |
| # =============================== | |
| def get_season(date): | |
| month = date.month | |
| if month in [12, 1, 2]: return 'winter' | |
| elif month in [3, 4, 5]: return 'spring' | |
| elif month in [6, 7, 8]: return 'summer' | |
| return 'autumn' | |
| def generate_family(): | |
| return { | |
| 'adult_male': random.randint(1, 3), | |
| 'adult_female': random.randint(1, 3), | |
| 'child': random.randint(0, 3) | |
| }, random.choice(REGIONS) | |
| def calculate_base_consumption(fam, region, season, event, product): | |
| base = BASE_CONSUMPTION.get(product, {'adult_male': 0.1, 'adult_female': 0.1, 'child': 0.05}) | |
| total = sum(base[k]*fam.get(k, 0) for k in base) | |
| total *= REGION_MULTIPLIER.get(region, 1.0) | |
| total *= SEASON_MULTIPLIER.get(season, 1.0) | |
| total *= EVENT_MULTIPLIER.get(event, 1.0) | |
| total *= np.random.normal(1, 0.05) | |
| return max(total, 0.01) | |
| def generate_data(products, families=3, days=180): | |
| data = [] | |
| for _ in range(families): | |
| fam, region = generate_family() | |
| start = datetime.today() - timedelta(days=days) | |
| for d in range(days): | |
| date = start + timedelta(days=d) | |
| season = get_season(date) | |
| event = random.choices(EVENTS, weights=[70,5,5,5,5,10], k=1)[0] | |
| for prod in products: | |
| cons = calculate_base_consumption(fam, region, season, event, prod) | |
| stock = random.uniform(1.0, 10.0) | |
| pred_days = stock / cons if cons > 0 else 1 | |
| act_days = pred_days * np.random.normal(1, 0.1) | |
| finish_error = int(act_days - pred_days) | |
| data.append({ | |
| 'date': date.strftime('%Y-%m-%d'), | |
| 'product': prod, 'region': region, 'season': season, 'event': event, | |
| 'adult_male': fam['adult_male'], 'adult_female': fam['adult_female'], 'child': fam['child'], | |
| 'consumption': cons, | |
| 'stock_quantity': stock, | |
| 'finish_error': finish_error, 'finish_days': act_days | |
| }) | |
| return pd.DataFrame(data) | |
| def is_new_product(product, existing_df): | |
| return product not in existing_df['product'].unique() | |
| def generate_and_append_new_product(product, csv_path=DATA_PATH): | |
| print(f"Generating synthetic data for new product: {product}") | |
| new_df = generate_data([product], families=3, days=180) | |
| if os.path.exists(csv_path): | |
| existing = pd.read_csv(csv_path) | |
| combined = pd.concat([existing, new_df], ignore_index=True) | |
| else: | |
| combined = new_df | |
| combined.to_csv(csv_path, index=False) | |
| print(f"Product '{product}' added to dataset.") | |
| def create_user_if_not_exists(user_id, user_name="test_user", email="test@example.com"): | |
| """Create a user if they don't exist in the database""" | |
| try: | |
| response = supabase.table("users").select("user_id").eq("user_id", user_id).execute() | |
| if response.data: | |
| print(f"User {user_id} already exists") | |
| return True | |
| new_user = { | |
| "user_id": user_id, | |
| "user_name": user_name, | |
| "email": email, | |
| "region": "urban", | |
| "adult_male": 1, | |
| "adult_female": 1, | |
| "child": 0 | |
| } | |
| response = supabase.table("users").insert(new_user).execute() | |
| print(f"Created new user: {user_id}") | |
| return True | |
| except Exception as e: | |
| print(f"Error creating user: {e}") | |
| return False | |
| def fetch_feedback_for_user(user_id): | |
| response = supabase.table("feedback_data").select("*").eq("user_id", user_id).execute() | |
| return pd.DataFrame(response.data) if response.data else pd.DataFrame() | |
| def insert_feedback(user_id, df): | |
| if not create_user_if_not_exists(user_id): | |
| print("Failed to create user, cannot insert feedback") | |
| return | |
| df['user_id'] = user_id | |
| response = supabase.table("feedback_data").insert(df.to_dict(orient="records")).execute() | |
| print("Feedback inserted successfully") | |
| def get_product_id(product_name): | |
| res = supabase.table("products").select("product_id").eq("product_name", product_name).execute() | |
| if res.data: | |
| return res.data[0]['product_id'] | |
| new_id = str(uuid.uuid4()) | |
| supabase.table("products").insert({ | |
| 'product_id': new_id, | |
| 'product_name': product_name, | |
| 'unit': 'unit' | |
| }).execute() | |
| return new_id | |
| def store_predictions(user_id, predictions, user_input): | |
| rows = [] | |
| for product, result in predictions.items(): | |
| product_id = get_product_id(product) | |
| row = { | |
| 'user_id': user_id, | |
| 'product_id': product_id, | |
| 'prediction_date': datetime.today().date().isoformat(), | |
| 'predicted_consumption': result['predicted_consumption'], | |
| 'predicted_finish_days': result['predicted_finish_days'], | |
| 'predicted_finish_date': result['predicted_finish_date'], | |
| 'predicted_error': result['predicted_finish_error'], | |
| 'stock_quantity': user_input['stock'][product], | |
| } | |
| rows.append(row) | |
| supabase.table("prediction_outputs").insert(rows).execute() | |
| print("✅ Predictions stored successfully") | |
| # =============================== | |
| # 5. MODELING | |
| # =============================== | |
| def prepare_data(df, products): | |
| global le_product, scaler | |
| le_product = LabelEncoder().fit(products) | |
| df['region_enc'] = le_region.transform(df['region']) | |
| df['season_enc'] = le_season.transform(df['season']) | |
| df['event_enc'] = le_event.transform(df['event']) | |
| df['product_enc'] = le_product.transform(df['product']) | |
| df['date'] = pd.to_datetime(df['date']) | |
| df = df.sort_values('date') | |
| if scaler is None: | |
| scaler_local = MinMaxScaler() | |
| df[['adult_male','adult_female','child','consumption','stock_quantity']] = scaler_local.fit_transform(df[['adult_male','adult_female','child','consumption','stock_quantity']]) | |
| else: | |
| scaler_local = scaler | |
| df[['adult_male','adult_female','child','consumption','stock_quantity']] = scaler_local.transform(df[['adult_male','adult_female','child','consumption','stock_quantity']]) | |
| X, y1, y2, y3 = [], [], [], [] | |
| seq_len = 7 | |
| for p in df['product_enc'].unique(): | |
| sub = df[df['product_enc'] == p].reset_index(drop=True) | |
| feats = sub[['adult_male','adult_female','child','consumption','stock_quantity','region_enc','season_enc','event_enc','product_enc']].values | |
| c = sub['consumption'].values | |
| err = sub['finish_error'].values | |
| days = sub['finish_days'].values | |
| for i in range(len(sub) - seq_len): | |
| X.append(feats[i:i + seq_len]) | |
| y1.append(c[i + seq_len]) | |
| y2.append(err[i + seq_len]) | |
| y3.append(days[i + seq_len]) | |
| return np.array(X), np.array(y1), np.array(y2), np.array(y3), scaler_local | |
| def build_model(input_shape): | |
| inp = Input(shape=input_shape) # (7, 9) | |
| x = LSTM(64)(inp) | |
| x = Dense(32, activation='relu')(x) | |
| out1 = Dense(1, name='daily_consumption_output')(x) | |
| out2 = Dense(1, name='finish_error_output')(x) | |
| out3 = Dense(1, name='finish_days_output')(x) | |
| model = Model(inputs=inp, outputs=[out1, out2, out3]) | |
| model.compile(optimizer='adam', loss=tf.keras.losses.MeanSquaredError()) | |
| return model | |
| def load_or_train_model(): | |
| global scaler | |
| model_path = MODEL_PATH | |
| if not os.path.exists(DATA_PATH): | |
| pd.DataFrame(columns=[ | |
| 'date', 'product', 'region', 'season', 'event', | |
| 'adult_male', 'adult_female', 'child', | |
| 'consumption', 'stock_quantity', 'finish_error', 'finish_days' | |
| ]).to_csv(DATA_PATH, index=False) | |
| df = pd.read_csv(DATA_PATH) | |
| if df.empty: | |
| df = generate_data(list(BASE_CONSUMPTION.keys())) | |
| df.to_csv(DATA_PATH, index=False) | |
| products = df['product'].unique().tolist() | |
| X, y1, y2, y3, scaler_obj = prepare_data(df, products) | |
| scaler = scaler_obj | |
| if os.path.exists(model_path): | |
| os.remove(model_path) | |
| model = build_model((X.shape[1], X.shape[2])) | |
| model.fit( | |
| X, | |
| { | |
| 'daily_consumption_output': y1, | |
| 'finish_error_output': y2, | |
| 'finish_days_output': y3 | |
| }, | |
| epochs=10, | |
| batch_size=32, | |
| validation_split=0.1 | |
| ) | |
| model.save(model_path) | |
| joblib.dump(scaler, SCALER_PATH) | |
| print("✅ Model trained and saved.") | |
| print("✅ Scaler saved to scaler.pkl.") | |
| return model | |
| def retrain_model_with_feedback(user_id): | |
| base_df = pd.read_csv(DATA_PATH) | |
| feedback_df = fetch_feedback_for_user(user_id) | |
| if feedback_df.empty: | |
| print("No feedback found for user:", user_id) | |
| return {"message": f"No feedback found for user {user_id}"} | |
| combined_df = pd.concat([base_df, feedback_df], ignore_index=True) | |
| X, y1, y2, y3, scaler_obj = prepare_data(combined_df, combined_df['product'].unique().tolist()) | |
| model = build_model((X.shape[1], X.shape[2])) | |
| model.fit( | |
| X, | |
| {'daily_consumption_output': y1, | |
| 'finish_error_output': y2, | |
| 'finish_days_output': y3}, | |
| epochs=10, | |
| batch_size=32, | |
| validation_split=0.1, | |
| ) | |
| model.save(MODEL_PATH) | |
| joblib.dump(scaler_obj, SCALER_PATH) | |
| print("Retrained model saved.") | |
| return {"message": f"Retraining complete for user {user_id}"} | |
| # =============================== | |
| # 6. PREDICTION | |
| # =============================== | |
| def predict_user_input(user_input): | |
| global le_product, scaler, ml_model | |
| initial_df = pd.read_csv(DATA_PATH) | |
| predictions = {} | |
| for product in user_input['stock'].keys(): | |
| if is_new_product(product, initial_df): | |
| generate_and_append_new_product(product) | |
| initial_df = pd.read_csv(DATA_PATH) | |
| products = initial_df['product'].unique().tolist() | |
| le_product = LabelEncoder().fit(products) | |
| product_enc = le_product.transform([product])[0] | |
| vec = [] | |
| for _ in range(7): | |
| base = calculate_base_consumption( | |
| user_input['family'], | |
| user_input['region'], | |
| user_input['season'], | |
| user_input['event'], | |
| product | |
| ) | |
| stock_val = user_input['stock'][product] | |
| raw = [ | |
| user_input['family']['adult_male'], | |
| user_input['family']['adult_female'], | |
| user_input['family']['child'], | |
| base, | |
| stock_val | |
| ] | |
| df_input = pd.DataFrame( | |
| [raw], | |
| columns=['adult_male', 'adult_female', 'child', 'consumption', 'stock_quantity'] | |
| ) | |
| raw_scaled = scaler.transform(df_input)[0] | |
| region_enc = le_region.transform([user_input['region']])[0] | |
| season_enc = le_season.transform([user_input['season']])[0] | |
| event_enc = le_event.transform([user_input['event']])[0] | |
| features = list(raw_scaled) + [region_enc, season_enc, event_enc, product_enc] | |
| vec.append(features) | |
| vec = np.array(vec)[np.newaxis, :, :] # shape = (1, 7, 9) | |
| y1, y2, y3 = ml_model.predict(vec, verbose=0) | |
| # -------- INVERSE TRANSFORM START -------- | |
| stock_val = user_input['stock'][product] | |
| df_temp = pd.DataFrame([[ | |
| user_input['family']['adult_male'], | |
| user_input['family']['adult_female'], | |
| user_input['family']['child'], | |
| 0, # placeholder | |
| stock_val | |
| ]], columns=['adult_male', 'adult_female', 'child', 'consumption', 'stock_quantity']) | |
| scaled_temp = scaler.transform(df_temp) | |
| scaled_temp[0][3] = y1[0][0] # Replace scaled 'consumption' with predicted | |
| unscaled = scaler.inverse_transform(scaled_temp)[0] | |
| daily = float(unscaled[3]) # actual predicted consumption (inverse-transformed) | |
| # -------- INVERSE TRANSFORM END -------- | |
| error = float(y2[0][0]) | |
| days = float(y3[0][0]) | |
| finish_date = datetime.today() + timedelta(days=days) | |
| predictions[product] = { | |
| 'predicted_consumption': round(daily, 3), | |
| 'predicted_finish_days': round(days, 2), | |
| 'predicted_finish_date': finish_date.strftime('%Y-%m-%d'), | |
| 'predicted_finish_error': round(error, 2) | |
| } | |
| return predictions | |
| # =============================== | |
| # 8. GLOBAL LOADING FOR FASTAPI | |
| # =============================== | |
| if os.path.exists(MODEL_PATH): | |
| ml_model = tf.keras.models.load_model(MODEL_PATH) | |
| print("✅ Loaded trained model for prediction.") | |
| else: | |
| ml_model = load_or_train_model() | |
| scaler = joblib.load(SCALER_PATH) | |
| print("✅ Loaded scaler.") | |
| # =============================== | |
| # 7. EXAMPLE RUN | |
| # =============================== | |
| if __name__ == "__main__": | |
| model = load_or_train_model() | |
| user_input = { | |
| "family": {"adult_male": 2, "adult_female": 2, "child": 1}, | |
| "region": "urban", | |
| "season": "summer", | |
| "event": "normal", | |
| "stock": {"rice": 5, "milk": 3, "chicken": 4} # 'chicken' is new | |
| } | |
| results = predict_user_input(user_input) | |
| print(pd.DataFrame(results).T) | |
| feedback = pd.DataFrame([{ | |
| 'date': datetime.today().strftime('%Y-%m-%d'), | |
| 'product': k, | |
| 'region': user_input['region'], | |
| 'season': user_input['season'], | |
| 'event': user_input['event'], | |
| 'adult_male': user_input['family']['adult_male'], | |
| 'adult_female': user_input['family']['adult_female'], | |
| 'child': user_input['family']['child'], | |
| 'consumption': v['predicted_consumption'], | |
| 'finish_error': v['predicted_finish_error'], | |
| 'finish_days': v['predicted_finish_days'], | |
| 'stock_quantity': user_input['stock'][k] | |
| } for k, v in results.items()]) | |
| user_uuid = str(uuid.uuid5(uuid.NAMESPACE_DNS, "user001")) | |
| try: | |
| insert_feedback(user_uuid, feedback) | |
| store_predictions(user_uuid, results, user_input) | |
| print("Data insertion completed successfully!") | |
| except Exception as e: | |
| print(f"Error during data insertion: {e}") | |