Spaces:
Sleeping
Sleeping
| import numpy as np | |
| import pandas as pd | |
| from tensorflow.keras.models import load_model | |
| import joblib | |
| from openai import OpenAI | |
| from dotenv import load_dotenv | |
| import os | |
| import json | |
| import gradio as gr | |
| import logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| load_dotenv() | |
| API = os.environ.get("OPENROUTER_API_KEY") | |
| logger.info(f"API Key loaded: {bool(API)}") | |
| # Baselines | |
| BASELINE_LOWER = { | |
| 'Household': 30, | |
| 'Food': 40, | |
| 'Shopping': 7, | |
| 'Transportation': 5, | |
| 'Health & Fitness': 5, | |
| 'Entertainment': 5, | |
| 'Beauty': 4, | |
| 'Investment': 4, | |
| } | |
| BASELINE_UPPER = { | |
| 'Household': 11, | |
| 'Food': 10, | |
| 'Shopping': 13, | |
| 'Transportation': 11, | |
| 'Health & Fitness': 10, | |
| 'Entertainment': 18, | |
| 'Beauty': 8, | |
| 'Investment': 19, | |
| } | |
| # Load model and scaler | |
| def load_financial_model( | |
| model_path='model/fuzz_dnn_full_model.keras', | |
| scaler_path='model/fuzzy_dnn_scaler.pkl', | |
| ): | |
| logger.info("Starting to load model and scaler") | |
| try: | |
| model = load_model(model_path) | |
| scaler = joblib.load(scaler_path) | |
| logger.info("Model and scaler loaded successfully") | |
| return model, scaler | |
| except Exception as e: | |
| print(f"Error loading model or scaler: {e}") | |
| logger.error(f"Error loading model or scaler: {e}") | |
| return None, None | |
| # Prepare features | |
| def prepare_features(df): | |
| df['spend_deviation_ratio'] = df['Percent_Spend'] / (df['Deviation'].abs() + 1) | |
| return df[['Percent_Spend', 'Deviation', 'spend_deviation_ratio']] | |
| # Determine income level | |
| def determine_income_level(total_spending): | |
| return 'upper' if total_spending >= 5000 else 'lower' | |
| # Predict spending pattern | |
| def predict_spending_pattern(model, scaler, input_data): | |
| total_spending = sum(input_data.values()) | |
| income_level = determine_income_level(total_spending) | |
| baseline = BASELINE_UPPER if income_level == 'upper' else BASELINE_LOWER | |
| percent_spend = {k: (v / total_spending) * 100 for k, v in input_data.items()} | |
| rows = [] | |
| for category, spend_percent in percent_spend.items(): | |
| deviation = spend_percent - baseline.get(category, 0) | |
| rows.append( | |
| { | |
| 'Category': category, | |
| 'Percent_Spend': spend_percent, | |
| 'Deviation': deviation, | |
| } | |
| ) | |
| pred_df = pd.DataFrame(rows) | |
| X = prepare_features(pred_df) | |
| X_scaled = scaler.transform(X) | |
| predictions = model.predict(X_scaled, verbose=0) | |
| results = pd.DataFrame( | |
| { | |
| 'Category': pred_df['Category'], | |
| 'Percent_Spend': pred_df['Percent_Spend'], | |
| 'Deviation': pred_df['Deviation'], | |
| 'Raw_Score': predictions.flatten(), | |
| 'Prediction': ['Good' if pred >= 0.6 else 'Bad' for pred in predictions], | |
| } | |
| ) | |
| return ( | |
| results.sort_values('Percent_Spend', ascending=False), | |
| total_spending, | |
| income_level, | |
| ) | |
| # Suggest spending pattern | |
| def suggest_spending_pattern(results, total_spending, input_data, income_level): | |
| results = results.copy() | |
| suggested_spending = {} | |
| bad_categories = results[results['Prediction'] == 'Bad'] | |
| good_categories = results[results['Prediction'] == 'Good'] | |
| baseline = BASELINE_UPPER if income_level == 'upper' else BASELINE_LOWER | |
| if not bad_categories.empty: | |
| total_to_redistribute = sum( | |
| input_data[row['Category']] | |
| * min(max(abs(row['Deviation']) * 0.1, 0.25), 0.50) | |
| for _, row in bad_categories.iterrows() | |
| if row['Category'] not in ['Household', 'Food'] | |
| ) | |
| good_total = sum(input_data[cat] for cat in good_categories['Category']) | |
| distribution_weights = { | |
| cat: input_data[cat] / good_total if good_total > 0 else 0 | |
| for cat in good_categories['Category'] | |
| } | |
| for category in input_data: | |
| original = float(input_data[category]) | |
| baseline_dollars = total_spending * (baseline[category] / 100) | |
| if category in bad_categories['Category'].values and category not in [ | |
| 'Household', | |
| 'Food', | |
| ]: | |
| reduction = min( | |
| max( | |
| abs( | |
| results[results['Category'] == category][ | |
| 'Deviation' | |
| ].values[0] | |
| ) | |
| * 0.1, | |
| 0.25, | |
| ), | |
| 0.50, | |
| ) | |
| suggested = original * (1 - reduction) | |
| else: | |
| weight = distribution_weights.get(category, 0) | |
| increase = total_to_redistribute * weight | |
| suggested = max( | |
| original + increase, | |
| baseline_dollars if category in ['Household', 'Food'] else original, | |
| ) | |
| suggested_spending[category] = (original, round(suggested, 2)) | |
| else: | |
| suggested_spending = { | |
| cat: (float(val), float(val)) for cat, val in input_data.items() | |
| } | |
| return suggested_spending | |
| # Format for Mistral | |
| def format_for_mistral( | |
| results, suggested_spending, total_spending, income_level, input_data | |
| ): | |
| return { | |
| "total_spending": total_spending, | |
| "income_level": income_level, | |
| "categories": [ | |
| { | |
| "category": row['Category'], | |
| "percent_spend": round(row['Percent_Spend'], 2), | |
| "actual_dollars": round(input_data[row['Category']], 2), | |
| "deviation": round(row['Deviation'], 2), | |
| "prediction": row['Prediction'], | |
| "suggested_dollars": suggested_spending[row['Category']][1], | |
| } | |
| for _, row in results.iterrows() | |
| ], | |
| } | |
| # Get spending summary (Mistral API call) | |
| def get_spending_summary(spending_data): | |
| client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=API) | |
| analysis_prompt = f""" | |
| You are a financial counselor analyzing a ${spending_data['total_spending']} monthly budget for a {spending_data['income_level']} income individual. Follow these strict rules: | |
| ### Financial Literacy Summary | |
| #### Praise | |
| For each 'Good' category: | |
| ⚠️ **Only show if ALL conditions met:** | |
| - `prediction` = 'Good' | |
| - `abs(deviation)` < 2% | |
| ✅ **{{category}} (${{actual_dollars}})** - | |
| Explain using: | |
| 1. "% vs baseline: {{percent_spend}}% ({{deviation:+.2f}}% vs {{baseline}}%)" | |
| 2. Practical benefit | |
| 3. Savings impact ONLY if `deviation` > 0 | |
| #### Suggestions | |
| ⚠️ **Only show if ALL conditions met:** | |
| - `prediction` = 'Bad' | |
| - `abs(deviation)` > 2% | |
| - `suggested_dollars` ≠ `actual_dollars` | |
| For each 'Bad' category: | |
| ⚠️ **{{category}} (${{actual_dollars}} → ${{suggested_dollars}})** - | |
| Structure as: | |
| 1. If suggested INCREASE: "Prioritize {{category}} by adding ${{suggested_dollars - actual_dollars}}..." | |
| 2. If suggested DECREASE: "Reduce {{category}} by ${{actual_dollars - suggested_dollars}}..." | |
| #### Key Principle | |
| Identify the MOST URGENT issue using largest absolute deviation... | |
| **Baseline Reference ({spending_data['income_level'].capitalize()} Income):** | |
| {'Food (10%), Household (11%), Shopping (13%), Transportation (11%), Health & Fitness (10%), Entertainment (18%), Beauty (8%), Investment (19%)' if spending_data['income_level'] == 'upper' else 'Food (40%), Household (30%), Shopping (7%), Transportation (5%), Health & Fitness (5%), Entertainment (5%), Beauty (4%), Investment (4%)'} | |
| **Data:** | |
| {json.dumps(spending_data, indent=2)} | |
| **Begin Analysis:** | |
| """ | |
| try: | |
| response = client.chat.completions.create( | |
| model="mistralai/mistral-small-24b-instruct-2501:free", | |
| messages=[{"role": "user", "content": analysis_prompt}], | |
| temperature=0.5, | |
| ) | |
| return response.choices[0].message.content | |
| except Exception as e: | |
| return f"Error calling Mistral API: {e}" | |
| # Gradio interface function | |
| def analyze_spending( | |
| household, | |
| food, | |
| shopping, | |
| transportation, | |
| health_fitness, | |
| entertainment, | |
| beauty, | |
| investment, | |
| ): | |
| input_data = { | |
| 'Household': float(household), | |
| 'Food': float(food), | |
| 'Shopping': float(shopping), | |
| 'Transportation': float(transportation), | |
| 'Health & Fitness': float(health_fitness), | |
| 'Entertainment': float(entertainment), | |
| 'Beauty': float(beauty), | |
| 'Investment': float(investment), | |
| } | |
| logger.info("Before loading model") | |
| model, scaler = load_financial_model() | |
| logger.info("After loading model") | |
| if model is None or scaler is None: | |
| return "Error: Model or scaler failed to load.", None, None, None | |
| results, total_spending, income_level = predict_spending_pattern( | |
| model, scaler, input_data | |
| ) | |
| suggested_spending = suggest_spending_pattern( | |
| results, total_spending, input_data, income_level | |
| ) | |
| spending_data = format_for_mistral( | |
| results, suggested_spending, total_spending, income_level, input_data | |
| ) | |
| summary = get_spending_summary(spending_data) | |
| # Format suggested adjustments as a DataFrame | |
| suggested_df = pd.DataFrame( | |
| [(cat, orig, sugg) for cat, (orig, sugg) in suggested_spending.items()], | |
| columns=['Category', 'Original ($)', 'Suggested ($)'], | |
| ) | |
| return ( | |
| f"## Spending Analysis ({income_level.capitalize()} Income)\nTotal Spending: ${total_spending:.2f}", | |
| results, # For DataFrame display | |
| suggested_df, # For DataFrame display | |
| summary, # Financial summary | |
| ) | |
| # Gradio UI | |
| logger.info("Setting up Gradio interface") | |
| with gr.Blocks( | |
| title="Personal Finance Assistant", css=".gr-button {margin-top: 10px}" | |
| ) as demo: | |
| gr.Markdown("# Personal Finance Assistant") | |
| gr.Markdown("Enter your monthly spending in each category ($):") | |
| with gr.Row(): | |
| household = gr.Textbox(label="Household", value="500") | |
| food = gr.Textbox(label="Food", value="100") | |
| shopping = gr.Textbox(label="Shopping", value="950") | |
| transportation = gr.Textbox(label="Transportation", value="100") | |
| with gr.Row(): | |
| health_fitness = gr.Textbox(label="Health & Fitness", value="200") | |
| entertainment = gr.Textbox(label="Entertainment", value="200") | |
| beauty = gr.Textbox(label="Beauty", value="100") | |
| investment = gr.Textbox(label="Investment", value="100") | |
| submit_btn = gr.Button("Analyze") | |
| # Output components | |
| with gr.Column(): | |
| loading = gr.Markdown("### Analysis Results\n*Waiting for input...*") | |
| title = gr.Markdown() | |
| current_spending = gr.DataFrame(label="Current Spending") | |
| suggested_adjustments = gr.DataFrame(label="Suggested Adjustments") | |
| financial_summary = gr.Markdown() | |
| # Handle click with loading state | |
| def start_loading(): | |
| return "### Analysis Results\n*Processing your spending data...*" | |
| submit_btn.click(fn=start_loading, inputs=None, outputs=loading).then( | |
| fn=analyze_spending, | |
| inputs=[ | |
| household, | |
| food, | |
| shopping, | |
| transportation, | |
| health_fitness, | |
| entertainment, | |
| beauty, | |
| investment, | |
| ], | |
| outputs=[title, current_spending, suggested_adjustments, financial_summary], | |
| queue=True, | |
| ) | |
| logger.info("Launching Gradio server") | |
| demo.launch(server_name="0.0.0.0", server_port=7860) | |