report / app.py
entropy25's picture
Update app.py
81121cb verified
import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
from datetime import datetime
import io
import os
import warnings
warnings.filterwarnings('ignore')
# Initialize session state
if 'data_loaded' not in st.session_state:
st.session_state.data_loaded = False
if 'analyzer' not in st.session_state:
st.session_state.analyzer = None
# Page configuration
st.set_page_config(
page_title="๐Ÿ“Š FinanceGPT Analyzer",
page_icon="๐Ÿ“Š",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom CSS for better styling
st.markdown("""
<style>
.metric-card {
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
padding: 1rem;
border-radius: 10px;
color: white;
text-align: center;
margin: 0.5rem 0;
}
.insight-box {
background: #f8f9fa;
padding: 1rem;
border-left: 4px solid #007bff;
border-radius: 5px;
margin: 1rem 0;
}
.warning-box {
background: #fff3cd;
padding: 1rem;
border-left: 4px solid #ffc107;
border-radius: 5px;
margin: 1rem 0;
}
</style>
""", unsafe_allow_html=True)
# ไฟฎๅคๅŽ็š„ๆ–‡ไปถๅŠ ่ฝฝๅ‡ฝๆ•ฐ
@st.cache_data
def load_csv_with_encoding(file_content: bytes, filename: str) -> pd.DataFrame:
"""Load CSV with automatic encoding detection - cached"""
try:
import chardet
detected = chardet.detect(file_content)
encoding = detected['encoding'] if detected['encoding'] else 'utf-8'
try:
from io import BytesIO
return pd.read_csv(BytesIO(file_content), encoding=encoding)
except:
encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
for enc in encodings:
try:
return pd.read_csv(BytesIO(file_content), encoding=enc)
except:
continue
raise Exception("Cannot read file with any encoding")
except ImportError:
# Fallback if chardet is not available
from io import BytesIO
encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
for enc in encodings:
try:
return pd.read_csv(BytesIO(file_content), encoding=enc)
except:
continue
raise Exception("Cannot read file with any encoding")
@st.cache_data
def load_excel_file(file_content: bytes) -> pd.DataFrame:
"""Load Excel file - cached"""
from io import BytesIO
return pd.read_excel(BytesIO(file_content))
def load_data(uploaded_file):
"""Unified data loading function"""
file_content = uploaded_file.read()
uploaded_file.seek(0)
if uploaded_file.name.endswith('.csv'):
return load_csv_with_encoding(file_content, uploaded_file.name)
else:
return load_excel_file(file_content)
class FinanceAnalyzer:
def __init__(self):
self.data = None
self.processed_data = {}
def load_csv_data(self):
"""Load CSV data from local file"""
try:
# Try multiple possible paths for CSV file
possible_paths = [
os.path.join(os.path.dirname(__file__), '2024.csv'),
os.path.join('2024.csv'),
'2024.csv',
os.path.join('2024.csv')
]
for csv_path in possible_paths:
if os.path.exists(csv_path):
# Use the improved loading function
with open(csv_path, 'rb') as f:
file_content = f.read()
df = load_csv_with_encoding(file_content, csv_path)
st.success(f"โœ… CSV data loaded from: {csv_path}")
return df
# If no file found, show available files for debugging
current_dir = os.path.dirname(__file__) if __file__ else '.'
available_files = []
for root, dirs, files in os.walk(current_dir):
for file in files:
if file.endswith('.csv'):
available_files.append(os.path.join(root, file))
if available_files:
st.warning(f"CSV file not found in expected locations. Available CSV files: {available_files}")
else:
st.warning("No CSV files found. Using sample data instead.")
return self.load_sample_data()
except Exception as e:
st.error(f"Error loading CSV file: {e}")
st.info("Falling back to sample data...")
return self.load_sample_data()
def load_sample_data(self):
"""Load sample financial data based on your real CSV structure"""
sample_data = {
'Statement_Type': ['Income Statement'] * 19 + ['Balance Sheet'] * 10 + ['Cash Flow Statement'] * 5,
'Account_Name_Norwegian': [
# Income Statement
'Salgsinntekt', 'Sum inntekter', 'Varekostnad', 'Lรธnnskostnad',
'Avskrivning pรฅ varige driftsmidler og immaterielle eiendeler',
'Annen driftskostnad', 'Sum kostnader', 'Driftsresultat',
'Annen renteinntekt', 'Annen finansinntekt', 'Sum finansinntekter',
'Annen rentekostnad', 'Annen finanskostnad', 'Sum finanskostnader',
'Netto finans', 'Resultat fรธr skattekostnad', 'Skattekostnad',
'ร…rsresultat', 'Overfรธringer til/fra annen egenkapital',
# Balance Sheet
'Sum anleggsmidler', 'Kundefordringer', 'Andre fordringer',
'Sum fordringer', 'Bankinnskudd kontanter og lignende',
'Sum omlรธpsmidler', 'SUM EIENDELER', 'Sum egenkapital',
'Sum langsiktig gjeld', 'Sum kortsiktig gjeld', 'Sum gjeld',
# Cash Flow
'ร…rsresultat', 'Avskrivninger', 'Netto kontantstrรธm fra driftsaktiviteter',
'Netto kontantstrรธm fra investeringsaktiviteter', 'NETTO ENDRING I KONTANTER'
],
'Account_Name_English': [
# Income Statement
'Sales Revenue', 'Total Income', 'Cost of Goods Sold', 'Payroll Expenses',
'Depreciation on Fixed Assets and Intangible Assets',
'Other Operating Expenses', 'Total Expenses', 'Operating Result',
'Other Interest Income', 'Other Financial Income', 'Total Financial Income',
'Other Interest Expenses', 'Other Financial Expenses', 'Total Financial Expenses',
'Net Financial Result', 'Result Before Tax', 'Tax Expense',
'Annual Result', 'Transfers to/from Other Equity',
# Balance Sheet
'Total Fixed Assets', 'Customer Receivables', 'Other Receivables',
'Total Receivables', 'Bank Deposits Cash and Similar',
'Total Current Assets', 'TOTAL ASSETS', 'Total Equity',
'Total Long-term Debt', 'Total Short-term Debt', 'Total Debt',
# Cash Flow
'Net Income', 'Depreciation', 'Net Cash Flow from Operating Activities',
'Net Cash Flow from Investing Activities', 'NET CHANGE IN CASH'
],
'2024_Amount_NOK': [
# Income Statement
25107008, 25107008, 9880032, 3700289, 316180,
4355621, 18252121, 6854887, 11439, 1230, 12669,
51288, 3916, 55205, -42536, 6812351, 1498717,
5313634, 5313634,
# Balance Sheet
4282396, 5575707, 178797, 5754504, 1595549,
7350053, 11632449, 5602404, 653459, 5376586, 6030045,
# Cash Flow
5313634, 316180, 3385812, -3546128, 801948
],
'2023_Amount_NOK': [
# Income Statement
4891891, 4891891, 770840, 2703253, 0,
1330101, 4804194, 87697, 385, 0, 385,
59498, 0, 59498, -59113, 28584, 32524,
-3940, -3940,
# Balance Sheet
1052447, 2000151, 233394, 2233546, 793599,
3027145, 4079592, 288770, 630673, 3160150, 3790823,
# Cash Flow
-3940, 0, 951553, -1052448, 500891
]
}
return pd.DataFrame(sample_data)
def process_financial_data(self, df):
"""Process uploaded financial data - improved version"""
self.data = df
try:
# Handle the actual CSV structure (your format)
if 'Statement_Type' in df.columns and '2024_Amount_NOK' in df.columns:
# Filter income statement data
income_df = df[df['Statement_Type'] == 'Income Statement'].copy()
# Extract key financial metrics
revenue_rows = income_df[income_df['Account_Name_English'].str.contains('Sales Revenue', case=False, na=False)]
profit_rows = income_df[income_df['Account_Name_English'].str.contains('Annual Result|Net Income', case=False, na=False)]
cogs_rows = income_df[income_df['Account_Name_English'].str.contains('Cost of Goods', case=False, na=False)]
operating_rows = income_df[income_df['Account_Name_English'].str.contains('Operating Result', case=False, na=False)]
self.processed_data = {
'revenue_2024': revenue_rows['2024_Amount_NOK'].iloc[0] if len(revenue_rows) > 0 else 0,
'revenue_2023': revenue_rows['2023_Amount_NOK'].iloc[0] if len(revenue_rows) > 0 else 0,
'net_profit_2024': profit_rows['2024_Amount_NOK'].iloc[0] if len(profit_rows) > 0 else 0,
'net_profit_2023': profit_rows['2023_Amount_NOK'].iloc[0] if len(profit_rows) > 0 else 0,
'cogs_2024': abs(cogs_rows['2024_Amount_NOK'].iloc[0]) if len(cogs_rows) > 0 else 0,
'cogs_2023': abs(cogs_rows['2023_Amount_NOK'].iloc[0]) if len(cogs_rows) > 0 else 0,
'operating_profit_2024': operating_rows['2024_Amount_NOK'].iloc[0] if len(operating_rows) > 0 else 0,
'operating_profit_2023': operating_rows['2023_Amount_NOK'].iloc[0] if len(operating_rows) > 0 else 0,
}
st.success("โœ… Financial data processed successfully!")
st.info(f"๐Ÿ“Š Processed {len(df)} financial line items")
else:
# Fallback for different structures
st.warning("โš ๏ธ Using fallback data processing")
self.processed_data = {
'revenue_2024': 25107008,
'revenue_2023': 4891891,
'net_profit_2024': 5313634,
'net_profit_2023': -3940,
'cogs_2024': 9880032,
'cogs_2023': 770840,
'operating_profit_2024': 6854887,
'operating_profit_2023': 87697,
}
except Exception as e:
st.error(f"Error processing data: {e}")
st.info("Using default financial values...")
self.processed_data = {
'revenue_2024': 25107008,
'revenue_2023': 4891891,
'net_profit_2024': 5313634,
'net_profit_2023': -3940,
'cogs_2024': 9880032,
'cogs_2023': 770840,
'operating_profit_2024': 6854887,
'operating_profit_2023': 87697,
}
def calculate_metrics(self):
"""Calculate key financial metrics"""
if not self.processed_data:
return {}
data = self.processed_data
# Growth rates
revenue_growth = ((data['revenue_2024'] - data['revenue_2023']) /
abs(data['revenue_2023']) * 100) if data['revenue_2023'] != 0 else 0
# Profitability ratios
gross_margin_2024 = ((data['revenue_2024'] - data['cogs_2024']) /
data['revenue_2024'] * 100) if data['revenue_2024'] != 0 else 0
net_margin_2024 = (data['net_profit_2024'] / data['revenue_2024'] * 100) if data['revenue_2024'] != 0 else 0
return {
'revenue_growth': revenue_growth,
'gross_margin_2024': gross_margin_2024,
'net_margin_2024': net_margin_2024,
'revenue_2024_m': data['revenue_2024'] / 1000000,
'net_profit_2024_m': data['net_profit_2024'] / 1000000,
}
def create_revenue_trend_chart(self):
"""Create revenue trend visualization"""
if not self.processed_data:
return go.Figure()
fig = go.Figure()
years = [2023, 2024]
revenues = [self.processed_data['revenue_2023']/1000000,
self.processed_data['revenue_2024']/1000000]
net_profits = [self.processed_data['net_profit_2023']/1000000,
self.processed_data['net_profit_2024']/1000000]
fig.add_trace(go.Scatter(x=years, y=revenues, mode='lines+markers',
name='Revenue (M NOK)', line=dict(color='#1f77b4', width=3)))
fig.add_trace(go.Scatter(x=years, y=net_profits, mode='lines+markers',
name='Net Profit (M NOK)', line=dict(color='#ff7f0e', width=3)))
fig.update_layout(title='Revenue vs Profit Trend', xaxis_title='Year',
yaxis_title='Amount (M NOK)', height=400)
return fig
def create_financial_health_radar(self):
"""Create financial health radar chart"""
metrics = self.calculate_metrics()
categories = ['Revenue Growth', 'Gross Margin', 'Net Margin', 'Profitability', 'Efficiency']
values = [
min(metrics.get('revenue_growth', 0) / 5, 100), # Scale revenue growth
metrics.get('gross_margin_2024', 0),
max(metrics.get('net_margin_2024', 0), 0),
70, # Sample value
65 # Sample value
]
fig = go.Figure()
fig.add_trace(go.Scatterpolar(
r=values,
theta=categories,
fill='toself',
name='Financial Health'
))
fig.update_layout(
polar=dict(
radialaxis=dict(visible=True, range=[0, 100])
),
title="Financial Health Score",
height=400
)
return fig
def main():
st.title("๐Ÿ“Š FinanceGPT Analyzer")
st.markdown("### Professional Financial Analysis Dashboard")
# Debug information (can be removed in production)
with st.expander("๐Ÿ”ง Debug Information"):
st.write("**Current working directory:**", os.getcwd())
st.write("**Available files:**")
for root, dirs, files in os.walk('.'):
for file in files[:10]: # Limit to first 10 files
st.write(f"- {os.path.join(root, file)}")
# Initialize analyzer
if st.session_state.analyzer is None:
st.session_state.analyzer = FinanceAnalyzer()
analyzer = st.session_state.analyzer
# Sidebar navigation
with st.sidebar:
st.header("Navigation")
page = st.selectbox("Choose Analysis Page", [
"๐Ÿ  Dashboard",
"๐Ÿ’ฐ Income Analysis",
"๐Ÿ›๏ธ Balance Sheet Analysis",
"๐Ÿ’ธ Cash Flow Analysis",
"๐Ÿ“Š Financial Ratios Hub",
"๐Ÿค– AI Finance Assistant"
])
st.header("Data Upload")
uploaded_file = st.file_uploader("Upload CSV file", type=['csv'])
if st.button("Load CSV Data"):
try:
df = analyzer.load_csv_data()
analyzer.process_financial_data(df)
st.session_state.data_loaded = True
st.success("CSV data loaded successfully!")
st.rerun()
except Exception as e:
st.error(f"Error loading CSV data: {e}")
if st.button("Use Sample Data"):
analyzer.data = analyzer.load_sample_data()
analyzer.process_financial_data(analyzer.data)
st.session_state.data_loaded = True
st.success("Sample data loaded!")
st.rerun()
# ไฟฎๅคๅŽ็š„ๆ–‡ไปถไธŠไผ ๅค„็†
if uploaded_file is not None:
try:
# Use the improved file loading function
df = load_data(uploaded_file)
analyzer.data = df
analyzer.process_financial_data(df)
st.session_state.data_loaded = True
st.success("โœ… Data uploaded and processed successfully!")
# Show data preview
st.subheader("๐Ÿ“‹ Data Preview")
st.write("**Shape:**", df.shape)
st.write("**Columns:**", list(df.columns))
st.dataframe(df.head())
st.rerun()
except Exception as e:
st.error(f"โŒ Error loading file: {e}")
st.info("๐Ÿ’ก Please ensure your CSV file has the correct format with columns: Statement_Type, Account_Name_Norwegian, Account_Name_English, 2024_Amount_NOK, 2023_Amount_NOK")
# Main content based on selected page
if page == "๐Ÿ  Dashboard":
dashboard_page(analyzer)
elif page == "๐Ÿ’ฐ Income Analysis":
income_analysis_page(analyzer)
elif page == "๐Ÿ›๏ธ Balance Sheet Analysis":
balance_sheet_page(analyzer)
elif page == "๐Ÿ’ธ Cash Flow Analysis":
cash_flow_page(analyzer)
elif page == "๐Ÿ“Š Financial Ratios Hub":
ratios_page(analyzer)
elif page == "๐Ÿค– AI Finance Assistant":
ai_assistant_page(analyzer)
def dashboard_page(analyzer):
"""Main dashboard page"""
st.header("๐Ÿ“Š Financial Dashboard")
if analyzer.data is None:
st.warning("Please upload data or use sample data to begin analysis.")
return
metrics = analyzer.calculate_metrics()
# Key metrics cards
col1, col2, col3, col4 = st.columns(4)
with col1:
st.markdown("""
<div class="metric-card">
<h3>๐Ÿ’ฐ Revenue</h3>
<h2>{:.1f}M NOK</h2>
<p>+{:.0f}% ๐Ÿ”ฅ</p>
</div>
""".format(metrics.get('revenue_2024_m', 0), metrics.get('revenue_growth', 0)),
unsafe_allow_html=True)
with col2:
st.markdown("""
<div class="metric-card">
<h3>๐Ÿ“ˆ Net Profit</h3>
<h2>{:.1f}M NOK</h2>
<p>Profitable โœ…</p>
</div>
""".format(metrics.get('net_profit_2024_m', 0)), unsafe_allow_html=True)
with col3:
st.markdown("""
<div class="metric-card">
<h3>๐Ÿ“Š Gross Margin</h3>
<h2>{:.1f}%</h2>
<p>Healthy ๐Ÿ’ช</p>
</div>
""".format(metrics.get('gross_margin_2024', 0)), unsafe_allow_html=True)
with col4:
st.markdown("""
<div class="metric-card">
<h3>๐ŸŽฏ Net Margin</h3>
<h2>{:.1f}%</h2>
<p>Strong ๐Ÿ“ˆ</p>
</div>
""".format(metrics.get('net_margin_2024', 0)), unsafe_allow_html=True)
# Charts section
col1, col2 = st.columns(2)
with col1:
st.plotly_chart(analyzer.create_revenue_trend_chart(), use_container_width=True)
with col2:
st.plotly_chart(analyzer.create_financial_health_radar(), use_container_width=True)
# Quick insights
st.markdown("""
<div class="insight-box">
<h4>๐ŸŽฏ Quick Insights</h4>
<ul>
<li>โœ… Revenue growth of {:.0f}% indicates explosive business development</li>
<li>๐Ÿ’ก Net profit margin of {:.1f}% shows strong profitability</li>
<li>๐Ÿ“ˆ Gross margin of {:.1f}% demonstrates healthy pricing power</li>
</ul>
</div>
""".format(
metrics.get('revenue_growth', 0),
metrics.get('net_margin_2024', 0),
metrics.get('gross_margin_2024', 0)
), unsafe_allow_html=True)
def income_analysis_page(analyzer):
"""Income statement analysis page"""
st.header("๐Ÿ’ฐ Income Analysis")
if analyzer.data is None:
st.warning("Please upload data to begin analysis.")
return
# Revenue analysis
st.subheader("๐Ÿ“ˆ Revenue Trend Analysis")
st.plotly_chart(analyzer.create_revenue_trend_chart(), use_container_width=True)
# Cost structure
st.subheader("๐Ÿฅง Cost Structure Analysis")
if analyzer.processed_data:
data = analyzer.processed_data
costs = ['Cost of Goods Sold', 'Operating Expenses', 'Financial Expenses']
values = [data['cogs_2024'], 2000000, 234567] # Sample values
fig = px.pie(values=values, names=costs, title="Cost Breakdown 2024")
st.plotly_chart(fig, use_container_width=True)
# Profitability metrics
st.subheader("๐Ÿ“Š Profitability Indicators")
metrics = analyzer.calculate_metrics()
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Gross Margin", f"{metrics.get('gross_margin_2024', 0):.1f}%")
with col2:
st.metric("Net Margin", f"{metrics.get('net_margin_2024', 0):.1f}%")
with col3:
st.metric("Revenue Growth", f"{metrics.get('revenue_growth', 0):.1f}%")
def balance_sheet_page(analyzer):
"""Balance sheet analysis page"""
st.header("๐Ÿ›๏ธ Balance Sheet Analysis")
if analyzer.data is None:
st.warning("Please upload balance sheet data to begin analysis.")
return
st.info("Balance sheet analysis requires additional data. Please upload complete financial statements.")
# Sample asset structure chart
assets = ['Current Assets', 'Fixed Assets', 'Intangible Assets']
values = [45, 35, 20]
fig = px.pie(values=values, names=assets, title="Asset Structure")
st.plotly_chart(fig, use_container_width=True)
def cash_flow_page(analyzer):
"""Cash flow analysis page"""
st.header("๐Ÿ’ธ Cash Flow Analysis")
if analyzer.data is None:
st.warning("Please upload cash flow data to begin analysis.")
return
st.info("Cash flow analysis requires additional data. Please upload complete cash flow statements.")
# Sample cash flow chart
categories = ['Operating CF', 'Investing CF', 'Financing CF']
values = [5000000, -2000000, -1000000]
fig = go.Figure(go.Waterfall(
name="Cash Flow", orientation="v",
measure=["relative", "relative", "relative"],
x=categories, y=values,
text=[f"{v/1000000:.1f}M" for v in values]
))
fig.update_layout(title="Cash Flow Waterfall")
st.plotly_chart(fig, use_container_width=True)
def ratios_page(analyzer):
"""Financial ratios analysis page"""
st.header("๐Ÿ“Š Financial Ratios Hub")
if analyzer.data is None:
st.warning("Please upload data to calculate ratios.")
return
# Ratio categories
col1, col2, col3, col4 = st.columns(4)
with col1:
if st.button("Profitability"):
st.session_state.ratio_category = "profitability"
with col2:
if st.button("Liquidity"):
st.session_state.ratio_category = "liquidity"
with col3:
if st.button("Efficiency"):
st.session_state.ratio_category = "efficiency"
with col4:
if st.button("Growth"):
st.session_state.ratio_category = "growth"
# Display ratios based on selection
metrics = analyzer.calculate_metrics()
st.subheader("Key Financial Ratios")
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Gross Profit Margin", f"{metrics.get('gross_margin_2024', 0):.1f}%", "A+")
with col2:
st.metric("Net Profit Margin", f"{metrics.get('net_margin_2024', 0):.1f}%", "A")
with col3:
st.metric("Revenue Growth", f"{metrics.get('revenue_growth', 0):.1f}%", "A+")
def ai_assistant_page(analyzer):
"""AI finance assistant page"""
st.header("๐Ÿค– AI Finance Assistant")
if analyzer.data is None:
st.warning("Please upload data to enable AI analysis.")
return
# Chat interface
st.subheader("๐Ÿ’ฌ Ask Your Financial Questions")
# Predefined questions
col1, col2 = st.columns(2)
with col1:
if st.button("Analyze my financial health"):
st.session_state.ai_query = "financial_health"
if st.button("Find the biggest risks"):
st.session_state.ai_query = "risks"
with col2:
if st.button("Give investment advice"):
st.session_state.ai_query = "investment"
if st.button("Create improvement plan"):
st.session_state.ai_query = "improvement"
# Text input for custom questions
user_question = st.text_input("Or ask your own question:")
if user_question or 'ai_query' in st.session_state:
metrics = analyzer.calculate_metrics()
# Simple AI-like responses based on data
if user_question or st.session_state.get('ai_query') == 'financial_health':
st.markdown("""
<div class="insight-box">
<h4>๐ŸŽฏ Financial Health Analysis</h4>
<p>Based on your financial data:</p>
<ul>
<li>โœ… <strong>Revenue Growth:</strong> {:.0f}% growth shows strong market performance</li>
<li>โœ… <strong>Profitability:</strong> {:.1f}% net margin indicates healthy operations</li>
<li>๐Ÿ“Š <strong>Overall Rating:</strong> A- (Strong financial position)</li>
</ul>
</div>
""".format(
metrics.get('revenue_growth', 0),
metrics.get('net_margin_2024', 0)
), unsafe_allow_html=True)
# Clear the session state
if 'ai_query' in st.session_state:
del st.session_state.ai_query
if __name__ == "__main__":
main()