Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -25,9 +25,7 @@ class FinancialAnalyzer:
|
|
25 |
"""Clean and convert numerical values"""
|
26 |
try:
|
27 |
if isinstance(value, str):
|
28 |
-
# Remove currency symbols, commas, spaces
|
29 |
value = value.replace('$', '').replace(',', '').strip()
|
30 |
-
# Handle parentheses for negative numbers
|
31 |
if '(' in value and ')' in value:
|
32 |
value = '-' + value.replace('(', '').replace(')', '')
|
33 |
return float(value or 0)
|
@@ -39,7 +37,6 @@ class FinancialAnalyzer:
|
|
39 |
try:
|
40 |
with open(file_path, 'r') as f:
|
41 |
content = f.read()
|
42 |
-
# Simple check for Markdown structure
|
43 |
return any(line.startswith('#') or '|' in line for line in content.split('\n'))
|
44 |
except:
|
45 |
return False
|
@@ -60,14 +57,13 @@ class FinancialAnalyzer:
|
|
60 |
current_table = []
|
61 |
headers = None
|
62 |
elif '|' in line:
|
63 |
-
if '-|-' not in line:
|
64 |
row = [cell.strip() for cell in line.split('|')[1:-1]]
|
65 |
if not headers:
|
66 |
headers = row
|
67 |
else:
|
68 |
current_table.append(row)
|
69 |
|
70 |
-
# Process last table
|
71 |
if current_table and headers:
|
72 |
data[current_section] = self.process_table(headers, current_table)
|
73 |
|
@@ -91,69 +87,122 @@ class FinancialAnalyzer:
|
|
91 |
print(f"Error processing table: {str(e)}")
|
92 |
return {}
|
93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
def extract_metrics(self, income_data, balance_data):
|
95 |
"""Extract and calculate key financial metrics"""
|
96 |
try:
|
97 |
-
# Get current and previous year values for growth calculations
|
98 |
metrics = {
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
|
|
123 |
}
|
124 |
-
|
125 |
-
|
126 |
-
# Calculate financial ratios
|
127 |
revenue_2025 = metrics["Revenue"]["2025"]
|
128 |
if revenue_2025 != 0:
|
129 |
-
# Profitability Ratios
|
130 |
metrics["Ratios"] = {
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
# Growth Rates
|
148 |
-
"Revenue_Growth": ((metrics["Revenue"]["2025"] / metrics["Revenue"]["2024"]) - 1) * 100,
|
149 |
-
"5Year_Revenue_CAGR": ((metrics["Revenue"]["2025"] / metrics["Revenue"]["2021"]) ** (1/4) - 1) * 100
|
150 |
}
|
151 |
-
|
152 |
return metrics
|
153 |
except Exception as e:
|
154 |
print(f"Error extracting metrics: {str(e)}")
|
155 |
return {}
|
156 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
def generate_analysis(self, prompt):
|
158 |
"""Generate analysis using TinyLlama"""
|
159 |
try:
|
@@ -161,7 +210,7 @@ class FinancialAnalyzer:
|
|
161 |
|
162 |
outputs = self.model.generate(
|
163 |
inputs["input_ids"],
|
164 |
-
max_new_tokens=800,
|
165 |
temperature=0.7,
|
166 |
top_p=0.9,
|
167 |
do_sample=True,
|
@@ -170,7 +219,6 @@ class FinancialAnalyzer:
|
|
170 |
)
|
171 |
|
172 |
analysis = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
|
173 |
-
# Clean up the response
|
174 |
analysis = analysis.split("<human>")[-1].strip()
|
175 |
return analysis
|
176 |
except Exception as e:
|
@@ -179,28 +227,20 @@ class FinancialAnalyzer:
|
|
179 |
def analyze_financials(self, balance_sheet_file, income_stmt_file):
|
180 |
"""Main analysis function"""
|
181 |
try:
|
182 |
-
# Validate files
|
183 |
if not (self.is_valid_markdown(balance_sheet_file) and self.is_valid_markdown(income_stmt_file)):
|
184 |
return "Error: One or both files are invalid or not in Markdown format."
|
185 |
|
186 |
-
# Read files
|
187 |
with open(balance_sheet_file, 'r') as f:
|
188 |
balance_sheet = f.read()
|
189 |
with open(income_stmt_file, 'r') as f:
|
190 |
income_stmt = f.read()
|
191 |
|
192 |
-
# Parse financial data
|
193 |
income_data = self.parse_financial_data(income_stmt)
|
194 |
balance_data = self.parse_financial_data(balance_sheet)
|
195 |
-
|
196 |
-
# Extract key metrics
|
197 |
metrics = self.extract_metrics(income_data, balance_data)
|
198 |
-
|
199 |
-
# Generate and get analysis
|
200 |
-
prompt = self.generate_analysis_prompt(metrics)
|
201 |
analysis = self.generate_analysis(prompt)
|
202 |
|
203 |
-
# Prepare results
|
204 |
results = {
|
205 |
"Financial Analysis": {
|
206 |
"Key Metrics": metrics,
|
@@ -233,4 +273,4 @@ def create_interface():
|
|
233 |
|
234 |
if __name__ == "__main__":
|
235 |
iface = create_interface()
|
236 |
-
iface.launch()
|
|
|
25 |
"""Clean and convert numerical values"""
|
26 |
try:
|
27 |
if isinstance(value, str):
|
|
|
28 |
value = value.replace('$', '').replace(',', '').strip()
|
|
|
29 |
if '(' in value and ')' in value:
|
30 |
value = '-' + value.replace('(', '').replace(')', '')
|
31 |
return float(value or 0)
|
|
|
37 |
try:
|
38 |
with open(file_path, 'r') as f:
|
39 |
content = f.read()
|
|
|
40 |
return any(line.startswith('#') or '|' in line for line in content.split('\n'))
|
41 |
except:
|
42 |
return False
|
|
|
57 |
current_table = []
|
58 |
headers = None
|
59 |
elif '|' in line:
|
60 |
+
if '-|-' not in line:
|
61 |
row = [cell.strip() for cell in line.split('|')[1:-1]]
|
62 |
if not headers:
|
63 |
headers = row
|
64 |
else:
|
65 |
current_table.append(row)
|
66 |
|
|
|
67 |
if current_table and headers:
|
68 |
data[current_section] = self.process_table(headers, current_table)
|
69 |
|
|
|
87 |
print(f"Error processing table: {str(e)}")
|
88 |
return {}
|
89 |
|
90 |
+
def get_nested_value(self, data, section, key, year):
|
91 |
+
"""Safely get nested dictionary value"""
|
92 |
+
try:
|
93 |
+
return data.get(section, {}).get(key, {}).get(str(year), 0)
|
94 |
+
except:
|
95 |
+
return 0
|
96 |
+
|
97 |
def extract_metrics(self, income_data, balance_data):
|
98 |
"""Extract and calculate key financial metrics"""
|
99 |
try:
|
|
|
100 |
metrics = {
|
101 |
+
"Revenue": {
|
102 |
+
"2025": self.get_nested_value(income_data, "Revenue", "Total Net Revenue", "2025"),
|
103 |
+
"2024": self.get_nested_value(income_data, "Revenue", "Total Net Revenue", "2024"),
|
104 |
+
"2021": self.get_nested_value(income_data, "Revenue", "Total Net Revenue", "2021")
|
105 |
+
},
|
106 |
+
"Profitability": {
|
107 |
+
"Gross_Profit_2025": self.get_nested_value(income_data, "Cost and Gross Profit", "Gross Profit", "2025"),
|
108 |
+
"EBIT_2025": self.get_nested_value(income_data, "Profit Summary", "EBIT", "2025"),
|
109 |
+
"Net_Earnings_2025": self.get_nested_value(income_data, "Profit Summary", "Net Earnings", "2025"),
|
110 |
+
"Operating_Expenses_2025": self.get_nested_value(income_data, "Operating Expenses", "Total Operating Expenses", "2025")
|
111 |
+
},
|
112 |
+
"Balance_Sheet": {
|
113 |
+
"Total_Assets_2025": self.get_nested_value(balance_data, "Key Totals", "Total_Assets", "2025"),
|
114 |
+
"Current_Assets_2025": self.get_nested_value(balance_data, "Key Totals", "Total_Current_Assets", "2025"),
|
115 |
+
"Total_Liabilities_2025": self.get_nested_value(balance_data, "Key Totals", "Total_Liabilities", "2025"),
|
116 |
+
"Current_Liabilities_2025": self.get_nested_value(balance_data, "Key Totals", "Total_Current_Liabilities", "2025"),
|
117 |
+
"Equity_2025": self.get_nested_value(balance_data, "Key Totals", "Total_Shareholders_Equity", "2025"),
|
118 |
+
"Inventory_2025": self.get_nested_value(balance_data, "Balance Sheet Data 2021-2025", "Inventory", "2025"),
|
119 |
+
"Accounts_Receivable_2025": self.get_nested_value(balance_data, "Balance Sheet Data 2021-2025", "Accounts_Receivable", "2025"),
|
120 |
+
"Long_Term_Debt_2025": self.get_nested_value(balance_data, "Balance Sheet Data 2021-2025", "Long_Term_Debt", "2025")
|
121 |
+
},
|
122 |
+
"Cash_Flow": {
|
123 |
+
"Depreciation_2025": self.get_nested_value(income_data, "Operating Expenses", "Depreciation & Amortization", "2025"),
|
124 |
+
"Interest_Expense_2025": self.get_nested_value(income_data, "Profit Summary", "Interest Expense", "2025")
|
125 |
+
}
|
126 |
}
|
127 |
+
|
|
|
|
|
128 |
revenue_2025 = metrics["Revenue"]["2025"]
|
129 |
if revenue_2025 != 0:
|
|
|
130 |
metrics["Ratios"] = {
|
131 |
+
"Gross_Margin": (metrics["Profitability"]["Gross_Profit_2025"] / revenue_2025) * 100,
|
132 |
+
"Operating_Margin": (metrics["Profitability"]["EBIT_2025"] / revenue_2025) * 100,
|
133 |
+
"Net_Margin": (metrics["Profitability"]["Net_Earnings_2025"] / revenue_2025) * 100,
|
134 |
+
|
135 |
+
"Current_Ratio": metrics["Balance_Sheet"]["Current_Assets_2025"] / metrics["Balance_Sheet"]["Current_Liabilities_2025"] if metrics["Balance_Sheet"]["Current_Liabilities_2025"] != 0 else 0,
|
136 |
+
"Quick_Ratio": (metrics["Balance_Sheet"]["Current_Assets_2025"] - metrics["Balance_Sheet"]["Inventory_2025"]) / metrics["Balance_Sheet"]["Current_Liabilities_2025"] if metrics["Balance_Sheet"]["Current_Liabilities_2025"] != 0 else 0,
|
137 |
+
|
138 |
+
"Asset_Turnover": revenue_2025 / metrics["Balance_Sheet"]["Total_Assets_2025"] if metrics["Balance_Sheet"]["Total_Assets_2025"] != 0 else 0,
|
139 |
+
"Receivables_Turnover": revenue_2025 / metrics["Balance_Sheet"]["Accounts_Receivable_2025"] if metrics["Balance_Sheet"]["Accounts_Receivable_2025"] != 0 else 0,
|
140 |
+
|
141 |
+
"Debt_to_Equity": metrics["Balance_Sheet"]["Total_Liabilities_2025"] / metrics["Balance_Sheet"]["Equity_2025"] if metrics["Balance_Sheet"]["Equity_2025"] != 0 else 0,
|
142 |
+
"Interest_Coverage": metrics["Profitability"]["EBIT_2025"] / metrics["Cash_Flow"]["Interest_Expense_2025"] if metrics["Cash_Flow"]["Interest_Expense_2025"] != 0 else 0,
|
143 |
+
|
144 |
+
"Revenue_Growth": ((metrics["Revenue"]["2025"] / metrics["Revenue"]["2024"]) - 1) * 100 if metrics["Revenue"]["2024"] != 0 else 0,
|
145 |
+
"5Year_Revenue_CAGR": ((metrics["Revenue"]["2025"] / metrics["Revenue"]["2021"]) ** (1/4) - 1) * 100 if metrics["Revenue"]["2021"] != 0 else 0
|
|
|
|
|
|
|
|
|
146 |
}
|
147 |
+
|
148 |
return metrics
|
149 |
except Exception as e:
|
150 |
print(f"Error extracting metrics: {str(e)}")
|
151 |
return {}
|
152 |
|
153 |
+
def generate_prompt(self, metrics):
|
154 |
+
"""Create analysis prompt from metrics"""
|
155 |
+
try:
|
156 |
+
return f"""<human>
|
157 |
+
Please provide a comprehensive financial analysis for 2025 with detailed insights on:
|
158 |
+
|
159 |
+
1. Revenue and Growth:
|
160 |
+
- Total Revenue: ${metrics['Revenue']['2025']:,.1f}M
|
161 |
+
- YoY Growth Rate: {metrics['Ratios'].get('Revenue_Growth', 0):,.1f}%
|
162 |
+
- 5-Year CAGR: {metrics['Ratios'].get('5Year_Revenue_CAGR', 0):,.1f}%
|
163 |
+
|
164 |
+
2. Profitability Analysis:
|
165 |
+
- Gross Profit: ${metrics['Profitability']['Gross_Profit_2025']:,.1f}M
|
166 |
+
- EBIT: ${metrics['Profitability']['EBIT_2025']:,.1f}M
|
167 |
+
- Net Earnings: ${metrics['Profitability']['Net_Earnings_2025']:,.1f}M
|
168 |
+
- Margin Analysis:
|
169 |
+
* Gross Margin: {metrics['Ratios'].get('Gross_Margin', 0):,.1f}%
|
170 |
+
* Operating Margin: {metrics['Ratios'].get('Operating_Margin', 0):,.1f}%
|
171 |
+
* Net Margin: {metrics['Ratios'].get('Net_Margin', 0):,.1f}%
|
172 |
+
|
173 |
+
3. Balance Sheet Strength:
|
174 |
+
- Total Assets: ${metrics['Balance_Sheet']['Total_Assets_2025']:,.1f}M
|
175 |
+
- Total Liabilities: ${metrics['Balance_Sheet']['Total_Liabilities_2025']:,.1f}M
|
176 |
+
- Shareholders' Equity: ${metrics['Balance_Sheet']['Equity_2025']:,.1f}M
|
177 |
+
|
178 |
+
4. Key Financial Ratios:
|
179 |
+
- Liquidity:
|
180 |
+
* Current Ratio: {metrics['Ratios'].get('Current_Ratio', 0):,.2f}
|
181 |
+
* Quick Ratio: {metrics['Ratios'].get('Quick_Ratio', 0):,.2f}
|
182 |
+
- Efficiency:
|
183 |
+
* Asset Turnover: {metrics['Ratios'].get('Asset_Turnover', 0):,.2f}
|
184 |
+
* Receivables Turnover: {metrics['Ratios'].get('Receivables_Turnover', 0):,.2f}
|
185 |
+
- Solvency:
|
186 |
+
* Debt-to-Equity: {metrics['Ratios'].get('Debt_to_Equity', 0):,.2f}
|
187 |
+
* Interest Coverage: {metrics['Ratios'].get('Interest_Coverage', 0):,.2f}
|
188 |
+
|
189 |
+
Please provide:
|
190 |
+
1. An assessment of overall financial health and performance trends
|
191 |
+
2. Key strengths and potential areas of concern
|
192 |
+
3. Analysis of operational efficiency and working capital management
|
193 |
+
4. Evaluation of capital structure and debt management
|
194 |
+
5. Specific recommendations for:
|
195 |
+
- Improving operational efficiency
|
196 |
+
- Optimizing capital structure
|
197 |
+
- Enhancing shareholder value
|
198 |
+
- Managing key risks identified
|
199 |
+
|
200 |
+
Include quantitative support for your analysis and recommendations.
|
201 |
+
</human>"""
|
202 |
+
except Exception as e:
|
203 |
+
print(f"Error generating prompt: {str(e)}")
|
204 |
+
return ""
|
205 |
+
|
206 |
def generate_analysis(self, prompt):
|
207 |
"""Generate analysis using TinyLlama"""
|
208 |
try:
|
|
|
210 |
|
211 |
outputs = self.model.generate(
|
212 |
inputs["input_ids"],
|
213 |
+
max_new_tokens=800,
|
214 |
temperature=0.7,
|
215 |
top_p=0.9,
|
216 |
do_sample=True,
|
|
|
219 |
)
|
220 |
|
221 |
analysis = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
|
|
|
222 |
analysis = analysis.split("<human>")[-1].strip()
|
223 |
return analysis
|
224 |
except Exception as e:
|
|
|
227 |
def analyze_financials(self, balance_sheet_file, income_stmt_file):
|
228 |
"""Main analysis function"""
|
229 |
try:
|
|
|
230 |
if not (self.is_valid_markdown(balance_sheet_file) and self.is_valid_markdown(income_stmt_file)):
|
231 |
return "Error: One or both files are invalid or not in Markdown format."
|
232 |
|
|
|
233 |
with open(balance_sheet_file, 'r') as f:
|
234 |
balance_sheet = f.read()
|
235 |
with open(income_stmt_file, 'r') as f:
|
236 |
income_stmt = f.read()
|
237 |
|
|
|
238 |
income_data = self.parse_financial_data(income_stmt)
|
239 |
balance_data = self.parse_financial_data(balance_sheet)
|
|
|
|
|
240 |
metrics = self.extract_metrics(income_data, balance_data)
|
241 |
+
prompt = self.generate_prompt(metrics)
|
|
|
|
|
242 |
analysis = self.generate_analysis(prompt)
|
243 |
|
|
|
244 |
results = {
|
245 |
"Financial Analysis": {
|
246 |
"Key Metrics": metrics,
|
|
|
273 |
|
274 |
if __name__ == "__main__":
|
275 |
iface = create_interface()
|
276 |
+
iface.launch()
|