""" Enhanced Balance Sheet Generator Production-ready script for extracting, analyzing, and generating Excel balance sheets from financial JSON data. """ import os import json import re import logging from datetime import datetime from typing import Any, Dict, List, Optional from openpyxl import Workbook from openpyxl.styles import Font, Border, Side, Alignment import requests from dotenv import load_dotenv from pydantic import BaseModel, Field, ValidationError from pydantic_settings import BaseSettings # Import the template handler from balance_sheet_template_handler import BalanceSheetTemplate, STANDARD_NOTES_MAPPING load_dotenv() # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s %(levelname)s %(name)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) logger = logging.getLogger(__name__) class Settings(BaseSettings): """Application settings loaded from environment variables.""" input_file: str = Field(default="data/clean_financial_data_bs.json") output_dir: str = Field(default="data/output") model_config = { "extra": "ignore" } settings = Settings() class BalanceSheetItem(BaseModel): category: str subcategory: Optional[str] = "" name: str note: Optional[str] = "" value_2024: float value_2023: float class BalanceSheetTotals(BaseModel): shareholders_funds_2024: float = 0.0 shareholders_funds_2023: float = 0.0 share_application_money_2024: float = 0.0 share_application_money_2023: float = 0.0 non_current_liabilities_2024: float = 0.0 non_current_liabilities_2023: float = 0.0 current_liabilities_2024: float = 0.0 current_liabilities_2023: float = 0.0 non_current_assets_2024: float = 0.0 non_current_assets_2023: float = 0.0 current_assets_2024: float = 0.0 current_assets_2023: float = 0.0 total_equity_liabilities_2024: float = 0.0 total_equity_liabilities_2023: float = 0.0 total_assets_2024: float = 0.0 total_assets_2023: float = 0.0 balance_difference_2024: float = 0.0 balance_difference_2023: float = 0.0 class EnhancedBalanceSheetGenerator: def __init__(self, api_key: str): self.api_key = api_key self.base_url = "https://openrouter.ai/api/v1/chat/completions" # Initialize template self.template = BalanceSheetTemplate() self.field_mappings = self.template.get_field_mappings() self.formatting_rules = self.template.get_formatting_rules() logger.info(f"Loaded template with {len(self.template.get_template_structure())} items") def safe_float(self, value: Any) -> float: """Convert various value formats to float.""" if not value or str(value).strip() in ['-', '--', 'None', '', 'null']: return 0.0 # Handle strings if isinstance(value, str): # Remove currency symbols and brackets cleaned = re.sub(r'[₹,Rs\.\s\(\)]', '', value) # Handle negative values in brackets if '(' in str(value) and ')' in str(value): cleaned = '-' + cleaned.replace('(', '').replace(')', '') try: return float(cleaned) except Exception: return 0.0 # Handle numeric values try: return float(value) except Exception: return 0.0 def get_value_flexible(self, data: Any, date_key_2024: str = "2024-03-31 00:00:00", date_key_2023: str = "2023-03-31 00:00:00") -> tuple[float, float]: """Flexibly extract values from either list or dictionary format.""" if isinstance(data, dict): # Dictionary format - extract by date keys val_2024 = self.safe_float(data.get(date_key_2024, 0)) val_2023 = self.safe_float(data.get(date_key_2023, 0)) return val_2024, val_2023 elif isinstance(data, list): # List format - assume first element is 2024, second is 2023 val_2024 = self.safe_float(data[0]) if len(data) > 0 else 0.0 val_2023 = self.safe_float(data[1]) if len(data) > 1 else 0.0 return val_2024, val_2023 else: # Single value or other format val = self.safe_float(data) return val, 0.0 # Assume it's 2024 value, 2023 is 0 def call_ai_for_analysis(self, data_summary: str) -> Dict[str, Any]: """Use AI to analyze and extract balance sheet data using the template structure""" template_items = self.template.get_template_structure() prompt = f""" You are a financial analyst. Extract balance sheet data from the following JSON data and create a properly structured balance sheet. CRITICAL REQUIREMENTS: 1. Extract ALL line items with their 2024 and 2023 values 2. Use the EXACT template structure provided below 3. Calculate missing totals where needed 4. Ensure the balance sheet balances (Assets = Equity + Liabilities) 5. Return ONLY valid JSON in the exact format specified below Expected Balance Sheet Structure (use this EXACT structure): {json.dumps(template_items, indent=2)} Data to analyze: {data_summary} Return ONLY this JSON format: {{ "balance_sheet_items": [ {{ "category": "Shareholders' funds", "subcategory": "", "name": "Share capital", "note": "2", "value_2024": 542.52, "value_2023": 542.52 }}, {{ "category": "Shareholders' funds", "subcategory": "", "name": "Reserves and surplus", "note": "3", "value_2024": 3152.39, "value_2023": 2642.87 }} ], "totals": {{ "shareholders_funds_2024": 3694.91, "shareholders_funds_2023": 3185.39, "total_equity_liabilities_2024": 5246.10, "total_equity_liabilities_2023": 4725.23, "total_assets_2024": 5246.10, "total_assets_2023": 4725.23 }} }} """ headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } payload = { "model": "mistralai/mixtral-8x7b-instruct", "messages": [{"role": "user", "content": prompt}], "temperature": 0.1, "max_tokens": 4000 } try: response = requests.post(self.base_url, headers=headers, json=payload, timeout=60) content = response.json()['choices'][0]['message']['content'] # Clean the response content = re.sub(r'```(?:json)?\s*', '', content).strip('`').strip() return json.loads(content) except Exception as e: logger.error(f"AI analysis failed: {e}") return {"balance_sheet_items": [], "totals": {}} def extract_from_json_structure(self, json_data: Dict[str, Any]) -> List[Dict[str, Any]]: """Extract data using template structure with flexible JSON mapping""" items = [] company_data = json_data.get("company_financial_data", {}) # Get template structure to guide extraction template_items = self.template.get_template_structure() # Extract based on template structure for template_item in template_items: item_name = template_item["name"] category = template_item["category"] subcategory = template_item.get("subcategory", "") note = template_item["note"] val_2024 = val_2023 = 0.0 # Map template items to JSON data extraction logic if item_name == "Share capital": share_capital = company_data.get("share_capital", {}) total_share_capital = share_capital.get("Total issued, subscribed and fully paid-up share capital", {}) if total_share_capital: val_2024, val_2023 = self.get_value_flexible(total_share_capital) elif item_name == "Reserves and surplus": reserves = company_data.get("reserves_and_surplus", {}) closing_balance = reserves.get("Balance, at the end of the year", {}) if closing_balance: val_2024, val_2023 = self.get_value_flexible(closing_balance) elif item_name == "Money received against share warrants": # Try to find this in the data - might be in share capital or other sections pass # Will be 0 if not found elif item_name == "Share application money pending allotment": # Try to find this in the data pass # Will be 0 if not found elif item_name == "Long-term borrowings": borrowings = company_data.get("borrowings", {}).get("4. Long-Term Borrowings", {}) for key, value in borrowings.items(): if key != "_metadata" and value is not None: v24, v23 = self.get_value_flexible(value) val_2024 += v24 val_2023 += v23 elif item_name == "Deferred tax liabilities (Net)": deferred_tax = company_data.get("other_data", {}).get("5. Deferred Tax Liability / (Asset)", {}) if deferred_tax: dtl = deferred_tax.get("Deferred tax liability", {}) val_2024, val_2023 = self.get_value_flexible(dtl) elif item_name == "Short-term borrowings": # Look for short term borrowings in current liabilities current_liabilities = company_data.get("current_liabilities", {}) # This might be in various sections, keep as 0 for now pass elif "total outstanding dues of micro enterprises" in item_name: # Extract from trade payables - micro enterprises current_liabilities = company_data.get("current_liabilities", {}) trade_payables = current_liabilities.get("6. Trade Payables", {}) # Look for micro enterprises specific data pass elif "total outstanding dues of creditors other than micro" in item_name: # Extract from trade payables - others current_liabilities = company_data.get("current_liabilities", {}) trade_payables = current_liabilities.get("6. Trade Payables", {}) for key, value in trade_payables.items(): if key not in ["_metadata", "Particulars", "Disputed dues"] and value is not None: v24, v23 = self.get_value_flexible(value) val_2024 += v24 val_2023 += v23 elif item_name == "Other current liabilities": current_liabilities = company_data.get("current_liabilities", {}) other_cl = current_liabilities.get("7. Other Current Liabilities", {}) for key, value in other_cl.items(): if key != "_metadata" and value is not None: v24, v23 = self.get_value_flexible(value) val_2024 += v24 val_2023 += v23 elif item_name == "Short-term provisions": current_liabilities = company_data.get("current_liabilities", {}) provisions = current_liabilities.get("8. Short Term Provisions", {}) for key, value in provisions.items(): if key != "_metadata" and value is not None: v24, v23 = self.get_value_flexible(value) val_2024 += v24 val_2023 += v23 elif item_name == "Tangible assets": fixed_assets = company_data.get("fixed_assets", {}) tangible = fixed_assets.get("tangible_assets", {}).get("", {}) if tangible: net_carrying = tangible.get("net_carrying_value", {}) if net_carrying: if isinstance(net_carrying, dict): val_2024 = self.safe_float(net_carrying.get("closing", 0)) val_2023 = self.safe_float(net_carrying.get("opening", 0)) else: val_2024, val_2023 = self.get_value_flexible(net_carrying) elif item_name == "Intangible assets": fixed_assets = company_data.get("fixed_assets", {}) intangible = fixed_assets.get("intangible_assets", {}).get("", {}) if intangible: net_carrying = intangible.get("net_carrying_value", {}) if net_carrying: if isinstance(net_carrying, dict): val_2024 = self.safe_float(net_carrying.get("closing", 0)) val_2023 = self.safe_float(net_carrying.get("opening", 0)) else: val_2024, val_2023 = self.get_value_flexible(net_carrying) elif item_name == "Long-term loans and advances": lt_loans = company_data.get("loans_and_advances", {}).get("10. Long Term Loans and advances", {}) for key, value in lt_loans.items(): if key != "_metadata" and value is not None: v24, v23 = self.get_value_flexible(value) val_2024 += v24 val_2023 += v23 elif item_name == "Inventories": current_assets = company_data.get("current_assets", {}) inventories = current_assets.get("11. Inventories", {}) for key, value in inventories.items(): if key != "_metadata" and value is not None: v24, v23 = self.get_value_flexible(value) val_2024 += v24 val_2023 += v23 elif item_name == "Trade receivables": current_assets = company_data.get("current_assets", {}) trade_recv = current_assets.get("12. Trade receivables", {}) for key, value in trade_recv.items(): if key not in ["_metadata", "Particulars", "trade_receivables_aging"] and value is not None: v24, v23 = self.get_value_flexible(value) val_2024 += v24 val_2023 += v23 elif item_name == "Cash and cash equivalents": current_assets = company_data.get("current_assets", {}) cash_bank = current_assets.get("13. Cash and bank balances", {}) for key, value in cash_bank.items(): if key != "_metadata" and value is not None: v24, v23 = self.get_value_flexible(value) val_2024 += v24 val_2023 += v23 elif item_name == "Short-term loans and advances": st_loans = company_data.get("loans_and_advances", {}).get("14. Short Term Loans and Advances", {}) for key, value in st_loans.items(): if key != "_metadata" and value is not None: v24, v23 = self.get_value_flexible(value) val_2024 += v24 val_2023 += v23 elif item_name == "Other current assets": other_ca = company_data.get("other_data", {}).get("15. Other Current Assets", {}) for key, value in other_ca.items(): if key != "_metadata" and value is not None: v24, v23 = self.get_value_flexible(value) val_2024 += v24 val_2023 += v23 # Add item if it has any value or is part of template structure if val_2024 != 0 or val_2023 != 0 or True: # Always add template items items.append({ "category": category, "subcategory": subcategory, "name": item_name, "note": note, "value_2024": val_2024, "value_2023": val_2023 }) return items def calculate_totals(self, items: List[Dict[str, Any]]) -> BalanceSheetTotals: """Calculate section totals and verify balance using template categories""" # Group by categories categories = {} for item in items: cat = item["category"] if cat not in categories: categories[cat] = {"2024": 0, "2023": 0} categories[cat]["2024"] += item["value_2024"] categories[cat]["2023"] += item["value_2023"] # Calculate major totals using template categories shareholders_funds_2024 = categories.get("Shareholders' funds", {}).get("2024", 0) shareholders_funds_2023 = categories.get("Shareholders' funds", {}).get("2023", 0) share_app_money_2024 = categories.get("Share application money pending allotment", {}).get("2024", 0) share_app_money_2023 = categories.get("Share application money pending allotment", {}).get("2023", 0) non_current_liab_2024 = categories.get("Non-Current liabilities", {}).get("2024", 0) non_current_liab_2023 = categories.get("Non-Current liabilities", {}).get("2023", 0) current_liab_2024 = categories.get("Current liabilities", {}).get("2024", 0) current_liab_2023 = categories.get("Current liabilities", {}).get("2023", 0) non_current_assets_2024 = categories.get("Non-current assets", {}).get("2024", 0) non_current_assets_2023 = categories.get("Non-current assets", {}).get("2023", 0) current_assets_2024 = categories.get("Current assets", {}).get("2024", 0) current_assets_2023 = categories.get("Current assets", {}).get("2023", 0) total_equity_liab_2024 = shareholders_funds_2024 + share_app_money_2024 + non_current_liab_2024 + current_liab_2024 total_equity_liab_2023 = shareholders_funds_2023 + share_app_money_2023 + non_current_liab_2023 + current_liab_2023 total_assets_2024 = non_current_assets_2024 + current_assets_2024 total_assets_2023 = non_current_assets_2023 + current_assets_2023 return BalanceSheetTotals( shareholders_funds_2024=shareholders_funds_2024, shareholders_funds_2023=shareholders_funds_2023, share_application_money_2024=share_app_money_2024, share_application_money_2023=share_app_money_2023, non_current_liabilities_2024=non_current_liab_2024, non_current_liabilities_2023=non_current_liab_2023, current_liabilities_2024=current_liab_2024, current_liabilities_2023=current_liab_2023, non_current_assets_2024=non_current_assets_2024, non_current_assets_2023=non_current_assets_2023, current_assets_2024=current_assets_2024, current_assets_2023=current_assets_2023, total_equity_liabilities_2024=total_equity_liab_2024, total_equity_liabilities_2023=total_equity_liab_2023, total_assets_2024=total_assets_2024, total_assets_2023=total_assets_2023, balance_difference_2024=abs(total_assets_2024 - total_equity_liab_2024), balance_difference_2023=abs(total_assets_2023 - total_equity_liab_2023) ) def generate_balance_sheet_excel(self, items: List[Dict[str, Any]], totals: BalanceSheetTotals, output_dir: str = "output") -> str: """Generate formatted Excel balance sheet using template formatting""" os.makedirs(output_dir, exist_ok=True) wb = Workbook() ws = wb.active ws.title = "Balance Sheet" # Set column widths ws.column_dimensions["A"].width = 50 ws.column_dimensions["B"].width = 10 ws.column_dimensions["C"].width = 20 ws.column_dimensions["D"].width = 20 # Styles bold_font = Font(bold=True) thin_border = Border( left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin') ) row = 1 def add_row(desc, note, val_2024, val_2023, bold=False, indent=0, border=False): nonlocal row # Description cell_a = ws.cell(row=row, column=1, value=" " * indent + desc) if bold: cell_a.font = bold_font if border: cell_a.border = thin_border # Note cell_b = ws.cell(row=row, column=2, value=note) if bold: cell_b.font = bold_font if border: cell_b.border = thin_border # Values for col, val in [(3, val_2024), (4, val_2023)]: cell = ws.cell(row=row, column=col) if val != 0: cell.value = val cell.number_format = '#,##0.00' if bold: cell.font = bold_font if border: cell.border = thin_border cell.alignment = Alignment(horizontal='right') row += 1 # Header using template formatting header = self.formatting_rules.header add_row(header["title"], "", 0, 0, True) add_row("", "", 0, 0) add_row(header["currency_note"], "", 0, 0) add_row("", "", 0, 0) # Column headers headers = header["column_headers"] add_row(headers[0], headers[1], headers[2], headers[3], True) add_row("", "", 0, 0) # I. EQUITY AND LIABILITIES add_row("I. EQUITY AND LIABILITIES", "", 0, 0, True) add_row("", "", 0, 0) # (1) Shareholders' funds add_row("(1) Shareholders' funds", "", 0, 0, True) shareholders_items = [item for item in items if item["category"] == "Shareholders' funds"] for item in shareholders_items: add_row(f" ({item['note'][0] if item['note'] else ''}) {item['name']}", item["note"], item["value_2024"], item["value_2023"]) add_row("", "", totals.shareholders_funds_2024, totals.shareholders_funds_2023, True) add_row("", "", 0, 0) # (2) Share application money pending allotment share_app_items = [item for item in items if item["category"] == "Share application money pending allotment"] if any(item["value_2024"] != 0 or item["value_2023"] != 0 for item in share_app_items): add_row("(2) Share application money pending allotment", "", 0, 0, True) for item in share_app_items: add_row(f" ({item['note']}) {item['name']}", item["note"], item["value_2024"], item["value_2023"]) add_row("", "", totals.share_application_money_2024, totals.share_application_money_2023, True) add_row("", "", 0, 0) # (3) Non-Current liabilities add_row("(3) Non-Current liabilities", "", 0, 0, True) non_current_liab_items = [item for item in items if item["category"] == "Non-Current liabilities"] for item in non_current_liab_items: add_row(f" ({item['note']}) {item['name']}", item["note"], item["value_2024"], item["value_2023"]) add_row("", "", totals.non_current_liabilities_2024, totals.non_current_liabilities_2023, True) add_row("", "", 0, 0) # (4) Current liabilities add_row("(4) Current liabilities", "", 0, 0, True) current_liab_items = [item for item in items if item["category"] == "Current liabilities"] # Group trade payables trade_payables_items = [item for item in current_liab_items if item["subcategory"] == "Trade payables"] other_current_items = [item for item in current_liab_items if item["subcategory"] != "Trade payables"] # Add other current liability items first for item in other_current_items: add_row(f" ({item['note']}) {item['name']}", item["note"], item["value_2024"], item["value_2023"]) # Add trade payables with subcategory if trade_payables_items: trade_payables_total_2024 = sum(item["value_2024"] for item in trade_payables_items) trade_payables_total_2023 = sum(item["value_2023"] for item in trade_payables_items) add_row(" (11) Trade payables", "11", 0, 0, True, 1) for item in trade_payables_items: add_row(f" (A) {item['name']}", item["note"], item["value_2024"], item["value_2023"]) add_row("", "", trade_payables_total_2024, trade_payables_total_2023, True, 2) add_row("", "", totals.current_liabilities_2024, totals.current_liabilities_2023, True) add_row("", "", 0, 0) # TOTAL EQUITY & LIABILITIES add_row("TOTAL", "", totals.total_equity_liabilities_2024, totals.total_equity_liabilities_2023, True, 0, True) add_row("", "", 0, 0) # II. ASSETS add_row("II. ASSETS", "", 0, 0, True) add_row("", "", 0, 0) # Non-current assets add_row("Non-current assets", "", 0, 0, True) # (1) Property, Plant and Equipment ppe_items = [item for item in items if item.get("subcategory") == "Property, Plant and Equipment"] if ppe_items: add_row("(1) Property, Plant and Equipment", "", 0, 0, True, 1) ppe_total_2024 = ppe_total_2023 = 0 for item in ppe_items: add_row(f" ({item['note']}) {item['name']}", item["note"], item["value_2024"], item["value_2023"], False, 1) ppe_total_2024 += item["value_2024"] ppe_total_2023 += item["value_2023"] add_row("", "", ppe_total_2024, ppe_total_2023, True, 1) add_row("", "", 0, 0) # Other non-current assets other_non_current = [item for item in items if item["category"] == "Non-current assets" and item.get("subcategory") != "Property, Plant and Equipment"] for item in other_non_current: add_row(f"({item['note']}) {item['name']}", item["note"], item["value_2024"], item["value_2023"], False, 1) add_row("", "", totals.non_current_assets_2024, totals.non_current_assets_2023, True) add_row("", "", 0, 0) # (2) Current assets add_row("(2) Current assets", "", 0, 0, True) current_asset_items = [item for item in items if item["category"] == "Current assets"] for item in current_asset_items: add_row(f" ({item['note']}) {item['name']}", item["note"], item["value_2024"], item["value_2023"], False, 1) add_row("", "", totals.current_assets_2024, totals.current_assets_2023, True) add_row("", "", 0, 0) # TOTAL ASSETS add_row("TOTAL", "", totals.total_assets_2024, totals.total_assets_2023, True, 0, True) # Add balance verification add_row("", "", 0, 0) balance_2024 = totals.balance_difference_2024 balance_2023 = totals.balance_difference_2023 if balance_2024 < 0.01 and balance_2023 < 0.01: add_row("✓ Balance Sheet is BALANCED", "", 0, 0, True) else: add_row(f"⚠ Balance Difference: {balance_2024:.2f} | {balance_2023:.2f}", "", 0, 0, True) # Save file timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") output_file = os.path.join(output_dir, f"balance_sheet_{timestamp}.xlsx") wb.save(output_file) logger.info(f"Output file: {output_file}") print(f"Output file: {os.path.abspath(output_file)}") # For API subprocess parsing return output_file def process(self, input_file: str, output_dir: str = "output") -> Optional[str]: """Main processing function""" try: logger.info(f"Processing: {input_file}") # Load JSON data with open(input_file, 'r', encoding='utf-8') as f: json_data = json.load(f) logger.info("Extracting data using template structure...") # Method 1: Direct extraction using template structure items = self.extract_from_json_structure(json_data) logger.info(f"Extracted {len(items)} items using template structure") # Method 2: AI-assisted extraction if needed if len([item for item in items if item["value_2024"] != 0 or item["value_2023"] != 0]) < 5: logger.info("Using AI for additional extraction...") # Create summary for AI summary = json.dumps(json_data, indent=2)[:8000] # Limit size ai_result = self.call_ai_for_analysis(summary) ai_items = ai_result.get("balance_sheet_items", []) logger.info(f"AI extracted {len(ai_items)} additional items") # Merge items (replace template items with AI items if they have values) ai_items_dict = {item["name"]: item for item in ai_items} for i, item in enumerate(items): if item["name"] in ai_items_dict: ai_item = ai_items_dict[item["name"]] if ai_item["value_2024"] != 0 or ai_item["value_2023"] != 0: items[i] = ai_item # Calculate totals totals = self.calculate_totals(items) # Display summary logger.info(f"\n BALANCE SHEET SUMMARY:") logger.info(f"Template Items: {len(self.template.get_template_structure())}") logger.info(f"Items with Values: {len([item for item in items if item['value_2024'] != 0 or item['value_2023'] != 0])}") logger.info(f" EQUITY & LIABILITIES 2024:") logger.info(f" - Shareholders' funds: Rs. {totals.shareholders_funds_2024:,.2f} Lakhs") logger.info(f" - Share application money: Rs. {totals.share_application_money_2024:,.2f} Lakhs") logger.info(f" - Non-current liabilities: Rs. {totals.non_current_liabilities_2024:,.2f} Lakhs") logger.info(f" - Current liabilities: Rs. {totals.current_liabilities_2024:,.2f} Lakhs") logger.info(f" - TOTAL: Rs. {totals.total_equity_liabilities_2024:,.2f} Lakhs") logger.info(f" ASSETS 2024:") logger.info(f" - Non-current assets: Rs. {totals.non_current_assets_2024:,.2f} Lakhs") logger.info(f" - Current assets: Rs. {totals.current_assets_2024:,.2f} Lakhs") logger.info(f" - TOTAL: Rs. {totals.total_assets_2024:,.2f} Lakhs") logger.info(f" Balance Difference 2024: Rs. {totals.balance_difference_2024:,.2f} Lakhs") logger.info(f" Balance Difference 2023: Rs. {totals.balance_difference_2023:,.2f} Lakhs") # Check if balanced is_balanced_2024 = totals.balance_difference_2024 < 0.01 is_balanced_2023 = totals.balance_difference_2023 < 0.01 if is_balanced_2024 and is_balanced_2023: logger.info(" Balance Sheet is PERFECTLY BALANCED!") else: logger.warning(" Balance Sheet has differences - may need adjustment") # Generate Excel using template formatting output_file = self.generate_balance_sheet_excel(items, totals, output_dir) logger.info(f" SUCCESS: Generated {output_file}") return output_file except Exception as e: logger.error(f" Error processing file: {e}", exc_info=True) return None def main() -> None: """ Main function for running the balance sheet generator. Accepts input file, output directory, and API key from command-line arguments. Handles errors gracefully and logs all major events. """ logger.info(" ENHANCED BALANCE SHEET GENERATOR v3.0 (Template-Based) started.") import sys api_key = os.environ.get("OPENROUTER_API_KEY") input_file = settings.input_file output_dir = settings.output_dir if len(sys.argv) > 1: input_file = sys.argv[1] if len(sys.argv) > 2: output_dir = sys.argv[2] if not api_key: logger.error(" Missing OpenRouter API key. Please provide your API key via the 'user_api_key' parameter in your request.") sys.exit(1) if not os.path.exists(input_file): logger.error(f" Input file not found: {input_file}. Please ensure your JSON data file exists.") sys.exit(1) generator = EnhancedBalanceSheetGenerator(api_key) try: result = generator.process(input_file, output_dir) if result: abs_path = os.path.abspath(result) if os.path.exists(abs_path): logger.info(f" COMPLETED SUCCESSFULLY! Output file: {abs_path}") print(f"Output file: {abs_path}") # For API subprocess parsing else: logger.error(f" PROCESSING FAILED. Output file not created: {abs_path}") sys.exit(1) else: logger.error(" PROCESSING FAILED. Please check the error messages above and try again.") sys.exit(1) except Exception as e: logger.error(f" Fatal error: {e}", exc_info=True) sys.exit(1) if __name__ == "__main__": main()