Spaces:
Build error
Build error
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| from datetime import datetime, timedelta, date | |
| import requests | |
| import google.generativeai as genai | |
| from PIL import Image | |
| import re | |
| from gtts import gTTS | |
| import io | |
| # Constants | |
| ENGINEER_SALARY = 10000 # Monthly cost per engineer ($120K/year) | |
| # Initialize session state variables | |
| if 'startups' not in st.session_state: | |
| st.session_state.startups = {} # Dictionary to store startup data | |
| if 'current_startup' not in st.session_state: | |
| st.session_state.current_startup = None # Currently selected startup | |
| if 'current_page' not in st.session_state: | |
| st.session_state.current_page = 'upload' # Default page | |
| if 'insights_cache' not in st.session_state: | |
| st.session_state.insights_cache = {} | |
| if 'chat_history' not in st.session_state: | |
| st.session_state.chat_history = [ | |
| {"role": "assistant", "content": "Hi! I'm your AI financial advisor. How can I help with your social enterprise startup's finances?"} | |
| ] | |
| # Setup page config and styling | |
| st.set_page_config(page_title="MoneyMindsPilot", page_icon="💰", layout="wide") | |
| # Apply custom styling | |
| st.markdown(""" | |
| <style> | |
| .main-header {font-size: 2.5rem; color: #0066cc; margin-bottom: 0.5rem;} | |
| .sub-header {font-size: 1.5rem; color: #5c5c5c; margin-bottom: 1.5rem;} | |
| .metric-card {background-color: #f8f9fa; border-radius: 10px; padding: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);} | |
| .metric-label {font-size: 1rem; color: #5c5c5c;} | |
| .metric-value {font-size: 1.8rem; color: #0066cc; font-weight: bold;} | |
| .good-metric {color: #28a745;} | |
| .warning-metric {color: #ffc107;} | |
| .danger-metric {color: #dc3545;} | |
| .title-box {background: linear-gradient(45deg, #0066cc, #66b3ff); padding: 20px; border-radius: 10px; | |
| margin-bottom: 20px; text-align: center; color: white;} | |
| .ai-badge {display: inline-block; background-color: #0066cc; color: white; border-radius: 4px; | |
| padding: 2px 6px; font-size: 0.7rem; font-weight: bold; margin-bottom: 8px;} | |
| .insight-card, .advisor-card {background-color: #f8f9fa; border-radius: 10px; padding: 15px; | |
| margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);} | |
| div.stButton > button {width: 100%; padding: 10px; border: none; background-color: #E6F3FF; | |
| color: #0066cc; border-radius: 10px; text-align: left; font-weight: bold;} | |
| div.stButton > button:hover {background-color: #CCE5FF; color: #004080;} | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # AI Integration Functions | |
| def initialize_gemini(): | |
| """Initialize Google's Gemini AI with API key""" | |
| try: | |
| api_key = st.secrets.get("GEMINI_API_KEY", None) | |
| if api_key: | |
| genai.configure(api_key=api_key) | |
| return True | |
| else: | |
| st.warning("Gemini API key not found. Using simulated responses.") | |
| return False | |
| except Exception as e: | |
| st.error(f"Failed to initialize Gemini AI: {e}") | |
| return False | |
| def generate_ai_response(prompt, simulate=False): | |
| """Generate text using Google's Gemini AI""" | |
| if simulate: | |
| return "AI response simulation: Based on your financial data, I recommend focusing on extending runway, accelerating revenue growth, and preparing for your next funding round." | |
| else: | |
| try: | |
| model = genai.GenerativeModel('gemini-2.0-flash') | |
| response = model.generate_content(prompt) | |
| return response.text | |
| except Exception as e: | |
| st.error(f"Error generating AI response: {e}") | |
| return "Sorry, I couldn't generate a response at this time." | |
| def autoplay_audio(audio_content): | |
| """Generate HTML with audio player that auto-plays""" | |
| b64 = base64.b64encode(audio_content).decode() | |
| md = f""" | |
| <audio controls autoplay> | |
| <source src="data:audio/mp3;base64,{b64}" type="audio/mp3"> | |
| </audio> | |
| """ | |
| st.markdown(md, unsafe_allow_html=True) | |
| def generate_voice_response(text, simulate=False): | |
| """Generate voice response using gTTS""" | |
| if simulate: | |
| return None | |
| try: | |
| # Create a gTTS object | |
| tts = gTTS(text=text, lang='en') | |
| # Save to a bytes buffer | |
| mp3_fp = io.BytesIO() | |
| tts.write_to_fp(mp3_fp) | |
| mp3_fp.seek(0) | |
| return mp3_fp.getvalue() | |
| except Exception as e: | |
| st.error(f"Error generating voice response: {e}") | |
| return None | |
| def extract_invoice_data(image_file): | |
| """ | |
| Extract financial data from invoices using Gemini flash | |
| Args: | |
| image_file: Uploaded image file from Streamlit | |
| Returns: | |
| Extracted text information | |
| """ | |
| try: | |
| # Initialize Gemini | |
| api_key = st.secrets.get("GEMINI_API_KEY") | |
| if not api_key: | |
| st.error("Gemini API key not found") | |
| return None | |
| genai.configure(api_key=api_key) | |
| # Load the image | |
| image = Image.open(image_file) | |
| # Convert image to bytes | |
| img_byte_arr = io.BytesIO() | |
| image.save(img_byte_arr, format=image.format) | |
| img_byte_arr = img_byte_arr.getvalue() | |
| # Prepare the model | |
| model = genai.GenerativeModel('gemini-2.0-flash') | |
| # Comprehensive prompt for invoice extraction | |
| prompt = """ | |
| Carefully extract all financial and relevant information from this invoice: | |
| Please provide a detailed, structured text output that includes: | |
| - Complete invoice details | |
| - Vendor/Company information | |
| - Line items or services | |
| - Total amounts | |
| - Tax details | |
| - Payment terms | |
| - Any other significant financial information | |
| Format the output clearly and comprehensively, making it easy to read and understand. | |
| """ | |
| # Generate response | |
| response = model.generate_content([prompt, image]) | |
| # Return the extracted text | |
| return response.text | |
| except Exception as e: | |
| st.error(f"Error extracting invoice data: {e}") | |
| return None | |
| # Utility Functions | |
| def switch_page(page_name): | |
| """Function to switch between pages""" | |
| st.session_state.current_page = page_name | |
| st.rerun() | |
| def calculate_runway(cash, burn_rate, revenue, growth_rate, months=24): | |
| """Calculate runway based on cash, burn, revenue and growth""" | |
| current_date = datetime.now() | |
| date_range = [current_date + timedelta(days=30*i) for i in range(months)] | |
| cash_flow = [] | |
| monthly_revenue = revenue | |
| for i in range(months): | |
| net_burn = burn_rate - monthly_revenue | |
| cash_flow.append(net_burn) | |
| monthly_revenue *= (1 + growth_rate) | |
| df = pd.DataFrame({ | |
| 'Net_Burn': cash_flow, | |
| 'Cumulative_Cash': [cash - sum(cash_flow[:i+1]) for i in range(len(cash_flow))] | |
| }, index=date_range) | |
| negative_cash = df[df['Cumulative_Cash'] < 0] | |
| runway_months = (negative_cash.index[0] - current_date).days // 30 if len(negative_cash) > 0 else months | |
| return runway_months, df | |
| def simulate_decision(cash, burn_rate, revenue, growth_rate, | |
| additional_expenses, new_hires, marketing_increase, growth_impact): | |
| """ Simulate the financial impact of a business decision with AI-powered insights | |
| Args: | |
| - cash: Current cash balance | |
| - burn_rate: Current monthly burn rate | |
| - revenue: Current monthly revenue | |
| - growth_rate: Current monthly growth rate | |
| - additional_expenses: Proposed additional monthly expenses | |
| - new_hires: Number of new hires | |
| - marketing_increase: Proposed marketing budget increase | |
| - growth_impact: Expected growth rate impact | |
| Returns: | |
| - current_runway: Current financial runway in months | |
| - new_runway: Projected runway after proposed changes | |
| - current_df: DataFrame with current financial projection | |
| - new_df: DataFrame with projected financial scenario | |
| - ai_analysis: AI-generated insights about the decision | |
| """ | |
| current_runway, current_df = calculate_runway(cash, burn_rate, revenue, growth_rate) | |
| new_burn_rate = burn_rate + additional_expenses + (new_hires * ENGINEER_SALARY) + marketing_increase | |
| new_growth_rate = growth_rate + growth_impact | |
| new_runway, new_df = calculate_runway(cash, new_burn_rate, revenue, new_growth_rate) | |
| # Generate AI analysis of the decision | |
| try: | |
| ai_analysis = generate_ai_response(f""" | |
| You are a strategic financial advisor for startups. Analyze this potential business decision: | |
| Current Financial Situation: | |
| - Cash Balance: ${cash:,} | |
| - Monthly Burn Rate: ${burn_rate:,} | |
| - Monthly Revenue: ${revenue:,} | |
| - Current Growth Rate: {growth_rate * 100:.1f}% | |
| - Current Runway: {current_runway} months | |
| Proposed Changes: | |
| - Additional Expenses: ${additional_expenses:,}/month | |
| - New Hires: {new_hires} engineers (${new_hires * ENGINEER_SALARY:,}/month) | |
| - Marketing Budget Increase: ${marketing_increase:,}/month | |
| - Expected Growth Impact: +{growth_impact * 100:.1f}% | |
| Projected Outcome: | |
| - New Burn Rate: ${new_burn_rate:,}/month | |
| - New Growth Rate: {new_growth_rate * 100:.1f}% | |
| - Projected Runway: {new_runway} months | |
| Provide a comprehensive analysis addressing: | |
| 1. Financial feasibility of the proposed changes | |
| 2. Risk assessment | |
| 3. Potential strategic benefits | |
| 4. Recommendations for optimization | |
| 5. Key metrics to monitor | |
| Be direct, specific, and provide actionable insights. | |
| """, simulate=False) | |
| except Exception as e: | |
| ai_analysis = f"AI analysis unavailable. Error: {str(e)}" | |
| return current_runway, new_runway, current_df, new_df, ai_analysis | |
| def detect_suspicious_transactions(transactions_df): | |
| """AI-enhanced suspicious transaction detection""" | |
| df = transactions_df.copy() | |
| # Define thresholds for each category | |
| category_thresholds = { | |
| "Travel": 3000, "Marketing": 10000, "Office": 7000, | |
| "Software": 6000, "Consulting": 5000, "Legal": 6000 | |
| } | |
| suspicious_terms = ['luxury', 'cruise', 'premium', 'personal', 'gift'] | |
| # Add analysis columns | |
| df['Suspicious'] = False | |
| df['Reason'] = "" | |
| df['Risk_Score'] = 0 | |
| for idx, row in df.iterrows(): | |
| reasons = [] | |
| risk_score = 0 | |
| # Check category thresholds | |
| if row['Category'] in category_thresholds and row['Amount'] > category_thresholds[row['Category']]: | |
| reasons.append(f"Amount exceeds typical spending for {row['Category']}") | |
| risk_score += 30 | |
| # Check for suspicious terms | |
| for field in ['Vendor', 'Description']: | |
| if any(term in str(row[field]).lower() for term in suspicious_terms): | |
| reasons.append(f"{field} contains suspicious term") | |
| risk_score += 20 | |
| # Check for round amounts | |
| if row['Amount'] % 1000 == 0 and row['Amount'] > 3000: | |
| reasons.append(f"Suspiciously round amount") | |
| risk_score += 15 | |
| # Mark as suspicious if risk score is high enough | |
| if risk_score >= 30: | |
| df.at[idx, 'Suspicious'] = True | |
| df.at[idx, 'Reason'] = "; ".join(reasons) | |
| df.at[idx, 'Risk_Score'] = risk_score | |
| return df.sort_values(by='Risk_Score', ascending=False) | |
| def parse_csv_to_df(file): | |
| """Parse uploaded CSV file to DataFrame""" | |
| try: | |
| df = pd.read_csv(file) | |
| return df, None | |
| except Exception as e: | |
| return None, f"Error parsing CSV: {e}" | |
| # Navigation | |
| def create_sidebar(): | |
| with st.sidebar: | |
| st.markdown(""" | |
| <div class="title-box"> | |
| <h1>💰 StartupFinancePilot</h1> | |
| <p>AI-powered financial assistant for startups</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Startup selector | |
| if st.session_state.startups: | |
| st.subheader("Selected Startup") | |
| startup_names = list(st.session_state.startups.keys()) | |
| selected_startup = st.selectbox( | |
| "Choose Startup", | |
| startup_names, | |
| index=startup_names.index(st.session_state.current_startup) if st.session_state.current_startup in startup_names else 0 | |
| ) | |
| st.session_state.current_startup = selected_startup | |
| # Show basic startup info | |
| if selected_startup in st.session_state.startups: | |
| startup_data = st.session_state.startups[selected_startup]['profile'] | |
| st.markdown(f""" | |
| **Stage:** {startup_data['stage']} | |
| **Cash:** ${startup_data['cash']:,} | |
| **Monthly Burn:** ${startup_data['burn_rate']:,} | |
| **Monthly Revenue:** ${startup_data['revenue']:,} | |
| """) | |
| st.markdown("<hr>", unsafe_allow_html=True) | |
| # Navigation buttons | |
| if st.button("📤 Upload Startup Data", use_container_width=True): | |
| switch_page('upload') | |
| if st.button("📊 Financial Dashboard", use_container_width=True): | |
| switch_page('dashboard') | |
| if st.button("🔮 Decision Simulator", use_container_width=True): | |
| switch_page('simulator') | |
| if st.button("🕵️ Fund Monitoring", use_container_width=True): | |
| switch_page('monitoring') | |
| if st.button("🤖 AI Financial Advisor", use_container_width=True): | |
| switch_page('advisor') | |
| if st.button("📄 Invoice Processor", use_container_width=True): | |
| switch_page('invoice_processor') | |
| # Page Renderers | |
| def render_upload_page(): | |
| """Render the upload page for startup data""" | |
| st.markdown("<h1 class='main-header'>Upload Your Startup Data</h1>", unsafe_allow_html=True) | |
| st.markdown("<p class='sub-header'>Upload CSV files to get started</p>", unsafe_allow_html=True) | |
| with st.expander("Upload Instructions", expanded=False): | |
| st.markdown(""" | |
| ### How to Upload Your Startup Data | |
| You can upload three types of files: | |
| 1. **Company Profile** - A CSV with basic information about your startup including: | |
| - name, stage, founded, employees, last_funding, cash, burn_rate, revenue, growth_rate | |
| 2. **Cash Flow Data** - A CSV with monthly cash flow data with columns: | |
| - Month, Revenue, Payroll, Marketing, Office, Software, Travel, Legal, Misc | |
| 3. **Transaction Data** - A CSV with transaction details: | |
| - Date, Category, Vendor, Amount, Description, Flag | |
| """) | |
| startup_name = st.text_input("Startup Name", value="My Startup") | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| profile_file = st.file_uploader("Upload Company Profile (CSV)", type=['csv']) | |
| with col2: | |
| cash_flow_file = st.file_uploader("Upload Cash Flow Data (CSV)", type=['csv']) | |
| with col3: | |
| transactions_file = st.file_uploader("Upload Transactions Data (CSV)", type=['csv']) | |
| # Process the files if uploaded | |
| if st.button("Process Data"): | |
| # Initialize with default values | |
| startup_data = { | |
| "name": startup_name, | |
| "stage": "Seed", | |
| "founded": "12 months ago", | |
| "employees": 5, | |
| "last_funding": "Not specified", | |
| "cash": 100000, | |
| "burn_rate": 20000, | |
| "revenue": 5000, | |
| "growth_rate": 0.05 | |
| } | |
| cash_flow_df = None | |
| transactions_df = None | |
| # Parse company profile | |
| if profile_file: | |
| profile_df, error = parse_csv_to_df(profile_file) | |
| if error: | |
| st.error(error) | |
| elif len(profile_df) > 0: | |
| startup_data.update(profile_df.iloc[0].to_dict()) | |
| st.success(f"Successfully loaded company profile") | |
| # Parse cash flow data | |
| if cash_flow_file: | |
| cash_flow_df, error = parse_csv_to_df(cash_flow_file) | |
| if error: | |
| st.error(error) | |
| else: | |
| if "Total_Expenses" not in cash_flow_df.columns: | |
| expense_columns = [col for col in cash_flow_df.columns if col not in ["Month", "Revenue", "Total_Expenses", "Net_Burn"]] | |
| cash_flow_df["Total_Expenses"] = cash_flow_df[expense_columns].sum(axis=1) | |
| if "Net_Burn" not in cash_flow_df.columns: | |
| cash_flow_df["Net_Burn"] = cash_flow_df["Total_Expenses"] - cash_flow_df["Revenue"] | |
| st.success("Successfully loaded cash flow data") | |
| # Parse transactions data | |
| if transactions_file: | |
| transactions_df, error = parse_csv_to_df(transactions_file) | |
| if error: | |
| st.error(error) | |
| else: | |
| # Ensure transactions data has required columns | |
| required_columns = ["Date", "Category", "Vendor", "Amount", "Description"] | |
| if all(col in transactions_df.columns for col in required_columns): | |
| if "Flag" not in transactions_df.columns: | |
| transactions_df["Flag"] = "Normal" | |
| st.success("Successfully loaded transactions data") | |
| else: | |
| st.error("Transactions file is missing required columns") | |
| # Save to session state if we have at least some data | |
| if profile_file: | |
| # Store in session state | |
| st.session_state.startups[startup_data['name']] = { | |
| 'profile': startup_data, | |
| 'cash_flow': cash_flow_df, | |
| 'transactions': transactions_df | |
| } | |
| # Set as current startup | |
| st.session_state.current_startup = startup_data['name'] | |
| st.success(f"Successfully added {startup_data['name']} to your startups") | |
| switch_page('dashboard') | |
| else: | |
| st.error("Please upload at least a company profile file") | |
| def render_financial_dashboard(): | |
| """Render the AI-powered financial dashboard page""" | |
| if not st.session_state.current_startup or st.session_state.current_startup not in st.session_state.startups: | |
| st.warning("No startup selected. Please upload data first.") | |
| render_upload_page() | |
| return | |
| # Get the selected startup data | |
| startup_data = st.session_state.startups[st.session_state.current_startup]['profile'] | |
| cash_flow_df = st.session_state.startups[st.session_state.current_startup]['cash_flow'] | |
| st.markdown("<h1 class='main-header'>Financial Dashboard</h1>", unsafe_allow_html=True) | |
| # AI Insights | |
| insights_key = f"dashboard_{date.today().isoformat()}" | |
| if insights_key not in st.session_state.insights_cache: | |
| insights = generate_ai_response(f""" | |
| You are a financial advisor for startups. Based on this startup's data: | |
| - Current cash: ${startup_data['cash']} | |
| - Monthly burn rate: ${startup_data['burn_rate']} | |
| - Monthly revenue: ${startup_data['revenue']} | |
| - Monthly growth rate: {startup_data['growth_rate'] * 100}% | |
| Provide the top 3 most important financial insights that the founder should know today. | |
| Format each insight as a brief, action-oriented bullet point. | |
| """) | |
| st.session_state.insights_cache[insights_key] = insights | |
| with st.expander("📊 AI Financial Insights", expanded=True): | |
| st.markdown("<span class='ai-badge'>AI-Generated Insights</span>", unsafe_allow_html=True) | |
| st.markdown(st.session_state.insights_cache[insights_key]) | |
| # Key metrics | |
| col1, col2, col3, col4 = st.columns(4) | |
| # Calculate runway | |
| runway_months, runway_df = calculate_runway( | |
| startup_data['cash'], | |
| startup_data['burn_rate'], | |
| startup_data['revenue'], | |
| startup_data['growth_rate'] | |
| ) | |
| # Determine status colors | |
| runway_status = "danger-metric" if runway_months < 6 else ("warning-metric" if runway_months < 9 else "good-metric") | |
| burn_status = "danger-metric" if startup_data['burn_rate'] > 100000 else ("warning-metric" if startup_data['burn_rate'] > 80000 else "good-metric") | |
| revenue_status = "good-metric" if startup_data['revenue'] > 20000 else ("warning-metric" if startup_data['revenue'] > 10000 else "danger-metric") | |
| with col1: | |
| st.markdown(f""" | |
| <div class='metric-card'> | |
| <p class='metric-label'>Current Cash</p> | |
| <p class='metric-value'>${startup_data['cash']:,}</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with col2: | |
| st.markdown(f""" | |
| <div class='metric-card'> | |
| <p class='metric-label'>Monthly Burn</p> | |
| <p class='metric-value {burn_status}'>${startup_data['burn_rate']:,}</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with col3: | |
| st.markdown(f""" | |
| <div class='metric-card'> | |
| <p class='metric-label'>Monthly Revenue</p> | |
| <p class='metric-value {revenue_status}'>${startup_data['revenue']:,}</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with col4: | |
| st.markdown(f""" | |
| <div class='metric-card'> | |
| <p class='metric-label'>Runway</p> | |
| <p class='metric-value {runway_status}'>{runway_months} months</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Financial charts | |
| st.subheader("Financial Overview") | |
| # Display only if we have cash flow data | |
| if cash_flow_df is not None: | |
| # Runway chart | |
| fig = px.line(runway_df.reset_index(), x='index', y='Cumulative_Cash', | |
| title="Cash Runway Projection", | |
| labels={'index': 'Date', 'Cumulative_Cash': 'Remaining Cash ($)'}, | |
| color_discrete_sequence=['#0066cc']) | |
| fig.add_hline(y=0, line_dash="dash", line_color="red", annotation_text="Out of Cash") | |
| fig.update_layout(height=400) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Revenue vs Expenses | |
| fig = px.bar(cash_flow_df, x='Month', y=['Revenue', 'Total_Expenses'], | |
| title="Revenue vs. Expenses", | |
| barmode='group', | |
| color_discrete_sequence=['#28a745', '#dc3545']) | |
| st.plotly_chart(fig, use_container_width=True) | |
| else: | |
| st.info("Upload cash flow data to see detailed financial charts") | |
| def render_invoice_processor(): | |
| """Render the invoice processing page""" | |
| st.markdown("<h1 class='main-header'>Invoice Data Extractor</h1>", unsafe_allow_html=True) | |
| st.markdown("<p class='sub-header'>AI-powered financial data extraction</p>", unsafe_allow_html=True) | |
| # File uploader for invoices | |
| uploaded_file = st.file_uploader("Upload Invoice Image", type=['png', 'jpg', 'jpeg', 'pdf']) | |
| if uploaded_file is not None: | |
| # Display the uploaded image | |
| st.image(uploaded_file, caption="Uploaded Invoice", use_container_width=True) | |
| # Extract invoice data | |
| if st.button("Extract Invoice Details"): | |
| with st.spinner("Extracting invoice information..."): | |
| invoice_data = extract_invoice_data(uploaded_file) | |
| if invoice_data: | |
| # Display extracted data in a formatted, readable way | |
| st.subheader("Extracted Invoice Information") | |
| st.markdown("<div class='advisor-card'>", unsafe_allow_html=True) | |
| st.markdown("<span class='ai-badge'>AI Invoice Extraction</span>", unsafe_allow_html=True) | |
| # Use st.text to preserve formatting | |
| st.text(invoice_data) | |
| # Optional: Copy to clipboard | |
| st.download_button( | |
| label="Copy Invoice Details", | |
| data=invoice_data, | |
| file_name="invoice_details.txt", | |
| mime="text/plain" | |
| ) | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| else: | |
| st.error("Could not extract invoice data") | |
| def render_decision_simulator(): | |
| """Render the decision simulator page for startup financial decision-making""" | |
| # Check if a startup is selected | |
| if not st.session_state.current_startup or st.session_state.current_startup not in st.session_state.startups: | |
| st.warning("No startup selected. Please upload data first.") | |
| render_upload_page() | |
| return | |
| # Get the current startup's data | |
| startup_data = st.session_state.startups[st.session_state.current_startup]['profile'] | |
| # Page header | |
| st.markdown("<h1 class='main-header'>Decision Simulator</h1>", unsafe_allow_html=True) | |
| st.markdown("<p class='sub-header'>AI-Powered Financial Decision Analysis</p>", unsafe_allow_html=True) | |
| # Decision input form | |
| with st.form("decision_form"): | |
| st.subheader("Scenario Parameters") | |
| # Create two columns for input | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| # New engineering hires input | |
| new_hires = st.number_input( | |
| "New Engineering Hires", | |
| min_value=0, | |
| max_value=10, | |
| value=0, | |
| help="Number of new engineers to hire" | |
| ) | |
| st.caption(f"Monthly Cost: ${new_hires * ENGINEER_SALARY:,}") | |
| # Marketing budget increase | |
| new_marketing = st.number_input( | |
| "Additional Monthly Marketing Budget", | |
| min_value=0, | |
| max_value=50000, | |
| value=0, | |
| step=1000, | |
| help="Increase in monthly marketing spending" | |
| ) | |
| with col2: | |
| # Other additional expenses | |
| other_expenses = st.number_input( | |
| "Other Additional Monthly Expenses", | |
| min_value=0, | |
| max_value=50000, | |
| value=0, | |
| step=1000, | |
| help="Any other additional monthly expenses" | |
| ) | |
| # Growth impact slider | |
| growth_impact = st.slider( | |
| "Estimated Impact on Monthly Growth Rate", | |
| min_value=0.0, | |
| max_value=0.10, | |
| value=0.0, | |
| step=0.01, | |
| format="%.2f", | |
| help="Expected increase in monthly growth rate" | |
| ) | |
| # Scenario description | |
| question = st.text_area( | |
| "Describe your decision scenario", | |
| height=100, | |
| placeholder="Provide context for your financial decision..." | |
| ) | |
| # Decision summary | |
| decision_summary = f""" | |
| Decision Breakdown: | |
| - {new_hires} new engineers: ${new_hires * ENGINEER_SALARY:,}/month | |
| - Marketing increase: ${new_marketing:,}/month | |
| - Other expenses: ${other_expenses:,}/month | |
| - Total additional burn: ${new_hires * ENGINEER_SALARY + new_marketing + other_expenses:,}/month | |
| - Growth impact: +{growth_impact * 100:.1f}% monthly growth | |
| """ | |
| st.markdown(f"**Decision Summary:**\n{decision_summary}") | |
| # Simulation submission button | |
| submitted = st.form_submit_button("Simulate Decision") | |
| # Process the simulation when submitted | |
| if submitted: | |
| # Validate inputs | |
| if new_hires == 0 and new_marketing == 0 and other_expenses == 0: | |
| st.warning("Please adjust at least one parameter to simulate a decision.") | |
| return | |
| # Run decision simulation | |
| try: | |
| current_runway, new_runway, current_df, new_df, ai_analysis = simulate_decision( | |
| startup_data['cash'], | |
| startup_data['burn_rate'], | |
| startup_data['revenue'], | |
| startup_data['growth_rate'], | |
| other_expenses, | |
| new_hires, | |
| new_marketing, | |
| growth_impact | |
| ) | |
| # Decision Impact Analysis Header | |
| st.markdown("<h3>Decision Impact Analysis</h3>", unsafe_allow_html=True) | |
| # Summary Metrics | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.metric("Current Runway", f"{current_runway} months") | |
| with col2: | |
| runway_change = new_runway - current_runway | |
| st.metric( | |
| "New Runway", | |
| f"{new_runway} months", | |
| delta=f"{runway_change} months", | |
| delta_color="off" if runway_change == 0 else ("normal" if runway_change > 0 else "inverse") | |
| ) | |
| with col3: | |
| new_burn = startup_data['burn_rate'] + other_expenses + (new_hires * ENGINEER_SALARY) + new_marketing | |
| burn_change = new_burn - startup_data['burn_rate'] | |
| burn_percentage = burn_change / startup_data['burn_rate'] * 100 | |
| st.metric( | |
| "New Monthly Burn", | |
| f"${new_burn:,}", | |
| delta=f"${burn_change:,} ({burn_percentage:.1f}%)", | |
| delta_color="inverse" | |
| ) | |
| # Cash Projection Comparison | |
| st.subheader("Cash Projection Comparison") | |
| # Prepare data for visualization | |
| current_df['Scenario'] = 'Current' | |
| new_df['Scenario'] = 'After Decision' | |
| combined_df = pd.concat([current_df, new_df]) | |
| combined_df = combined_df.reset_index() | |
| combined_df = combined_df.rename(columns={'index': 'Date'}) | |
| # Plot comparison | |
| fig = px.line( | |
| combined_df, | |
| x='Date', | |
| y='Cumulative_Cash', | |
| color='Scenario', | |
| title="Cash Runway Comparison", | |
| labels={'Cumulative_Cash': 'Remaining Cash'}, | |
| color_discrete_sequence=['#4c78a8', '#f58518'] | |
| ) | |
| fig.add_hline(y=0, line_dash="dash", line_color="red", annotation_text="Out of Cash") | |
| fig.update_layout(height=400) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # AI Decision Analysis | |
| if ai_analysis: | |
| st.markdown("<div class='advisor-card'>", unsafe_allow_html=True) | |
| st.markdown("<span class='ai-badge'>AI Decision Analysis</span>", unsafe_allow_html=True) | |
| st.markdown(f"<p class='advice-text'>{ai_analysis}</p>", unsafe_allow_html=True) | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| except Exception as e: | |
| st.error(f"Error simulating decision: {e}") | |
| st.warning("Please check your inputs and try again.") | |
| def render_fund_monitoring(): | |
| """Render the fund monitoring page""" | |
| if not st.session_state.current_startup or st.session_state.current_startup not in st.session_state.startups: | |
| st.warning("No startup selected. Please upload data first.") | |
| render_upload_page() | |
| return | |
| # Get the selected startup data | |
| transactions_df = st.session_state.startups[st.session_state.current_startup]['transactions'] | |
| st.markdown("<h1 class='main-header'>Fund Monitoring</h1>", unsafe_allow_html=True) | |
| st.markdown("<p class='sub-header'>AI-powered fraud detection and spending analysis</p>", unsafe_allow_html=True) | |
| if transactions_df is None: | |
| st.warning("No transaction data available. Please upload transaction data.") | |
| return | |
| # Process transactions to detect suspicious ones | |
| processed_df = detect_suspicious_transactions(transactions_df) | |
| # Summary metrics | |
| total_transactions = len(processed_df) | |
| suspicious_transactions = processed_df[processed_df['Suspicious']].copy() | |
| suspicious_count = len(suspicious_transactions) | |
| suspicious_amount = suspicious_transactions['Amount'].sum() if not suspicious_transactions.empty else 0 | |
| total_amount = processed_df['Amount'].sum() | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown(f""" | |
| <div class='metric-card'> | |
| <p class='metric-label'>Total Transactions</p> | |
| <p class='metric-value'>{total_transactions}</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with col2: | |
| flagged_percent = suspicious_count/total_transactions*100 if total_transactions > 0 else 0 | |
| status = "danger-metric" if flagged_percent > 10 else ("warning-metric" if flagged_percent > 5 else "good-metric") | |
| st.markdown(f""" | |
| <div class='metric-card'> | |
| <p class='metric-label'>Flagged Transactions</p> | |
| <p class='metric-value {status}'>{suspicious_count} ({flagged_percent:.1f}%)</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Tabs for different views | |
| tab1, tab2 = st.tabs(["Flagged Transactions", "All Transactions"]) | |
| with tab1: | |
| if suspicious_count > 0: | |
| st.dataframe( | |
| suspicious_transactions[['Date', 'Category', 'Vendor', 'Amount', 'Description', 'Risk_Score', 'Reason']], | |
| use_container_width=True | |
| ) | |
| # Get AI analysis of suspicious transactions | |
| fraud_key = f"fraud_{date.today().isoformat()}" | |
| if fraud_key not in st.session_state.insights_cache: | |
| suspicious_text = "\n".join([ | |
| f"- {row['Vendor']} (${row['Amount']:.2f}): {row['Description']}" | |
| for _, row in suspicious_transactions.head(5).iterrows() | |
| ]) | |
| fraud_analysis = generate_ai_response(f""" | |
| You are a financial fraud detection expert. Review these flagged suspicious transactions: | |
| {suspicious_text} | |
| Provide a brief analysis and recommendations. | |
| """) | |
| st.session_state.insights_cache[fraud_key] = fraud_analysis | |
| st.markdown("<div class='advisor-card'>", unsafe_allow_html=True) | |
| st.markdown("<span class='ai-badge'>AI Fraud Analysis</span>", unsafe_allow_html=True) | |
| st.markdown(f"<p class='advice-text'>{st.session_state.insights_cache[fraud_key]}</p>", unsafe_allow_html=True) | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| else: | |
| st.success("No suspicious transactions detected.") | |
| with tab2: | |
| st.dataframe(processed_df[['Date', 'Category', 'Vendor', 'Amount', 'Description', 'Suspicious', 'Risk_Score']], | |
| use_container_width=True) | |
| # Category spending | |
| if not processed_df.empty: | |
| st.subheader("Spending by Category") | |
| category_spending = processed_df.groupby('Category')['Amount'].sum().reset_index() | |
| fig = px.bar(category_spending, x='Category', y='Amount', | |
| title="Spending by Category", | |
| color='Amount', | |
| color_continuous_scale='Blues') | |
| st.plotly_chart(fig, use_container_width=True) | |
| def render_ai_financial_advisor(): | |
| """Render the AI financial advisor page with voice chat""" | |
| if not st.session_state.current_startup or st.session_state.current_startup not in st.session_state.startups: | |
| st.warning("No startup selected. Please upload data first.") | |
| return | |
| startup_data = st.session_state.startups[st.session_state.current_startup]['profile'] | |
| st.markdown("<h1 class='main-header'>AI Financial Advisor</h1>", unsafe_allow_html=True) | |
| # Chat container | |
| st.markdown("<div style='background-color: #f8f9fa; padding: 20px; border-radius: 10px; margin-bottom: 20px;'>", unsafe_allow_html=True) | |
| # Display chat history | |
| for message in st.session_state.chat_history: | |
| if message["role"] == "user": | |
| st.markdown(f"<div style='background-color: #e6f7ff; padding: 10px; border-radius: 10px; margin-bottom: 10px;'><strong>You:</strong> {message['content']}</div>", unsafe_allow_html=True) | |
| else: | |
| st.markdown(f"<div style='background-color: #f0f7ff; padding: 10px; border-radius: 10px; margin-bottom: 10px;'><strong>Financial Advisor:</strong> {message['content']}</div>", unsafe_allow_html=True) | |
| # Show play button for voice if it exists | |
| if 'audio' in message and message['audio']: | |
| try: | |
| st.audio(message['audio'], format='audio/mp3') | |
| except Exception as e: | |
| st.error(f"Error playing audio: {e}") | |
| # Input for new message | |
| col1, col2 = st.columns([5, 1]) | |
| with col1: | |
| user_input = st.text_input("Ask a financial question", key="user_question") | |
| with col2: | |
| use_voice = st.checkbox("Voice", value=True) | |
| # Common financial questions | |
| st.markdown("### Common Questions") | |
| question_cols = st.columns(3) | |
| common_questions = [ | |
| "How much runway do we have?", | |
| "When should we start fundraising?", | |
| "How can we optimize our burn rate?" | |
| ] | |
| for i, question in enumerate(common_questions): | |
| with question_cols[i % 3]: | |
| if st.button(question, key=f"q_{i}"): | |
| user_input = question | |
| # Process user input | |
| if user_input: | |
| # Add user message to chat history | |
| st.session_state.chat_history.append({"role": "user", "content": user_input}) | |
| # Get AI response | |
| response = generate_ai_response(f""" | |
| You are a strategic financial advisor for startups. A founder asks: | |
| "{user_input}" | |
| Here's their current financial situation: | |
| - Stage: {startup_data['stage']} | |
| - Current cash: ${startup_data['cash']} | |
| - Monthly burn rate: ${startup_data['burn_rate']} | |
| - Monthly revenue: ${startup_data['revenue']} | |
| - Monthly growth rate: {startup_data['growth_rate'] * 100}% | |
| - Last funding: {startup_data['last_funding']} | |
| Provide concise, actionable advice. | |
| """) | |
| # Generate voice response if enabled | |
| audio_data = None | |
| if use_voice: | |
| with st.spinner("Generating voice response..."): | |
| audio_data = generate_voice_response(response) | |
| # Add AI response to chat history | |
| st.session_state.chat_history.append({ | |
| "role": "assistant", | |
| "content": response, | |
| "audio": audio_data | |
| }) | |
| # Rerun to display updated chat | |
| st.rerun() | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| # Main function | |
| def main(): | |
| # Initialize AI | |
| initialize_gemini() | |
| # Create sidebar navigation | |
| create_sidebar() | |
| # Render the correct page based on session state | |
| if st.session_state.current_page == 'upload': | |
| render_upload_page() | |
| elif st.session_state.current_page == 'dashboard': | |
| render_financial_dashboard() | |
| elif st.session_state.current_page == 'simulator': | |
| render_decision_simulator() | |
| elif st.session_state.current_page == 'monitoring': | |
| render_fund_monitoring() | |
| elif st.session_state.current_page == 'advisor': | |
| render_ai_financial_advisor() | |
| elif st.session_state.current_page == 'invoice_processor': | |
| render_invoice_processor() | |
| if __name__ == "__main__": | |
| main() |