|
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 |
|
|
|
|
|
ENGINEER_SALARY = 10000 |
|
|
|
|
|
if 'startups' not in st.session_state: |
|
st.session_state.startups = {} |
|
if 'current_startup' not in st.session_state: |
|
st.session_state.current_startup = None |
|
if 'current_page' not in st.session_state: |
|
st.session_state.current_page = 'upload' |
|
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 startup's finances?"} |
|
] |
|
|
|
|
|
st.set_page_config(page_title="MONEYMINDSPilot", page_icon="💰", layout="wide") |
|
|
|
|
|
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) |
|
|
|
|
|
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 ElevenLabs API""" |
|
if simulate: |
|
return None |
|
else: |
|
try: |
|
api_key = st.secrets.get("ELEVENLABS_API_KEY", None) |
|
if not api_key: |
|
return None |
|
|
|
url = "https://api.elevenlabs.io/v1/text-to-speech/21m00Tcm4TlvDq8ikWAM" |
|
|
|
headers = { |
|
"Accept": "audio/mpeg", |
|
"Content-Type": "application/json", |
|
"xi-api-key": api_key |
|
} |
|
|
|
data = { |
|
"text": text, |
|
"model_id": "eleven_monolingual_v1", |
|
"voice_settings": { |
|
"stability": 0.5, |
|
"similarity_boost": 0.5 |
|
} |
|
} |
|
|
|
response = requests.post(url, json=data, headers=headers) |
|
|
|
if response.status_code == 200: |
|
return response.content |
|
else: |
|
st.error(f"Error with ElevenLabs API: {response.status_code}") |
|
return None |
|
|
|
except Exception as e: |
|
st.error(f"Error generating voice response: {e}") |
|
return None |
|
|
|
|
|
|
|
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) |
|
|
|
|
|
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() |
|
|
|
|
|
category_thresholds = { |
|
"Travel": 3000, "Marketing": 10000, "Office": 7000, |
|
"Software": 6000, "Consulting": 5000, "Legal": 6000 |
|
} |
|
|
|
suspicious_terms = ['luxury', 'cruise', 'premium', 'personal', 'gift'] |
|
|
|
|
|
df['Suspicious'] = False |
|
df['Reason'] = "" |
|
df['Risk_Score'] = 0 |
|
|
|
for idx, row in df.iterrows(): |
|
reasons = [] |
|
risk_score = 0 |
|
|
|
|
|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
if row['Amount'] % 1000 == 0 and row['Amount'] > 3000: |
|
reasons.append(f"Suspiciously round amount") |
|
risk_score += 15 |
|
|
|
|
|
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}" |
|
|
|
|
|
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) |
|
|
|
|
|
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 |
|
|
|
|
|
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) |
|
|
|
|
|
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') |
|
|
|
|
|
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']) |
|
|
|
|
|
if st.button("Process Data"): |
|
|
|
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 |
|
|
|
|
|
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") |
|
|
|
|
|
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") |
|
|
|
|
|
if transactions_file: |
|
transactions_df, error = parse_csv_to_df(transactions_file) |
|
if error: |
|
st.error(error) |
|
else: |
|
|
|
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") |
|
|
|
|
|
if profile_file: |
|
|
|
st.session_state.startups[startup_data['name']] = { |
|
'profile': startup_data, |
|
'cash_flow': cash_flow_df, |
|
'transactions': transactions_df |
|
} |
|
|
|
|
|
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 |
|
|
|
|
|
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) |
|
|
|
|
|
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]) |
|
|
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
|
|
runway_months, runway_df = calculate_runway( |
|
startup_data['cash'], |
|
startup_data['burn_rate'], |
|
startup_data['revenue'], |
|
startup_data['growth_rate'] |
|
) |
|
|
|
|
|
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) |
|
|
|
|
|
st.subheader("Financial Overview") |
|
|
|
|
|
if cash_flow_df is not None: |
|
|
|
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) |
|
|
|
|
|
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_decision_simulator(startup_data): |
|
"""Render the decision simulator page""" |
|
st.markdown("<h1 class='main-header'>Decision Simulator</h1>", unsafe_allow_html=True) |
|
st.markdown("<p class='sub-header'>Test the financial impact of business decisions</p>", unsafe_allow_html=True) |
|
|
|
|
|
with st.form("decision_form"): |
|
st.subheader("Scenario Parameters") |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
new_hires = st.number_input("New Engineering Hires", min_value=0, max_value=10, value=0) |
|
st.caption(f"Monthly Cost: ${new_hires * ENGINEER_SALARY:,}") |
|
|
|
new_marketing = st.number_input("Additional Monthly Marketing Budget", |
|
min_value=0, max_value=50000, value=0, step=1000) |
|
|
|
with col2: |
|
other_expenses = st.number_input("Other Additional Monthly Expenses", |
|
min_value=0, max_value=50000, value=0, step=1000) |
|
|
|
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") |
|
|
|
question = st.text_area("Describe your decision scenario", height=100) |
|
|
|
decision_summary = f""" |
|
- {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}") |
|
|
|
submitted = st.form_submit_button("Simulate Decision") |
|
|
|
if submitted: |
|
|
|
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 |
|
) |
|
|
|
|
|
st.markdown("<h3>Decision Impact Analysis</h3>", unsafe_allow_html=True) |
|
|
|
|
|
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") |
|
|
|
|
|
st.subheader("Cash Projection Comparison") |
|
|
|
|
|
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'}) |
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
processed_df = detect_suspicious_transactions(transactions_df) |
|
|
|
|
|
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) |
|
|
|
|
|
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 |
|
) |
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
st.markdown("<div style='background-color: #f8f9fa; padding: 20px; border-radius: 10px; margin-bottom: 20px;'>", unsafe_allow_html=True) |
|
|
|
|
|
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) |
|
|
|
|
|
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}") |
|
|
|
|
|
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) |
|
|
|
|
|
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 |
|
|
|
|
|
if user_input: |
|
|
|
st.session_state.chat_history.append({"role": "user", "content": user_input}) |
|
|
|
|
|
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. |
|
""") |
|
|
|
|
|
audio_data = None |
|
if use_voice: |
|
with st.spinner("Generating voice response..."): |
|
audio_data = generate_voice_response(response) |
|
|
|
|
|
st.session_state.chat_history.append({ |
|
"role": "assistant", |
|
"content": response, |
|
"audio": audio_data |
|
}) |
|
|
|
|
|
st.rerun() |
|
|
|
st.markdown("</div>", unsafe_allow_html=True) |
|
|
|
def main(): |
|
|
|
create_sidebar() |
|
|
|
|
|
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( |
|
st.session_state.startups[st.session_state.current_startup]['profile'] |
|
) |
|
elif st.session_state.current_page == 'monitoring': |
|
render_fund_monitoring() |
|
elif st.session_state.current_page == 'advisor': |
|
render_ai_financial_advisor() |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |