Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -3,8 +3,10 @@ import gradio as gr
|
|
3 |
import pandas as pd
|
4 |
import torch
|
5 |
import logging
|
6 |
-
from transformers import pipeline, AutoModelForSequenceClassification, AutoTokenizer
|
7 |
import gc
|
|
|
|
|
|
|
8 |
|
9 |
# Setup logging
|
10 |
logging.basicConfig(
|
@@ -17,38 +19,100 @@ logger = logging.getLogger(__name__)
|
|
17 |
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
18 |
logger.info(f"Using device: {DEVICE}")
|
19 |
|
20 |
-
|
21 |
-
"""
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
def validate_financial_csv(file_obj, file_type):
|
27 |
-
"""Validate financial CSV files"""
|
28 |
-
try:
|
29 |
-
df = pd.read_csv(file_obj)
|
30 |
-
|
31 |
-
# Expected columns based on file type
|
32 |
-
expected_columns = {
|
33 |
-
'income_statement': ['Revenue', 'Expenses', 'Profit'],
|
34 |
-
'balance_sheet': ['Assets', 'Liabilities', 'Equity']
|
35 |
-
}
|
36 |
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
|
41 |
-
|
42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
|
44 |
-
|
45 |
-
|
46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
47 |
|
48 |
class FinancialAnalyzer:
|
49 |
-
"""
|
50 |
|
51 |
def __init__(self):
|
|
|
52 |
self.sentiment_model = None
|
53 |
self.analysis_model = None
|
54 |
self.load_models()
|
@@ -64,7 +128,7 @@ class FinancialAnalyzer:
|
|
64 |
truncation=True
|
65 |
)
|
66 |
|
67 |
-
# Load small model for analysis
|
68 |
self.analysis_model = pipeline(
|
69 |
"text-generation",
|
70 |
model="TinyLlama/TinyLlama-1.1B-Chat-v1.0",
|
@@ -76,173 +140,166 @@ class FinancialAnalyzer:
|
|
76 |
logger.error(f"Error loading models: {str(e)}")
|
77 |
raise
|
78 |
|
79 |
-
def
|
80 |
-
"""
|
81 |
try:
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
# Read CSV with better column handling
|
86 |
-
df = pd.read_csv(file_obj, skipinitialspace=True)
|
87 |
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
logger.info(f"Numeric columns: {numeric_cols.tolist()}")
|
110 |
-
|
111 |
-
# Calculate meaningful KPIs
|
112 |
-
kpis = {
|
113 |
-
'total_revenue': df[numeric_cols].sum().sum(),
|
114 |
-
'average_values': df[numeric_cols].mean(),
|
115 |
-
'year_over_year_growth': df[numeric_cols].pct_change().mean() * 100,
|
116 |
-
'key_metrics': df[numeric_cols].describe()
|
117 |
}
|
118 |
|
119 |
-
return
|
120 |
|
121 |
except Exception as e:
|
122 |
-
logger.error(f"Error
|
123 |
-
|
124 |
-
|
125 |
-
def analyze_financials(self, income_summary, balance_summary):
|
126 |
-
"""Generate financial analysis and recommendations"""
|
127 |
-
try:
|
128 |
-
# Extract meaningful metrics
|
129 |
-
income_metrics = {
|
130 |
-
'Total Revenue': income_summary[1]['total_revenue'],
|
131 |
-
'Average Values': income_summary[1]['average_values'].mean(),
|
132 |
-
'Growth Rate': income_summary[1]['year_over_year_growth'].mean()
|
133 |
-
}
|
134 |
-
|
135 |
-
balance_metrics = {
|
136 |
-
'Total Assets': balance_summary[1]['total_revenue'],
|
137 |
-
'Average Values': balance_summary[1]['average_values'].mean(),
|
138 |
-
'Growth Rate': balance_summary[1]['year_over_year_growth'].mean()
|
139 |
-
}
|
140 |
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
- Growth Rate: {balance_metrics['Growth Rate']:.2f}%
|
154 |
-
|
155 |
-
Detailed Balance Metrics:
|
156 |
-
{balance_summary[0].to_string()}
|
157 |
-
"""
|
158 |
-
|
159 |
-
# Generate sentiment analysis
|
160 |
-
sentiment = self.sentiment_model(
|
161 |
-
financial_context,
|
162 |
-
truncation=True,
|
163 |
-
max_length=512
|
164 |
-
)[0]
|
165 |
-
|
166 |
-
# Generate business analysis
|
167 |
-
analysis_prompt = f"""[INST] As a financial analyst, provide a detailed analysis based on these financial metrics:
|
168 |
-
|
169 |
-
{financial_context}
|
170 |
|
171 |
-
|
172 |
|
173 |
-
|
|
|
|
|
|
|
|
|
174 |
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
|
|
|
|
|
|
|
|
185 |
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
|
|
191 |
|
192 |
-
|
193 |
-
[/INST]"""
|
194 |
|
195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
196 |
analysis_prompt,
|
197 |
max_length=1500,
|
198 |
-
do_sample=
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
|
|
|
|
206 |
except Exception as e:
|
207 |
-
logger.error(f"
|
208 |
-
return f"Error
|
209 |
|
210 |
-
def format_response(self,
|
211 |
-
|
|
|
|
|
212 |
try:
|
213 |
-
# Split
|
214 |
-
sections =
|
215 |
-
|
216 |
-
# Initialize output sections
|
217 |
-
status = []
|
218 |
-
insights = []
|
219 |
-
recommendations = []
|
220 |
-
|
221 |
-
# Process each section
|
222 |
-
current_section = None
|
223 |
-
for section in sections:
|
224 |
-
if "Business Status" in section:
|
225 |
-
current_section = status
|
226 |
-
elif "Key Insights" in section:
|
227 |
-
current_section = insights
|
228 |
-
elif "Strategic Recommendations" in section:
|
229 |
-
current_section = recommendations
|
230 |
-
elif current_section is not None:
|
231 |
-
current_section.append(section.strip())
|
232 |
|
233 |
-
# Format
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
234 |
output = [
|
235 |
-
"# Financial Analysis Report\n
|
236 |
-
f"## Overall Sentiment: {sentiment['label'].upper()} ({sentiment['score']:.
|
237 |
-
|
238 |
-
"
|
239 |
-
|
240 |
-
"".join(f"- {item}\n" for item in insights if item),
|
241 |
-
"\n## Strategic Recommendations & Roadmap\n",
|
242 |
-
"".join(f"- {item}\n" for item in recommendations if item)
|
243 |
]
|
244 |
|
245 |
-
return "".join(output)
|
246 |
|
247 |
except Exception as e:
|
248 |
logger.error(f"Error formatting response: {str(e)}")
|
@@ -254,22 +311,19 @@ def analyze_statements(income_statement, balance_sheet):
|
|
254 |
if not income_statement or not balance_sheet:
|
255 |
return "Please upload both Income Statement and Balance Sheet CSV files."
|
256 |
|
257 |
-
#
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
balance_valid, balance_msg = validate_financial_csv(balance_sheet, 'balance_sheet')
|
263 |
-
if not balance_valid:
|
264 |
-
return f"Invalid Balance Sheet: {balance_msg}"
|
265 |
-
|
266 |
-
# Process if valid
|
267 |
analyzer = FinancialAnalyzer()
|
268 |
-
income_summary = analyzer.process_csv(income_statement)
|
269 |
-
balance_summary = analyzer.process_csv(balance_sheet)
|
270 |
|
271 |
-
|
|
|
|
|
|
|
272 |
clear_gpu_memory()
|
|
|
273 |
return result
|
274 |
|
275 |
except Exception as e:
|
@@ -277,58 +331,167 @@ def analyze_statements(income_statement, balance_sheet):
|
|
277 |
return f"""Analysis Error: {str(e)}
|
278 |
|
279 |
Please ensure your CSV files:
|
280 |
-
1.
|
281 |
-
2.
|
282 |
-
3.
|
283 |
4. Are not corrupted"""
|
284 |
|
285 |
-
# Create Gradio interface
|
286 |
iface = gr.Interface(
|
287 |
fn=analyze_statements,
|
288 |
inputs=[
|
289 |
gr.File(
|
290 |
label="Upload Income Statement (CSV)",
|
291 |
file_types=[".csv"],
|
292 |
-
file_count="single"
|
|
|
293 |
),
|
294 |
gr.File(
|
295 |
label="Upload Balance Sheet (CSV)",
|
296 |
file_types=[".csv"],
|
297 |
-
file_count="single"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
298 |
)
|
299 |
],
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
-
|
313 |
-
-
|
314 |
-
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
-
|
320 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
321 |
)
|
322 |
|
323 |
-
#
|
324 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
325 |
try:
|
326 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
327 |
iface.launch(
|
328 |
-
share=False,
|
329 |
server_name="0.0.0.0",
|
330 |
-
server_port=7860
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
331 |
)
|
|
|
332 |
except Exception as e:
|
333 |
logger.error(f"Launch error: {str(e)}")
|
334 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
import pandas as pd
|
4 |
import torch
|
5 |
import logging
|
|
|
6 |
import gc
|
7 |
+
import re
|
8 |
+
from transformers import pipeline
|
9 |
+
from typing import Dict, List, Tuple, Optional
|
10 |
|
11 |
# Setup logging
|
12 |
logging.basicConfig(
|
|
|
19 |
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
20 |
logger.info(f"Using device: {DEVICE}")
|
21 |
|
22 |
+
class FinancialDataExtractor:
|
23 |
+
"""Extracts and processes financial data from raw CSV files"""
|
24 |
+
|
25 |
+
def __init__(self):
|
26 |
+
self.numeric_pattern = re.compile(r'[^\d.-]+')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
+
def clean_number(self, value: str) -> float:
|
29 |
+
"""Clean numeric values from financial statements"""
|
30 |
+
try:
|
31 |
+
if pd.isna(value) or value == '' or value == '-':
|
32 |
+
return 0.0
|
33 |
+
if isinstance(value, (int, float)):
|
34 |
+
return float(value)
|
35 |
+
|
36 |
+
# Remove currency symbols, spaces, commas
|
37 |
+
cleaned = str(value).replace('$', '').replace(',', '').strip()
|
38 |
+
# Handle parentheses for negative numbers
|
39 |
+
if '(' in cleaned and ')' in cleaned:
|
40 |
+
cleaned = '-' + cleaned.replace('(', '').replace(')', '')
|
41 |
+
return float(cleaned)
|
42 |
+
except:
|
43 |
+
return 0.0
|
44 |
+
|
45 |
+
def process_income_statement(self, df: pd.DataFrame) -> Dict:
|
46 |
+
"""Process income statement data"""
|
47 |
+
metrics = {}
|
48 |
+
years = [col for col in df.columns if str(col).isdigit()]
|
49 |
|
50 |
+
for year in years:
|
51 |
+
metrics[year] = {
|
52 |
+
'Revenue': 0,
|
53 |
+
'Expenses': 0,
|
54 |
+
'Profit': 0,
|
55 |
+
'Details': {}
|
56 |
+
}
|
57 |
+
|
58 |
+
# Extract revenue
|
59 |
+
total_revenue = df[df.iloc[:, 0].str.contains('Total Net Revenue|Revenue', na=False, regex=True)].iloc[0][year]
|
60 |
+
metrics[year]['Revenue'] = self.clean_number(total_revenue)
|
61 |
+
|
62 |
+
# Extract expenses
|
63 |
+
total_expenses = df[df.iloc[:, 0].str.contains('Total Expenses', na=False)].iloc[0][year]
|
64 |
+
metrics[year]['Expenses'] = self.clean_number(total_expenses)
|
65 |
+
|
66 |
+
# Extract profit
|
67 |
+
net_income = df[df.iloc[:, 0].str.contains('Net Income|Net Earnings', na=False, regex=True)].iloc[-1][year]
|
68 |
+
metrics[year]['Profit'] = self.clean_number(net_income)
|
69 |
+
|
70 |
+
# Extract additional details
|
71 |
+
metrics[year]['Details'] = {
|
72 |
+
'COGS': self.clean_number(df[df.iloc[:, 0].str.contains('Cost of Goods Sold', na=False)].iloc[0][year]),
|
73 |
+
'Gross_Profit': self.clean_number(df[df.iloc[:, 0].str.contains('Gross Profit', na=False)].iloc[0][year]),
|
74 |
+
'Operating_Expenses': self.clean_number(total_expenses),
|
75 |
+
'EBIT': self.clean_number(df[df.iloc[:, 0].str.contains('Earnings Before Interest & Taxes', na=False)].iloc[0][year]),
|
76 |
+
'Interest_Expense': self.clean_number(df[df.iloc[:, 0].str.contains('Interest Expense', na=False)].iloc[0][year]),
|
77 |
+
'Tax_Expense': self.clean_number(df[df.iloc[:, 0].str.contains('Income Taxes', na=False)].iloc[0][year])
|
78 |
+
}
|
79 |
+
|
80 |
+
return metrics
|
81 |
+
|
82 |
+
def process_balance_sheet(self, df: pd.DataFrame) -> Dict:
|
83 |
+
"""Process balance sheet data"""
|
84 |
+
metrics = {}
|
85 |
+
years = [col for col in df.columns if str(col).isdigit()]
|
86 |
|
87 |
+
for year in years:
|
88 |
+
metrics[year] = {
|
89 |
+
'Assets': 0,
|
90 |
+
'Liabilities': 0,
|
91 |
+
'Equity': 0,
|
92 |
+
'Details': {}
|
93 |
+
}
|
94 |
+
|
95 |
+
# Extract main metrics
|
96 |
+
metrics[year]['Assets'] = self.clean_number(df[df.iloc[:, 0].str.contains('Total Assets', na=False)].iloc[0][year])
|
97 |
+
metrics[year]['Liabilities'] = self.clean_number(df[df.iloc[:, 0].str.contains('Total Liabilities', na=False)].iloc[0][year])
|
98 |
+
metrics[year]['Equity'] = self.clean_number(df[df.iloc[:, 0].str.contains("Shareholder's Equity", na=False)].iloc[-1][year])
|
99 |
+
|
100 |
+
# Extract additional details
|
101 |
+
metrics[year]['Details'] = {
|
102 |
+
'Current_Assets': self.clean_number(df[df.iloc[:, 0].str.contains('Total current assets', na=False)].iloc[0][year]),
|
103 |
+
'Fixed_Assets': self.clean_number(df[df.iloc[:, 0].str.contains('Property & Equipment', na=False)].iloc[0][year]),
|
104 |
+
'Current_Liabilities': self.clean_number(df[df.iloc[:, 0].str.contains('Total current liabilities', na=False)].iloc[0][year]),
|
105 |
+
'Long_Term_Debt': self.clean_number(df[df.iloc[:, 0].str.contains('Long-term debt', na=False)].iloc[0][year]),
|
106 |
+
'Retained_Earnings': self.clean_number(df[df.iloc[:, 0].str.contains('Retained Earnings', na=False)].iloc[0][year])
|
107 |
+
}
|
108 |
+
|
109 |
+
return metrics
|
110 |
|
111 |
class FinancialAnalyzer:
|
112 |
+
"""Enhanced Financial Analyzer using small models"""
|
113 |
|
114 |
def __init__(self):
|
115 |
+
self.extractor = FinancialDataExtractor()
|
116 |
self.sentiment_model = None
|
117 |
self.analysis_model = None
|
118 |
self.load_models()
|
|
|
128 |
truncation=True
|
129 |
)
|
130 |
|
131 |
+
# Load small model for analysis
|
132 |
self.analysis_model = pipeline(
|
133 |
"text-generation",
|
134 |
model="TinyLlama/TinyLlama-1.1B-Chat-v1.0",
|
|
|
140 |
logger.error(f"Error loading models: {str(e)}")
|
141 |
raise
|
142 |
|
143 |
+
def calculate_financial_ratios(self, income_metrics: Dict, balance_metrics: Dict, year: str) -> Dict:
|
144 |
+
"""Calculate key financial ratios"""
|
145 |
try:
|
146 |
+
income = income_metrics[year]
|
147 |
+
balance = balance_metrics[year]
|
|
|
|
|
|
|
148 |
|
149 |
+
ratios = {
|
150 |
+
'Profitability': {
|
151 |
+
'Gross_Margin': (income['Details']['Gross_Profit'] / income['Revenue']) * 100,
|
152 |
+
'Operating_Margin': (income['Details']['EBIT'] / income['Revenue']) * 100,
|
153 |
+
'Net_Margin': (income['Profit'] / income['Revenue']) * 100
|
154 |
+
},
|
155 |
+
'Liquidity': {
|
156 |
+
'Current_Ratio': balance['Details']['Current_Assets'] / balance['Details']['Current_Liabilities'],
|
157 |
+
'Quick_Ratio': (balance['Details']['Current_Assets'] - 0) / balance['Details']['Current_Liabilities'],
|
158 |
+
'Cash_Ratio': 0 # Would need cash balance for this
|
159 |
+
},
|
160 |
+
'Solvency': {
|
161 |
+
'Debt_to_Equity': balance['Liabilities'] / balance['Equity'],
|
162 |
+
'Debt_Ratio': balance['Liabilities'] / balance['Assets'],
|
163 |
+
'Interest_Coverage': income['Details']['EBIT'] / income['Details']['Interest_Expense'] if income['Details']['Interest_Expense'] != 0 else float('inf')
|
164 |
+
},
|
165 |
+
'Efficiency': {
|
166 |
+
'Asset_Turnover': income['Revenue'] / balance['Assets'],
|
167 |
+
'ROE': income['Profit'] / balance['Equity'] * 100,
|
168 |
+
'ROA': income['Profit'] / balance['Assets'] * 100
|
169 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
170 |
}
|
171 |
|
172 |
+
return ratios
|
173 |
|
174 |
except Exception as e:
|
175 |
+
logger.error(f"Error calculating ratios: {str(e)}")
|
176 |
+
return {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
177 |
|
178 |
+
def generate_analysis_prompt(self, income_metrics: Dict, balance_metrics: Dict, ratios: Dict) -> str:
|
179 |
+
"""Generate analysis prompt for LLM"""
|
180 |
+
latest_year = max(income_metrics.keys())
|
181 |
+
earliest_year = min(income_metrics.keys())
|
182 |
+
|
183 |
+
# Calculate growth metrics
|
184 |
+
revenue_growth = ((income_metrics[latest_year]['Revenue'] - income_metrics[earliest_year]['Revenue'])
|
185 |
+
/ income_metrics[earliest_year]['Revenue'] * 100)
|
186 |
+
profit_growth = ((income_metrics[latest_year]['Profit'] - income_metrics[earliest_year]['Profit'])
|
187 |
+
/ income_metrics[earliest_year]['Profit'] * 100)
|
188 |
+
asset_growth = ((balance_metrics[latest_year]['Assets'] - balance_metrics[earliest_year]['Assets'])
|
189 |
+
/ balance_metrics[earliest_year]['Assets'] * 100)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
190 |
|
191 |
+
prompt = f"""[INST] As a financial analyst, provide a comprehensive analysis of this company:
|
192 |
|
193 |
+
Financial Performance ({earliest_year}-{latest_year}):
|
194 |
+
1. Growth Metrics:
|
195 |
+
- Revenue Growth: {revenue_growth:.1f}%
|
196 |
+
- Profit Growth: {profit_growth:.1f}%
|
197 |
+
- Asset Growth: {asset_growth:.1f}%
|
198 |
|
199 |
+
2. Latest Year Performance ({latest_year}):
|
200 |
+
- Revenue: ${income_metrics[latest_year]['Revenue']:,.0f}
|
201 |
+
- Net Profit: ${income_metrics[latest_year]['Profit']:,.0f}
|
202 |
+
- Total Assets: ${balance_metrics[latest_year]['Assets']:,.0f}
|
203 |
|
204 |
+
3. Key Ratios ({latest_year}):
|
205 |
+
- Profitability:
|
206 |
+
* Gross Margin: {ratios['Profitability']['Gross_Margin']:.1f}%
|
207 |
+
* Operating Margin: {ratios['Profitability']['Operating_Margin']:.1f}%
|
208 |
+
* Net Margin: {ratios['Profitability']['Net_Margin']:.1f}%
|
209 |
+
- Financial Health:
|
210 |
+
* Current Ratio: {ratios['Liquidity']['Current_Ratio']:.2f}
|
211 |
+
* Debt-to-Equity: {ratios['Solvency']['Debt_to_Equity']:.2f}
|
212 |
+
* ROE: {ratios['Efficiency']['ROE']:.1f}%
|
213 |
|
214 |
+
Provide:
|
215 |
+
1. Overall financial health assessment
|
216 |
+
2. Key performance insights and trends
|
217 |
+
3. Risk analysis and concerns
|
218 |
+
4. Specific strategic recommendations
|
219 |
+
[/INST]"""
|
220 |
|
221 |
+
return prompt
|
|
|
222 |
|
223 |
+
def analyze_financials(self, income_df: pd.DataFrame, balance_df: pd.DataFrame) -> str:
|
224 |
+
"""Generate complete financial analysis"""
|
225 |
+
try:
|
226 |
+
# Extract metrics
|
227 |
+
income_metrics = self.extractor.process_income_statement(income_df)
|
228 |
+
balance_metrics = self.extractor.process_balance_sheet(balance_df)
|
229 |
+
|
230 |
+
# Calculate ratios for latest year
|
231 |
+
latest_year = max(income_metrics.keys())
|
232 |
+
ratios = self.calculate_financial_ratios(income_metrics, balance_metrics, latest_year)
|
233 |
+
|
234 |
+
# Generate analysis prompt
|
235 |
+
analysis_prompt = self.generate_analysis_prompt(income_metrics, balance_metrics, ratios)
|
236 |
+
|
237 |
+
# Get sentiment analysis
|
238 |
+
sentiment = self.sentiment_model(
|
239 |
+
analysis_prompt[:512],
|
240 |
+
truncation=True
|
241 |
+
)[0]
|
242 |
+
|
243 |
+
# Generate analysis
|
244 |
+
analysis = self.analysis_model(
|
245 |
analysis_prompt,
|
246 |
max_length=1500,
|
247 |
+
do_sample=True,
|
248 |
+
temperature=0.7,
|
249 |
+
num_return_sequences=1
|
250 |
+
)[0]['generated_text']
|
251 |
+
|
252 |
+
# Format the output
|
253 |
+
output = self.format_response(analysis, sentiment, income_metrics, balance_metrics, ratios, latest_year)
|
254 |
+
|
255 |
+
return output
|
256 |
+
|
257 |
except Exception as e:
|
258 |
+
logger.error(f"Analysis error: {str(e)}")
|
259 |
+
return f"Error in analysis: {str(e)}"
|
260 |
|
261 |
+
def format_response(self, analysis: str, sentiment: Dict,
|
262 |
+
income_metrics: Dict, balance_metrics: Dict,
|
263 |
+
ratios: Dict, year: str) -> str:
|
264 |
+
"""Format the analysis response"""
|
265 |
try:
|
266 |
+
# Split analysis into sections
|
267 |
+
sections = analysis.split('\n\n')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
268 |
|
269 |
+
# Format metrics section
|
270 |
+
metrics_section = f"""
|
271 |
+
### Key Financial Metrics ({year})
|
272 |
+
- Revenue: ${income_metrics[year]['Revenue']:,.0f}
|
273 |
+
- Net Profit: ${income_metrics[year]['Profit']:,.0f}
|
274 |
+
- Total Assets: ${balance_metrics[year]['Assets']:,.0f}
|
275 |
+
|
276 |
+
### Financial Ratios
|
277 |
+
- Profitability:
|
278 |
+
* Gross Margin: {ratios['Profitability']['Gross_Margin']:.1f}%
|
279 |
+
* Operating Margin: {ratios['Profitability']['Operating_Margin']:.1f}%
|
280 |
+
* Net Margin: {ratios['Profitability']['Net_Margin']:.1f}%
|
281 |
+
|
282 |
+
- Financial Health:
|
283 |
+
* Current Ratio: {ratios['Liquidity']['Current_Ratio']:.2f}
|
284 |
+
* Debt-to-Equity: {ratios['Solvency']['Debt_to_Equity']:.2f}
|
285 |
+
* Interest Coverage: {ratios['Solvency']['Interest_Coverage']:.2f}
|
286 |
+
|
287 |
+
- Efficiency:
|
288 |
+
* ROE: {ratios['Efficiency']['ROE']:.1f}%
|
289 |
+
* ROA: {ratios['Efficiency']['ROA']:.1f}%
|
290 |
+
* Asset Turnover: {ratios['Efficiency']['Asset_Turnover']:.2f}
|
291 |
+
"""
|
292 |
+
|
293 |
+
# Combine all sections
|
294 |
output = [
|
295 |
+
"# Financial Analysis Report\n",
|
296 |
+
f"## Overall Sentiment: {sentiment['label'].upper()} ({sentiment['score']:.1%})\n",
|
297 |
+
metrics_section,
|
298 |
+
"\n## Analysis\n",
|
299 |
+
analysis
|
|
|
|
|
|
|
300 |
]
|
301 |
|
302 |
+
return "\n".join(output)
|
303 |
|
304 |
except Exception as e:
|
305 |
logger.error(f"Error formatting response: {str(e)}")
|
|
|
311 |
if not income_statement or not balance_sheet:
|
312 |
return "Please upload both Income Statement and Balance Sheet CSV files."
|
313 |
|
314 |
+
# Read files
|
315 |
+
income_df = pd.read_csv(income_statement.name)
|
316 |
+
balance_df = pd.read_csv(balance_sheet.name)
|
317 |
+
|
318 |
+
# Create analyzer instance
|
|
|
|
|
|
|
|
|
|
|
319 |
analyzer = FinancialAnalyzer()
|
|
|
|
|
320 |
|
321 |
+
# Generate analysis
|
322 |
+
result = analyzer.analyze_financials(income_df, balance_df)
|
323 |
+
|
324 |
+
# Clear memory
|
325 |
clear_gpu_memory()
|
326 |
+
|
327 |
return result
|
328 |
|
329 |
except Exception as e:
|
|
|
331 |
return f"""Analysis Error: {str(e)}
|
332 |
|
333 |
Please ensure your CSV files:
|
334 |
+
1. Contain recognizable financial metrics
|
335 |
+
2. Have clear period/year columns
|
336 |
+
3. Use consistent number formatting
|
337 |
4. Are not corrupted"""
|
338 |
|
339 |
+
# Create Gradio interface with enhanced features
|
340 |
iface = gr.Interface(
|
341 |
fn=analyze_statements,
|
342 |
inputs=[
|
343 |
gr.File(
|
344 |
label="Upload Income Statement (CSV)",
|
345 |
file_types=[".csv"],
|
346 |
+
file_count="single",
|
347 |
+
info="Upload your income statement in CSV format"
|
348 |
),
|
349 |
gr.File(
|
350 |
label="Upload Balance Sheet (CSV)",
|
351 |
file_types=[".csv"],
|
352 |
+
file_count="single",
|
353 |
+
info="Upload your balance sheet in CSV format"
|
354 |
+
)
|
355 |
+
],
|
356 |
+
outputs=[
|
357 |
+
gr.Markdown(
|
358 |
+
label="Financial Analysis Report",
|
359 |
+
show_label=True
|
360 |
)
|
361 |
],
|
362 |
+
title="Advanced Financial Statement Analyzer",
|
363 |
+
description="""## Professional Financial Analysis Tool
|
364 |
+
|
365 |
+
### Key Features:
|
366 |
+
- Comprehensive financial analysis using AI
|
367 |
+
- Sentiment analysis of financial performance
|
368 |
+
- Key ratio calculations and trend analysis
|
369 |
+
- Strategic recommendations
|
370 |
+
|
371 |
+
### Supported Financial Statement Formats:
|
372 |
+
|
373 |
+
#### Income Statement should include:
|
374 |
+
- Revenue/Sales information
|
375 |
+
- Cost and Expense details
|
376 |
+
- Profit/Income figures
|
377 |
+
- Operating metrics
|
378 |
+
- Period/Year information
|
379 |
+
|
380 |
+
#### Balance Sheet should include:
|
381 |
+
- Asset information (Current & Non-current)
|
382 |
+
- Liability details (Current & Long-term)
|
383 |
+
- Equity components
|
384 |
+
- Period/Year information
|
385 |
+
|
386 |
+
### Analysis Output:
|
387 |
+
1. Overall Financial Health Assessment
|
388 |
+
2. Key Performance Metrics & Ratios
|
389 |
+
3. Trend Analysis
|
390 |
+
4. Risk Assessment
|
391 |
+
5. Strategic Recommendations
|
392 |
+
""",
|
393 |
+
article="""### Usage Tips:
|
394 |
+
1. Ensure your CSV files are properly formatted
|
395 |
+
2. Data should be in chronological order
|
396 |
+
3. Numbers can include commas and currency symbols
|
397 |
+
4. Negative numbers can be in parentheses
|
398 |
+
5. Common headers will be automatically recognized
|
399 |
+
|
400 |
+
### About the Analysis:
|
401 |
+
- Uses FinBERT for sentiment analysis
|
402 |
+
- Employs TinyLLama for strategic insights
|
403 |
+
- Calculates over 15 financial ratios
|
404 |
+
- Provides actionable recommendations
|
405 |
+
""",
|
406 |
+
examples=[
|
407 |
+
["sample_income_statement.csv", "sample_balance_sheet.csv"]
|
408 |
+
],
|
409 |
+
theme=gr.themes.Soft(
|
410 |
+
primary_hue="blue",
|
411 |
+
secondary_hue="purple",
|
412 |
+
),
|
413 |
+
css="""
|
414 |
+
.gradio-container {
|
415 |
+
font-family: 'IBM Plex Sans', sans-serif;
|
416 |
+
}
|
417 |
+
.gr-button {
|
418 |
+
color: white;
|
419 |
+
border-radius: 8px;
|
420 |
+
background: linear-gradient(45deg, #1f6feb, #4688f1);
|
421 |
+
}
|
422 |
+
.gr-button:hover {
|
423 |
+
background: linear-gradient(45deg, #1856c5, #3b78e7);
|
424 |
+
}
|
425 |
+
.gr-form {
|
426 |
+
border-radius: 12px;
|
427 |
+
background: white;
|
428 |
+
padding: 24px;
|
429 |
+
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
430 |
+
}
|
431 |
+
.gr-box {
|
432 |
+
border-radius: 8px;
|
433 |
+
}
|
434 |
+
"""
|
435 |
)
|
436 |
|
437 |
+
# Add custom error handling
|
438 |
+
def custom_error_handler(err):
|
439 |
+
logger.error(f"Application error: {str(err)}")
|
440 |
+
return """### Error in Analysis
|
441 |
+
|
442 |
+
An error occurred while processing your financial statements. Please check:
|
443 |
+
1. File format is correct CSV
|
444 |
+
2. Contains required financial metrics
|
445 |
+
3. Data is properly formatted
|
446 |
+
4. No corrupted or missing values
|
447 |
+
|
448 |
+
If the problem persists, try:
|
449 |
+
- Removing any special formatting
|
450 |
+
- Ensuring consistent date/period format
|
451 |
+
- Checking for missing required fields
|
452 |
+
|
453 |
+
Error details: {str(err)}
|
454 |
+
"""
|
455 |
+
|
456 |
+
iface.error_handler = custom_error_handler
|
457 |
+
|
458 |
+
# Launch configurations
|
459 |
+
def main():
|
460 |
try:
|
461 |
+
# Setup memory management
|
462 |
+
gc.enable()
|
463 |
+
|
464 |
+
# Configure Gradio
|
465 |
+
gr.close_all()
|
466 |
+
|
467 |
+
# Launch with custom configurations
|
468 |
+
iface.queue(concurrency_count=1)
|
469 |
iface.launch(
|
|
|
470 |
server_name="0.0.0.0",
|
471 |
+
server_port=7860,
|
472 |
+
share=False,
|
473 |
+
enable_queue=True,
|
474 |
+
max_threads=4,
|
475 |
+
auth=None, # Add authentication if needed
|
476 |
+
ssl_keyfile=None, # Add SSL if needed
|
477 |
+
ssl_certfile=None,
|
478 |
+
ssl_keyfile_password=None,
|
479 |
+
show_error=True,
|
480 |
+
show_tips=True,
|
481 |
+
analytics_enabled=False,
|
482 |
+
debug=False
|
483 |
)
|
484 |
+
|
485 |
except Exception as e:
|
486 |
logger.error(f"Launch error: {str(e)}")
|
487 |
+
raise
|
488 |
+
|
489 |
+
if __name__ == "__main__":
|
490 |
+
try:
|
491 |
+
main()
|
492 |
+
except KeyboardInterrupt:
|
493 |
+
logger.info("Shutting down gracefully...")
|
494 |
+
gr.close_all()
|
495 |
+
except Exception as e:
|
496 |
+
logger.error(f"Fatal error: {str(e)}")
|
497 |
+
sys.exit(1)
|