Sahil Garg commited on
Commit
436fd72
·
1 Parent(s): c172f37

pnl and bs changed to schedule 3 format

Browse files
bs/balance_sheet_generator.py CHANGED
@@ -15,6 +15,9 @@ from dotenv import load_dotenv
15
  from pydantic import BaseModel, Field, ValidationError
16
  from pydantic_settings import BaseSettings
17
 
 
 
 
18
  load_dotenv()
19
 
20
  # Configure logging
@@ -44,6 +47,8 @@ class BalanceSheetItem(BaseModel):
44
  class BalanceSheetTotals(BaseModel):
45
  shareholders_funds_2024: float = 0.0
46
  shareholders_funds_2023: float = 0.0
 
 
47
  non_current_liabilities_2024: float = 0.0
48
  non_current_liabilities_2023: float = 0.0
49
  current_liabilities_2024: float = 0.0
@@ -64,79 +69,12 @@ class EnhancedBalanceSheetGenerator:
64
  self.api_key = api_key
65
  self.base_url = "https://openrouter.ai/api/v1/chat/completions"
66
 
67
- # Enhanced mapping with multiple patterns
68
- self.field_mappings = {
69
- # Share Capital patterns
70
- 'share_capital': [
71
- 'share capital', 'equity share', 'paid up', 'issued shares',
72
- 'authorised shares', 'subscribed', 'fully paid'
73
- ],
74
- # Reserves patterns
75
- 'reserves_surplus': [
76
- 'reserves and surplus', 'reserves', 'surplus', 'retained earnings',
77
- 'profit and loss', 'general reserves', 'closing balance'
78
- ],
79
- # Long term borrowings
80
- 'long_term_borrowings': [
81
- 'long term borrowings', 'long-term borrowings', 'borrowings',
82
- 'debt', 'loans', 'financial corporation', 'bank loan'
83
- ],
84
- # Deferred tax
85
- 'deferred_tax': [
86
- 'deferred tax', 'tax liability', 'deferred tax liability'
87
- ],
88
- # Trade payables
89
- 'trade_payables': [
90
- 'trade payables', 'payables', 'creditors', 'sundry creditors',
91
- 'capital expenditure', 'other expenses'
92
- ],
93
- # Other current liabilities
94
- 'other_current_liabilities': [
95
- 'other current liabilities', 'current maturities', 'outstanding liabilities',
96
- 'statutory dues', 'accrued expenses'
97
- ],
98
- # Short term provisions
99
- 'short_term_provisions': [
100
- 'short term provisions', 'provisions', 'provision for taxation',
101
- 'tax provision'
102
- ],
103
- # Fixed assets - Tangible
104
- 'tangible_assets': [
105
- 'tangible assets', 'property plant', 'fixed assets', 'buildings',
106
- 'plant', 'equipment', 'net carrying value'
107
- ],
108
- # Fixed assets - Intangible
109
- 'intangible_assets': [
110
- 'intangible assets', 'software', 'goodwill', 'intangible'
111
- ],
112
- # Long term loans and advances
113
- 'long_term_loans_advances': [
114
- 'long term loans', 'security deposits', 'long term advances'
115
- ],
116
- # Inventories
117
- 'inventories': [
118
- 'inventories', 'stock', 'consumables', 'raw materials'
119
- ],
120
- # Trade receivables
121
- 'trade_receivables': [
122
- 'trade receivables', 'receivables', 'debtors', 'outstanding',
123
- 'other receivables'
124
- ],
125
- # Cash and bank
126
- 'cash_bank': [
127
- 'cash and bank', 'cash', 'bank balances', 'current accounts',
128
- 'cash on hand', 'fixed deposits'
129
- ],
130
- # Short term loans and advances
131
- 'short_term_loans_advances': [
132
- 'short term loans', 'prepaid expenses', 'other advances',
133
- 'advance tax', 'statutory authorities'
134
- ],
135
- # Other current assets
136
- 'other_current_assets': [
137
- 'other current assets', 'accrued income', 'interest accrued'
138
- ]
139
- }
140
 
141
  def safe_float(self, value: Any) -> float:
142
  """Convert various value formats to float."""
@@ -146,7 +84,7 @@ class EnhancedBalanceSheetGenerator:
146
  # Handle strings
147
  if isinstance(value, str):
148
  # Remove currency symbols and brackets
149
- cleaned = re.sub(r'[₹,Rs\.\s\(\)]', '', value)
150
  # Handle negative values in brackets
151
  if '(' in str(value) and ')' in str(value):
152
  cleaned = '-' + cleaned.replace('(', '').replace(')', '')
@@ -181,25 +119,22 @@ class EnhancedBalanceSheetGenerator:
181
  return val, 0.0 # Assume it's 2024 value, 2023 is 0
182
 
183
  def call_ai_for_analysis(self, data_summary: str) -> Dict[str, Any]:
184
- """Use AI to analyze and extract balance sheet data"""
 
 
185
 
186
  prompt = f"""
187
  You are a financial analyst. Extract balance sheet data from the following JSON data and create a properly structured balance sheet.
188
 
189
  CRITICAL REQUIREMENTS:
190
  1. Extract ALL line items with their 2024 and 2023 values
191
- 2. Calculate missing totals where needed
192
- 3. Ensure the balance sheet balances (Assets = Equity + Liabilities)
193
- 4. Return ONLY valid JSON in the exact format specified below
 
194
 
195
- Expected Balance Sheet Structure:
196
- - EQUITY AND LIABILITIES
197
- - Shareholders' funds (Share capital, Reserves and surplus)
198
- - Non-Current liabilities (Long term borrowings, Deferred tax liability)
199
- - Current liabilities (Trade payables, Other current liabilities, Short term provisions)
200
- - ASSETS
201
- - Non-current assets (Fixed assets - Tangible/Intangible, Long term loans and advances)
202
- - Current assets (Inventories, Trade receivables, Cash and bank balances, Short-term loans and advances, Other current assets)
203
 
204
  Data to analyze:
205
  {data_summary}
@@ -222,14 +157,6 @@ Return ONLY this JSON format:
222
  "note": "3",
223
  "value_2024": 3152.39,
224
  "value_2023": 2642.87
225
- }},
226
- {{
227
- "category": "Non-Current liabilities",
228
- "subcategory": "",
229
- "name": "Long term borrowings",
230
- "note": "4",
231
- "value_2024": 914.46,
232
- "value_2023": 321.36
233
  }}
234
  ],
235
  "totals": {{
@@ -268,294 +195,189 @@ Return ONLY this JSON format:
268
  return {"balance_sheet_items": [], "totals": {}}
269
 
270
  def extract_from_json_structure(self, json_data: Dict[str, Any]) -> List[Dict[str, Any]]:
271
- """Direct extraction from the structured JSON data with flexible list/dict support"""
272
  items = []
273
 
274
  company_data = json_data.get("company_financial_data", {})
275
 
276
- # Extract Share Capital
277
- share_capital = company_data.get("share_capital", {})
278
- total_share_capital = share_capital.get("Total issued, subscribed and fully paid-up share capital", {})
279
- if total_share_capital:
280
- val_2024, val_2023 = self.get_value_flexible(total_share_capital)
281
- if val_2024 or val_2023:
282
- items.append({
283
- "category": "Shareholders' funds",
284
- "name": "Share capital",
285
- "note": "2",
286
- "value_2024": val_2024,
287
- "value_2023": val_2023
288
- })
289
-
290
- # Extract Reserves and Surplus
291
- reserves = company_data.get("reserves_and_surplus", {})
292
- closing_balance = reserves.get("Balance, at the end of the year", {})
293
- if closing_balance:
294
- val_2024, val_2023 = self.get_value_flexible(closing_balance)
295
- if val_2024 or val_2023:
296
- items.append({
297
- "category": "Shareholders' funds",
298
- "name": "Reserves and surplus",
299
- "note": "3",
300
- "value_2024": val_2024,
301
- "value_2023": val_2023
302
- })
303
-
304
- # Extract Long-term Borrowings
305
- borrowings = company_data.get("borrowings", {}).get("4. Long-Term Borrowings", {})
306
- total_borrowings_2024 = 0
307
- total_borrowings_2023 = 0
308
-
309
- for key, value in borrowings.items():
310
- if key != "_metadata" and value is not None:
311
- val_2024, val_2023 = self.get_value_flexible(value)
312
- total_borrowings_2024 += val_2024
313
- total_borrowings_2023 += val_2023
314
-
315
- if total_borrowings_2024 or total_borrowings_2023:
316
- items.append({
317
- "category": "Non-Current liabilities",
318
- "name": "Long term borrowings",
319
- "note": "4",
320
- "value_2024": total_borrowings_2024,
321
- "value_2023": total_borrowings_2023
322
- })
323
-
324
- # Extract Deferred Tax
325
- deferred_tax = company_data.get("other_data", {}).get("5. Deferred Tax Liability / (Asset)", {})
326
- if deferred_tax:
327
- dtl = deferred_tax.get("Deferred tax liability", {})
328
- val_2024, val_2023 = self.get_value_flexible(dtl)
329
- if val_2024 or val_2023:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  items.append({
331
- "category": "Non-Current liabilities",
332
- "name": "Deferred Tax Liability (Net)",
333
- "note": "5",
 
334
  "value_2024": val_2024,
335
  "value_2023": val_2023
336
  })
337
-
338
- # Extract Current Liabilities
339
- current_liabilities = company_data.get("current_liabilities", {})
340
-
341
- # Trade Payables
342
- trade_payables = current_liabilities.get("6. Trade Payables", {})
343
- tp_2024 = tp_2023 = 0
344
- for key, value in trade_payables.items():
345
- if key not in ["_metadata", "Particulars", "Disputed dues"] and value is not None:
346
- val_2024, val_2023 = self.get_value_flexible(value)
347
- tp_2024 += val_2024
348
- tp_2023 += val_2023
349
-
350
- if tp_2024 or tp_2023:
351
- items.append({
352
- "category": "Current liabilities",
353
- "name": "Trade payables",
354
- "note": "6",
355
- "value_2024": tp_2024,
356
- "value_2023": tp_2023
357
- })
358
-
359
- # Other Current Liabilities
360
- other_cl = current_liabilities.get("7. Other Current Liabilities", {})
361
- ocl_2024 = ocl_2023 = 0
362
- for key, value in other_cl.items():
363
- if key != "_metadata" and value is not None:
364
- val_2024, val_2023 = self.get_value_flexible(value)
365
- ocl_2024 += val_2024
366
- ocl_2023 += val_2023
367
-
368
- if ocl_2024 or ocl_2023:
369
- items.append({
370
- "category": "Current liabilities",
371
- "name": "Other current liabilities",
372
- "note": "7",
373
- "value_2024": ocl_2024,
374
- "value_2023": ocl_2023
375
- })
376
-
377
- # Short Term Provisions
378
- provisions = current_liabilities.get("8. Short Term Provisions", {})
379
- prov_2024 = prov_2023 = 0
380
- for key, value in provisions.items():
381
- if key != "_metadata" and value is not None:
382
- val_2024, val_2023 = self.get_value_flexible(value)
383
- prov_2024 += val_2024
384
- prov_2023 += val_2023
385
-
386
- if prov_2024 or prov_2023:
387
- items.append({
388
- "category": "Current liabilities",
389
- "name": "Short term provisions",
390
- "note": "8",
391
- "value_2024": prov_2024,
392
- "value_2023": prov_2023
393
- })
394
-
395
- # Extract Fixed Assets
396
- fixed_assets = company_data.get("fixed_assets", {})
397
-
398
- # Tangible Assets
399
- tangible = fixed_assets.get("tangible_assets", {}).get("", {})
400
- if tangible:
401
- net_carrying = tangible.get("net_carrying_value", {})
402
- if net_carrying:
403
- # Handle both dict and list formats for net carrying value
404
- if isinstance(net_carrying, dict):
405
- val_2024 = self.safe_float(net_carrying.get("closing", 0))
406
- val_2023 = self.safe_float(net_carrying.get("opening", 0))
407
- else:
408
- val_2024, val_2023 = self.get_value_flexible(net_carrying)
409
-
410
- if val_2024 or val_2023:
411
- items.append({
412
- "category": "Non-current assets",
413
- "subcategory": "Fixed assets",
414
- "name": "Tangible assets",
415
- "note": "9",
416
- "value_2024": val_2024,
417
- "value_2023": val_2023
418
- })
419
-
420
- # Intangible Assets
421
- intangible = fixed_assets.get("intangible_assets", {}).get("", {})
422
- if intangible:
423
- net_carrying = intangible.get("net_carrying_value", {})
424
- if net_carrying:
425
- # Handle both dict and list formats for net carrying value
426
- if isinstance(net_carrying, dict):
427
- val_2024 = self.safe_float(net_carrying.get("closing", 0))
428
- val_2023 = self.safe_float(net_carrying.get("opening", 0))
429
- else:
430
- val_2024, val_2023 = self.get_value_flexible(net_carrying)
431
-
432
- if val_2024 or val_2023:
433
- items.append({
434
- "category": "Non-current assets",
435
- "subcategory": "Fixed assets",
436
- "name": "Intangible assets",
437
- "note": "9",
438
- "value_2024": val_2024,
439
- "value_2023": val_2023
440
- })
441
-
442
- # Long Term Loans and Advances
443
- lt_loans = company_data.get("loans_and_advances", {}).get("10. Long Term Loans and advances", {})
444
- lt_2024 = lt_2023 = 0
445
- for key, value in lt_loans.items():
446
- if key != "_metadata" and value is not None:
447
- val_2024, val_2023 = self.get_value_flexible(value)
448
- lt_2024 += val_2024
449
- lt_2023 += val_2023
450
-
451
- if lt_2024 or lt_2023:
452
- items.append({
453
- "category": "Non-current assets",
454
- "name": "Long Term Loans and Advances",
455
- "note": "10",
456
- "value_2024": lt_2024,
457
- "value_2023": lt_2023
458
- })
459
-
460
- # Extract Current Assets
461
- current_assets = company_data.get("current_assets", {})
462
-
463
- # Inventories
464
- inventories = current_assets.get("11. Inventories", {})
465
- inv_2024 = inv_2023 = 0
466
- for key, value in inventories.items():
467
- if key != "_metadata" and value is not None:
468
- val_2024, val_2023 = self.get_value_flexible(value)
469
- inv_2024 += val_2024
470
- inv_2023 += val_2023
471
-
472
- if inv_2024 or inv_2023:
473
- items.append({
474
- "category": "Current assets",
475
- "name": "Inventories",
476
- "note": "11",
477
- "value_2024": inv_2024,
478
- "value_2023": inv_2023
479
- })
480
-
481
- # Trade Receivables
482
- trade_recv = current_assets.get("12. Trade receivables", {})
483
- tr_2024 = tr_2023 = 0
484
- for key, value in trade_recv.items():
485
- if key not in ["_metadata", "Particulars", "trade_receivables_aging"] and value is not None:
486
- val_2024, val_2023 = self.get_value_flexible(value)
487
- tr_2024 += val_2024
488
- tr_2023 += val_2023
489
-
490
- if tr_2024 or tr_2023:
491
- items.append({
492
- "category": "Current assets",
493
- "name": "Trade receivables",
494
- "note": "12",
495
- "value_2024": tr_2024,
496
- "value_2023": tr_2023
497
- })
498
-
499
- # Cash and Bank Balances
500
- cash_bank = current_assets.get("13. Cash and bank balances", {})
501
- cb_2024 = cb_2023 = 0
502
- for key, value in cash_bank.items():
503
- if key != "_metadata" and value is not None:
504
- val_2024, val_2023 = self.get_value_flexible(value)
505
- cb_2024 += val_2024
506
- cb_2023 += val_2023
507
-
508
- if cb_2024 or cb_2023:
509
- items.append({
510
- "category": "Current assets",
511
- "name": "Cash and bank balances",
512
- "note": "13",
513
- "value_2024": cb_2024,
514
- "value_2023": cb_2023
515
- })
516
-
517
- # Short-term Loans and Advances
518
- st_loans = company_data.get("loans_and_advances", {}).get("14. Short Term Loans and Advances", {})
519
- st_2024 = st_2023 = 0
520
- for key, value in st_loans.items():
521
- if key != "_metadata" and value is not None:
522
- val_2024, val_2023 = self.get_value_flexible(value)
523
- st_2024 += val_2024
524
- st_2023 += val_2023
525
-
526
- if st_2024 or st_2023:
527
- items.append({
528
- "category": "Current assets",
529
- "name": "Short-term loans and advances",
530
- "note": "14",
531
- "value_2024": st_2024,
532
- "value_2023": st_2023
533
- })
534
-
535
- # Other Current Assets
536
- other_ca = company_data.get("other_data", {}).get("15. Other Current Assets", {})
537
- oca_2024 = oca_2023 = 0
538
- for key, value in other_ca.items():
539
- if key != "_metadata" and value is not None:
540
- val_2024, val_2023 = self.get_value_flexible(value)
541
- oca_2024 += val_2024
542
- oca_2023 += val_2023
543
-
544
- if oca_2024 or oca_2023:
545
- items.append({
546
- "category": "Current assets",
547
- "name": "Other current assets",
548
- "note": "15",
549
- "value_2024": oca_2024,
550
- "value_2023": oca_2023
551
- })
552
-
553
  return items
554
 
555
  def calculate_totals(self, items: List[Dict[str, Any]]) -> BalanceSheetTotals:
556
- """Calculate section totals and verify balance"""
557
- totals = {}
558
-
559
  # Group by categories
560
  categories = {}
561
  for item in items:
@@ -565,10 +387,13 @@ Return ONLY this JSON format:
565
  categories[cat]["2024"] += item["value_2024"]
566
  categories[cat]["2023"] += item["value_2023"]
567
 
568
- # Calculate major totals
569
  shareholders_funds_2024 = categories.get("Shareholders' funds", {}).get("2024", 0)
570
  shareholders_funds_2023 = categories.get("Shareholders' funds", {}).get("2023", 0)
571
 
 
 
 
572
  non_current_liab_2024 = categories.get("Non-Current liabilities", {}).get("2024", 0)
573
  non_current_liab_2023 = categories.get("Non-Current liabilities", {}).get("2023", 0)
574
 
@@ -581,8 +406,8 @@ Return ONLY this JSON format:
581
  current_assets_2024 = categories.get("Current assets", {}).get("2024", 0)
582
  current_assets_2023 = categories.get("Current assets", {}).get("2023", 0)
583
 
584
- total_equity_liab_2024 = shareholders_funds_2024 + non_current_liab_2024 + current_liab_2024
585
- total_equity_liab_2023 = shareholders_funds_2023 + non_current_liab_2023 + current_liab_2023
586
 
587
  total_assets_2024 = non_current_assets_2024 + current_assets_2024
588
  total_assets_2023 = non_current_assets_2023 + current_assets_2023
@@ -590,6 +415,8 @@ Return ONLY this JSON format:
590
  return BalanceSheetTotals(
591
  shareholders_funds_2024=shareholders_funds_2024,
592
  shareholders_funds_2023=shareholders_funds_2023,
 
 
593
  non_current_liabilities_2024=non_current_liab_2024,
594
  non_current_liabilities_2023=non_current_liab_2023,
595
  current_liabilities_2024=current_liab_2024,
@@ -607,7 +434,7 @@ Return ONLY this JSON format:
607
  )
608
 
609
  def generate_balance_sheet_excel(self, items: List[Dict[str, Any]], totals: BalanceSheetTotals, output_dir: str = "output") -> str:
610
- """Generate formatted Excel balance sheet"""
611
  os.makedirs(output_dir, exist_ok=True)
612
 
613
  wb = Workbook()
@@ -615,10 +442,10 @@ Return ONLY this JSON format:
615
  ws.title = "Balance Sheet"
616
 
617
  # Set column widths
618
- ws.column_dimensions["A"].width = 40
619
- ws.column_dimensions["B"].width = 8
620
- ws.column_dimensions["C"].width = 15
621
- ws.column_dimensions["D"].width = 15
622
 
623
  # Styles
624
  bold_font = Font(bold=True)
@@ -660,39 +487,68 @@ Return ONLY this JSON format:
660
 
661
  row += 1
662
 
663
- # Header
664
- add_row("Balance Sheet as at March 31, 2024", "", 0, 0, True)
 
665
  add_row("", "", 0, 0)
666
- add_row("(In Lakhs)", "", 0, 0)
667
- add_row("", "Notes", "March 31, 2024", "March 31, 2023", True)
668
  add_row("", "", 0, 0)
669
 
670
- # EQUITY AND LIABILITIES
671
- add_row("EQUITY AND LIABILITIES", "", 0, 0, True)
 
 
 
 
 
 
672
 
673
- # Shareholders' funds
674
- add_row("Shareholders' funds", "", 0, 0, True)
675
  shareholders_items = [item for item in items if item["category"] == "Shareholders' funds"]
676
  for item in shareholders_items:
677
- add_row(item["name"], item["note"], item["value_2024"], item["value_2023"])
678
-
679
  add_row("", "", totals.shareholders_funds_2024, totals.shareholders_funds_2023, True)
680
  add_row("", "", 0, 0)
681
 
682
- # Non-Current liabilities
683
- add_row("Non-Current liabilities", "", 0, 0, True)
 
 
 
 
 
 
 
 
 
684
  non_current_liab_items = [item for item in items if item["category"] == "Non-Current liabilities"]
685
  for item in non_current_liab_items:
686
- add_row(item["name"], item["note"], item["value_2024"], item["value_2023"])
687
-
688
  add_row("", "", totals.non_current_liabilities_2024, totals.non_current_liabilities_2023, True)
689
  add_row("", "", 0, 0)
690
 
691
- # Current liabilities
692
- add_row("Current liabilities", "", 0, 0, True)
693
  current_liab_items = [item for item in items if item["category"] == "Current liabilities"]
694
- for item in current_liab_items:
695
- add_row(item["name"], item["note"], item["value_2024"], item["value_2023"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
696
 
697
  add_row("", "", totals.current_liabilities_2024, totals.current_liabilities_2023, True)
698
  add_row("", "", 0, 0)
@@ -701,36 +557,38 @@ Return ONLY this JSON format:
701
  add_row("TOTAL", "", totals.total_equity_liabilities_2024, totals.total_equity_liabilities_2023, True, 0, True)
702
  add_row("", "", 0, 0)
703
 
704
- # ASSETS
705
- add_row("ASSETS", "", 0, 0, True)
 
706
 
707
  # Non-current assets
708
  add_row("Non-current assets", "", 0, 0, True)
709
 
710
- # Fixed assets
711
- fixed_asset_items = [item for item in items if item.get("subcategory") == "Fixed assets"]
712
- if fixed_asset_items:
713
- add_row("Fixed assets", "", 0, 0, True, 1)
714
- fixed_total_2024 = fixed_total_2023 = 0
715
- for item in fixed_asset_items:
716
- add_row(item["name"], item["note"], item["value_2024"], item["value_2023"], False, 2)
717
- fixed_total_2024 += item["value_2024"]
718
- fixed_total_2023 += item["value_2023"]
719
- add_row("", "", fixed_total_2024, fixed_total_2023, True, 2)
 
720
 
721
  # Other non-current assets
722
- other_non_current = [item for item in items if item["category"] == "Non-current assets" and item.get("subcategory") != "Fixed assets"]
723
  for item in other_non_current:
724
- add_row(item["name"], item["note"], item["value_2024"], item["value_2023"], False, 1)
725
 
726
  add_row("", "", totals.non_current_assets_2024, totals.non_current_assets_2023, True)
727
  add_row("", "", 0, 0)
728
 
729
- # Current assets
730
- add_row("Current assets", "", 0, 0, True)
731
  current_asset_items = [item for item in items if item["category"] == "Current assets"]
732
  for item in current_asset_items:
733
- add_row(item["name"], item["note"], item["value_2024"], item["value_2023"], False, 1)
734
 
735
  add_row("", "", totals.current_assets_2024, totals.current_assets_2023, True)
736
  add_row("", "", 0, 0)
@@ -744,9 +602,9 @@ Return ONLY this JSON format:
744
  balance_2023 = totals.balance_difference_2023
745
 
746
  if balance_2024 < 0.01 and balance_2023 < 0.01:
747
- add_row(" Balance Sheet is BALANCED", "", 0, 0, True)
748
  else:
749
- add_row(f" Balance Difference: {balance_2024:.2f} | {balance_2023:.2f}", "", 0, 0, True)
750
 
751
  # Save file
752
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
@@ -765,14 +623,14 @@ Return ONLY this JSON format:
765
  with open(input_file, 'r', encoding='utf-8') as f:
766
  json_data = json.load(f)
767
 
768
- logger.info("Extracting data from JSON structure...")
769
 
770
- # Method 1: Direct extraction from structured JSON
771
  items = self.extract_from_json_structure(json_data)
772
- logger.info(f"Extracted {len(items)} items from JSON structure")
773
 
774
  # Method 2: AI-assisted extraction if needed
775
- if len(items) < 10: # If we don't have enough items
776
  logger.info("Using AI for additional extraction...")
777
 
778
  # Create summary for AI
@@ -782,46 +640,52 @@ Return ONLY this JSON format:
782
  ai_items = ai_result.get("balance_sheet_items", [])
783
  logger.info(f"AI extracted {len(ai_items)} additional items")
784
 
785
- # Merge items (avoid duplicates)
786
- existing_names = {item["name"].lower() for item in items}
787
- for ai_item in ai_items:
788
- if ai_item["name"].lower() not in existing_names:
789
- items.append(ai_item)
790
-
791
- if not items:
792
- logger.error("No balance sheet items extracted")
793
- return None
794
 
795
  # Calculate totals
796
  totals = self.calculate_totals(items)
797
 
798
  # Display summary
799
  logger.info(f"\n BALANCE SHEET SUMMARY:")
800
- logger.info(f"Total Items Extracted: {len(items)}")
801
- logger.info(f"Assets 2024: Rs. {totals.total_assets_2024:,.2f} Lakhs")
802
- logger.info(f"Equity & Liabilities 2024: Rs. {totals.total_equity_liabilities_2024:,.2f} Lakhs")
803
- logger.info(f"Balance Difference 2024: Rs. {totals.balance_difference_2024:,.2f} Lakhs")
804
- logger.info(f"Assets 2023: Rs. {totals.total_assets_2023:,.2f} Lakhs")
805
- logger.info(f"Equity & Liabilities 2023: Rs. {totals.total_equity_liabilities_2023:,.2f} Lakhs")
806
- logger.info(f"Balance Difference 2023: Rs. {totals.balance_difference_2023:,.2f} Lakhs")
 
 
 
 
 
 
 
807
 
808
  # Check if balanced
809
  is_balanced_2024 = totals.balance_difference_2024 < 0.01
810
  is_balanced_2023 = totals.balance_difference_2023 < 0.01
811
 
812
  if is_balanced_2024 and is_balanced_2023:
813
- logger.info("Balance Sheet is PERFECTLY BALANCED!")
814
  else:
815
- logger.warning("Balance Sheet has differences - may need adjustment")
816
 
817
- # Generate Excel
818
  output_file = self.generate_balance_sheet_excel(items, totals, output_dir)
819
 
820
- logger.info(f"SUCCESS: Generated {output_file}")
821
  return output_file
822
 
823
  except Exception as e:
824
- logger.error(f"Error processing file: {e}", exc_info=True)
825
  return None
826
 
827
  def main() -> None:
@@ -830,7 +694,7 @@ def main() -> None:
830
  Accepts input file and output directory from command-line arguments or environment variables.
831
  Handles errors gracefully and logs all major events.
832
  """
833
- logger.info("ENHANCED BALANCE SHEET GENERATOR v2.0 started.")
834
  import sys
835
  api_key = settings.api_key
836
  input_file = settings.input_file
@@ -840,25 +704,26 @@ def main() -> None:
840
  if len(sys.argv) > 2:
841
  output_dir = sys.argv[2]
842
  if not api_key:
843
- logger.error("Missing OPENROUTER_API_KEY environment variable. Please set your OpenRouter API key in the .env file.")
844
  return
845
  if not os.path.exists(input_file):
846
- logger.error(f"Input file not found: {input_file}. Please ensure your JSON data file exists.")
847
  return
 
848
  generator = EnhancedBalanceSheetGenerator(api_key)
849
  try:
850
  result = generator.process(input_file, output_dir)
851
  if result:
852
  abs_path = os.path.abspath(result)
853
  if os.path.exists(abs_path):
854
- logger.info(f"COMPLETED SUCCESSFULLY! Output file: {abs_path}")
855
  print(f"Output file: {abs_path}") # For API subprocess parsing
856
  else:
857
- logger.error(f"PROCESSING FAILED. Output file not created: {abs_path}")
858
  else:
859
- logger.error("PROCESSING FAILED. Please check the error messages above and try again.")
860
  except Exception as e:
861
- logger.error(f"Fatal error: {e}", exc_info=True)
862
 
863
  if __name__ == "__main__":
864
  main()
 
15
  from pydantic import BaseModel, Field, ValidationError
16
  from pydantic_settings import BaseSettings
17
 
18
+ # Import the template handler
19
+ from balance_sheet_template_handler import BalanceSheetTemplate, STANDARD_NOTES_MAPPING
20
+
21
  load_dotenv()
22
 
23
  # Configure logging
 
47
  class BalanceSheetTotals(BaseModel):
48
  shareholders_funds_2024: float = 0.0
49
  shareholders_funds_2023: float = 0.0
50
+ share_application_money_2024: float = 0.0
51
+ share_application_money_2023: float = 0.0
52
  non_current_liabilities_2024: float = 0.0
53
  non_current_liabilities_2023: float = 0.0
54
  current_liabilities_2024: float = 0.0
 
69
  self.api_key = api_key
70
  self.base_url = "https://openrouter.ai/api/v1/chat/completions"
71
 
72
+ # Initialize template
73
+ self.template = BalanceSheetTemplate()
74
+ self.field_mappings = self.template.get_field_mappings()
75
+ self.formatting_rules = self.template.get_formatting_rules()
76
+
77
+ logger.info(f"Loaded template with {len(self.template.get_template_structure())} items")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  def safe_float(self, value: Any) -> float:
80
  """Convert various value formats to float."""
 
84
  # Handle strings
85
  if isinstance(value, str):
86
  # Remove currency symbols and brackets
87
+ cleaned = re.sub(r'[₹,Rs\.\s\(\)]', '', value)
88
  # Handle negative values in brackets
89
  if '(' in str(value) and ')' in str(value):
90
  cleaned = '-' + cleaned.replace('(', '').replace(')', '')
 
119
  return val, 0.0 # Assume it's 2024 value, 2023 is 0
120
 
121
  def call_ai_for_analysis(self, data_summary: str) -> Dict[str, Any]:
122
+ """Use AI to analyze and extract balance sheet data using the template structure"""
123
+
124
+ template_items = self.template.get_template_structure()
125
 
126
  prompt = f"""
127
  You are a financial analyst. Extract balance sheet data from the following JSON data and create a properly structured balance sheet.
128
 
129
  CRITICAL REQUIREMENTS:
130
  1. Extract ALL line items with their 2024 and 2023 values
131
+ 2. Use the EXACT template structure provided below
132
+ 3. Calculate missing totals where needed
133
+ 4. Ensure the balance sheet balances (Assets = Equity + Liabilities)
134
+ 5. Return ONLY valid JSON in the exact format specified below
135
 
136
+ Expected Balance Sheet Structure (use this EXACT structure):
137
+ {json.dumps(template_items, indent=2)}
 
 
 
 
 
 
138
 
139
  Data to analyze:
140
  {data_summary}
 
157
  "note": "3",
158
  "value_2024": 3152.39,
159
  "value_2023": 2642.87
 
 
 
 
 
 
 
 
160
  }}
161
  ],
162
  "totals": {{
 
195
  return {"balance_sheet_items": [], "totals": {}}
196
 
197
  def extract_from_json_structure(self, json_data: Dict[str, Any]) -> List[Dict[str, Any]]:
198
+ """Extract data using template structure with flexible JSON mapping"""
199
  items = []
200
 
201
  company_data = json_data.get("company_financial_data", {})
202
 
203
+ # Get template structure to guide extraction
204
+ template_items = self.template.get_template_structure()
205
+
206
+ # Extract based on template structure
207
+ for template_item in template_items:
208
+ item_name = template_item["name"]
209
+ category = template_item["category"]
210
+ subcategory = template_item.get("subcategory", "")
211
+ note = template_item["note"]
212
+
213
+ val_2024 = val_2023 = 0.0
214
+
215
+ # Map template items to JSON data extraction logic
216
+ if item_name == "Share capital":
217
+ share_capital = company_data.get("share_capital", {})
218
+ total_share_capital = share_capital.get("Total issued, subscribed and fully paid-up share capital", {})
219
+ if total_share_capital:
220
+ val_2024, val_2023 = self.get_value_flexible(total_share_capital)
221
+
222
+ elif item_name == "Reserves and surplus":
223
+ reserves = company_data.get("reserves_and_surplus", {})
224
+ closing_balance = reserves.get("Balance, at the end of the year", {})
225
+ if closing_balance:
226
+ val_2024, val_2023 = self.get_value_flexible(closing_balance)
227
+
228
+ elif item_name == "Money received against share warrants":
229
+ # Try to find this in the data - might be in share capital or other sections
230
+ pass # Will be 0 if not found
231
+
232
+ elif item_name == "Share application money pending allotment":
233
+ # Try to find this in the data
234
+ pass # Will be 0 if not found
235
+
236
+ elif item_name == "Long-term borrowings":
237
+ borrowings = company_data.get("borrowings", {}).get("4. Long-Term Borrowings", {})
238
+ for key, value in borrowings.items():
239
+ if key != "_metadata" and value is not None:
240
+ v24, v23 = self.get_value_flexible(value)
241
+ val_2024 += v24
242
+ val_2023 += v23
243
+
244
+ elif item_name == "Deferred tax liabilities (Net)":
245
+ deferred_tax = company_data.get("other_data", {}).get("5. Deferred Tax Liability / (Asset)", {})
246
+ if deferred_tax:
247
+ dtl = deferred_tax.get("Deferred tax liability", {})
248
+ val_2024, val_2023 = self.get_value_flexible(dtl)
249
+
250
+ elif item_name == "Short-term borrowings":
251
+ # Look for short term borrowings in current liabilities
252
+ current_liabilities = company_data.get("current_liabilities", {})
253
+ # This might be in various sections, keep as 0 for now
254
+ pass
255
+
256
+ elif "total outstanding dues of micro enterprises" in item_name:
257
+ # Extract from trade payables - micro enterprises
258
+ current_liabilities = company_data.get("current_liabilities", {})
259
+ trade_payables = current_liabilities.get("6. Trade Payables", {})
260
+ # Look for micro enterprises specific data
261
+ pass
262
+
263
+ elif "total outstanding dues of creditors other than micro" in item_name:
264
+ # Extract from trade payables - others
265
+ current_liabilities = company_data.get("current_liabilities", {})
266
+ trade_payables = current_liabilities.get("6. Trade Payables", {})
267
+ for key, value in trade_payables.items():
268
+ if key not in ["_metadata", "Particulars", "Disputed dues"] and value is not None:
269
+ v24, v23 = self.get_value_flexible(value)
270
+ val_2024 += v24
271
+ val_2023 += v23
272
+
273
+ elif item_name == "Other current liabilities":
274
+ current_liabilities = company_data.get("current_liabilities", {})
275
+ other_cl = current_liabilities.get("7. Other Current Liabilities", {})
276
+ for key, value in other_cl.items():
277
+ if key != "_metadata" and value is not None:
278
+ v24, v23 = self.get_value_flexible(value)
279
+ val_2024 += v24
280
+ val_2023 += v23
281
+
282
+ elif item_name == "Short-term provisions":
283
+ current_liabilities = company_data.get("current_liabilities", {})
284
+ provisions = current_liabilities.get("8. Short Term Provisions", {})
285
+ for key, value in provisions.items():
286
+ if key != "_metadata" and value is not None:
287
+ v24, v23 = self.get_value_flexible(value)
288
+ val_2024 += v24
289
+ val_2023 += v23
290
+
291
+ elif item_name == "Tangible assets":
292
+ fixed_assets = company_data.get("fixed_assets", {})
293
+ tangible = fixed_assets.get("tangible_assets", {}).get("", {})
294
+ if tangible:
295
+ net_carrying = tangible.get("net_carrying_value", {})
296
+ if net_carrying:
297
+ if isinstance(net_carrying, dict):
298
+ val_2024 = self.safe_float(net_carrying.get("closing", 0))
299
+ val_2023 = self.safe_float(net_carrying.get("opening", 0))
300
+ else:
301
+ val_2024, val_2023 = self.get_value_flexible(net_carrying)
302
+
303
+ elif item_name == "Intangible assets":
304
+ fixed_assets = company_data.get("fixed_assets", {})
305
+ intangible = fixed_assets.get("intangible_assets", {}).get("", {})
306
+ if intangible:
307
+ net_carrying = intangible.get("net_carrying_value", {})
308
+ if net_carrying:
309
+ if isinstance(net_carrying, dict):
310
+ val_2024 = self.safe_float(net_carrying.get("closing", 0))
311
+ val_2023 = self.safe_float(net_carrying.get("opening", 0))
312
+ else:
313
+ val_2024, val_2023 = self.get_value_flexible(net_carrying)
314
+
315
+ elif item_name == "Long-term loans and advances":
316
+ lt_loans = company_data.get("loans_and_advances", {}).get("10. Long Term Loans and advances", {})
317
+ for key, value in lt_loans.items():
318
+ if key != "_metadata" and value is not None:
319
+ v24, v23 = self.get_value_flexible(value)
320
+ val_2024 += v24
321
+ val_2023 += v23
322
+
323
+ elif item_name == "Inventories":
324
+ current_assets = company_data.get("current_assets", {})
325
+ inventories = current_assets.get("11. Inventories", {})
326
+ for key, value in inventories.items():
327
+ if key != "_metadata" and value is not None:
328
+ v24, v23 = self.get_value_flexible(value)
329
+ val_2024 += v24
330
+ val_2023 += v23
331
+
332
+ elif item_name == "Trade receivables":
333
+ current_assets = company_data.get("current_assets", {})
334
+ trade_recv = current_assets.get("12. Trade receivables", {})
335
+ for key, value in trade_recv.items():
336
+ if key not in ["_metadata", "Particulars", "trade_receivables_aging"] and value is not None:
337
+ v24, v23 = self.get_value_flexible(value)
338
+ val_2024 += v24
339
+ val_2023 += v23
340
+
341
+ elif item_name == "Cash and cash equivalents":
342
+ current_assets = company_data.get("current_assets", {})
343
+ cash_bank = current_assets.get("13. Cash and bank balances", {})
344
+ for key, value in cash_bank.items():
345
+ if key != "_metadata" and value is not None:
346
+ v24, v23 = self.get_value_flexible(value)
347
+ val_2024 += v24
348
+ val_2023 += v23
349
+
350
+ elif item_name == "Short-term loans and advances":
351
+ st_loans = company_data.get("loans_and_advances", {}).get("14. Short Term Loans and Advances", {})
352
+ for key, value in st_loans.items():
353
+ if key != "_metadata" and value is not None:
354
+ v24, v23 = self.get_value_flexible(value)
355
+ val_2024 += v24
356
+ val_2023 += v23
357
+
358
+ elif item_name == "Other current assets":
359
+ other_ca = company_data.get("other_data", {}).get("15. Other Current Assets", {})
360
+ for key, value in other_ca.items():
361
+ if key != "_metadata" and value is not None:
362
+ v24, v23 = self.get_value_flexible(value)
363
+ val_2024 += v24
364
+ val_2023 += v23
365
+
366
+ # Add item if it has any value or is part of template structure
367
+ if val_2024 != 0 or val_2023 != 0 or True: # Always add template items
368
  items.append({
369
+ "category": category,
370
+ "subcategory": subcategory,
371
+ "name": item_name,
372
+ "note": note,
373
  "value_2024": val_2024,
374
  "value_2023": val_2023
375
  })
376
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
  return items
378
 
379
  def calculate_totals(self, items: List[Dict[str, Any]]) -> BalanceSheetTotals:
380
+ """Calculate section totals and verify balance using template categories"""
 
 
381
  # Group by categories
382
  categories = {}
383
  for item in items:
 
387
  categories[cat]["2024"] += item["value_2024"]
388
  categories[cat]["2023"] += item["value_2023"]
389
 
390
+ # Calculate major totals using template categories
391
  shareholders_funds_2024 = categories.get("Shareholders' funds", {}).get("2024", 0)
392
  shareholders_funds_2023 = categories.get("Shareholders' funds", {}).get("2023", 0)
393
 
394
+ share_app_money_2024 = categories.get("Share application money pending allotment", {}).get("2024", 0)
395
+ share_app_money_2023 = categories.get("Share application money pending allotment", {}).get("2023", 0)
396
+
397
  non_current_liab_2024 = categories.get("Non-Current liabilities", {}).get("2024", 0)
398
  non_current_liab_2023 = categories.get("Non-Current liabilities", {}).get("2023", 0)
399
 
 
406
  current_assets_2024 = categories.get("Current assets", {}).get("2024", 0)
407
  current_assets_2023 = categories.get("Current assets", {}).get("2023", 0)
408
 
409
+ total_equity_liab_2024 = shareholders_funds_2024 + share_app_money_2024 + non_current_liab_2024 + current_liab_2024
410
+ total_equity_liab_2023 = shareholders_funds_2023 + share_app_money_2023 + non_current_liab_2023 + current_liab_2023
411
 
412
  total_assets_2024 = non_current_assets_2024 + current_assets_2024
413
  total_assets_2023 = non_current_assets_2023 + current_assets_2023
 
415
  return BalanceSheetTotals(
416
  shareholders_funds_2024=shareholders_funds_2024,
417
  shareholders_funds_2023=shareholders_funds_2023,
418
+ share_application_money_2024=share_app_money_2024,
419
+ share_application_money_2023=share_app_money_2023,
420
  non_current_liabilities_2024=non_current_liab_2024,
421
  non_current_liabilities_2023=non_current_liab_2023,
422
  current_liabilities_2024=current_liab_2024,
 
434
  )
435
 
436
  def generate_balance_sheet_excel(self, items: List[Dict[str, Any]], totals: BalanceSheetTotals, output_dir: str = "output") -> str:
437
+ """Generate formatted Excel balance sheet using template formatting"""
438
  os.makedirs(output_dir, exist_ok=True)
439
 
440
  wb = Workbook()
 
442
  ws.title = "Balance Sheet"
443
 
444
  # Set column widths
445
+ ws.column_dimensions["A"].width = 50
446
+ ws.column_dimensions["B"].width = 10
447
+ ws.column_dimensions["C"].width = 20
448
+ ws.column_dimensions["D"].width = 20
449
 
450
  # Styles
451
  bold_font = Font(bold=True)
 
487
 
488
  row += 1
489
 
490
+ # Header using template formatting
491
+ header = self.formatting_rules.header
492
+ add_row(header["title"], "", 0, 0, True)
493
  add_row("", "", 0, 0)
494
+ add_row(header["currency_note"], "", 0, 0)
 
495
  add_row("", "", 0, 0)
496
 
497
+ # Column headers
498
+ headers = header["column_headers"]
499
+ add_row(headers[0], headers[1], headers[2], headers[3], True)
500
+ add_row("", "", 0, 0)
501
+
502
+ # I. EQUITY AND LIABILITIES
503
+ add_row("I. EQUITY AND LIABILITIES", "", 0, 0, True)
504
+ add_row("", "", 0, 0)
505
 
506
+ # (1) Shareholders' funds
507
+ add_row("(1) Shareholders' funds", "", 0, 0, True)
508
  shareholders_items = [item for item in items if item["category"] == "Shareholders' funds"]
509
  for item in shareholders_items:
510
+ add_row(f" ({item['note'][0] if item['note'] else ''}) {item['name']}", item["note"], item["value_2024"], item["value_2023"])
 
511
  add_row("", "", totals.shareholders_funds_2024, totals.shareholders_funds_2023, True)
512
  add_row("", "", 0, 0)
513
 
514
+ # (2) Share application money pending allotment
515
+ share_app_items = [item for item in items if item["category"] == "Share application money pending allotment"]
516
+ if any(item["value_2024"] != 0 or item["value_2023"] != 0 for item in share_app_items):
517
+ add_row("(2) Share application money pending allotment", "", 0, 0, True)
518
+ for item in share_app_items:
519
+ add_row(f" ({item['note']}) {item['name']}", item["note"], item["value_2024"], item["value_2023"])
520
+ add_row("", "", totals.share_application_money_2024, totals.share_application_money_2023, True)
521
+ add_row("", "", 0, 0)
522
+
523
+ # (3) Non-Current liabilities
524
+ add_row("(3) Non-Current liabilities", "", 0, 0, True)
525
  non_current_liab_items = [item for item in items if item["category"] == "Non-Current liabilities"]
526
  for item in non_current_liab_items:
527
+ add_row(f" ({item['note']}) {item['name']}", item["note"], item["value_2024"], item["value_2023"])
 
528
  add_row("", "", totals.non_current_liabilities_2024, totals.non_current_liabilities_2023, True)
529
  add_row("", "", 0, 0)
530
 
531
+ # (4) Current liabilities
532
+ add_row("(4) Current liabilities", "", 0, 0, True)
533
  current_liab_items = [item for item in items if item["category"] == "Current liabilities"]
534
+
535
+ # Group trade payables
536
+ trade_payables_items = [item for item in current_liab_items if item["subcategory"] == "Trade payables"]
537
+ other_current_items = [item for item in current_liab_items if item["subcategory"] != "Trade payables"]
538
+
539
+ # Add other current liability items first
540
+ for item in other_current_items:
541
+ add_row(f" ({item['note']}) {item['name']}", item["note"], item["value_2024"], item["value_2023"])
542
+
543
+ # Add trade payables with subcategory
544
+ if trade_payables_items:
545
+ trade_payables_total_2024 = sum(item["value_2024"] for item in trade_payables_items)
546
+ trade_payables_total_2023 = sum(item["value_2023"] for item in trade_payables_items)
547
+
548
+ add_row(" (11) Trade payables", "11", 0, 0, True, 1)
549
+ for item in trade_payables_items:
550
+ add_row(f" (A) {item['name']}", item["note"], item["value_2024"], item["value_2023"])
551
+ add_row("", "", trade_payables_total_2024, trade_payables_total_2023, True, 2)
552
 
553
  add_row("", "", totals.current_liabilities_2024, totals.current_liabilities_2023, True)
554
  add_row("", "", 0, 0)
 
557
  add_row("TOTAL", "", totals.total_equity_liabilities_2024, totals.total_equity_liabilities_2023, True, 0, True)
558
  add_row("", "", 0, 0)
559
 
560
+ # II. ASSETS
561
+ add_row("II. ASSETS", "", 0, 0, True)
562
+ add_row("", "", 0, 0)
563
 
564
  # Non-current assets
565
  add_row("Non-current assets", "", 0, 0, True)
566
 
567
+ # (1) Property, Plant and Equipment
568
+ ppe_items = [item for item in items if item.get("subcategory") == "Property, Plant and Equipment"]
569
+ if ppe_items:
570
+ add_row("(1) Property, Plant and Equipment", "", 0, 0, True, 1)
571
+ ppe_total_2024 = ppe_total_2023 = 0
572
+ for item in ppe_items:
573
+ add_row(f" ({item['note']}) {item['name']}", item["note"], item["value_2024"], item["value_2023"], False, 1)
574
+ ppe_total_2024 += item["value_2024"]
575
+ ppe_total_2023 += item["value_2023"]
576
+ add_row("", "", ppe_total_2024, ppe_total_2023, True, 1)
577
+ add_row("", "", 0, 0)
578
 
579
  # Other non-current assets
580
+ other_non_current = [item for item in items if item["category"] == "Non-current assets" and item.get("subcategory") != "Property, Plant and Equipment"]
581
  for item in other_non_current:
582
+ add_row(f"({item['note']}) {item['name']}", item["note"], item["value_2024"], item["value_2023"], False, 1)
583
 
584
  add_row("", "", totals.non_current_assets_2024, totals.non_current_assets_2023, True)
585
  add_row("", "", 0, 0)
586
 
587
+ # (2) Current assets
588
+ add_row("(2) Current assets", "", 0, 0, True)
589
  current_asset_items = [item for item in items if item["category"] == "Current assets"]
590
  for item in current_asset_items:
591
+ add_row(f" ({item['note']}) {item['name']}", item["note"], item["value_2024"], item["value_2023"], False, 1)
592
 
593
  add_row("", "", totals.current_assets_2024, totals.current_assets_2023, True)
594
  add_row("", "", 0, 0)
 
602
  balance_2023 = totals.balance_difference_2023
603
 
604
  if balance_2024 < 0.01 and balance_2023 < 0.01:
605
+ add_row(" Balance Sheet is BALANCED", "", 0, 0, True)
606
  else:
607
+ add_row(f" Balance Difference: {balance_2024:.2f} | {balance_2023:.2f}", "", 0, 0, True)
608
 
609
  # Save file
610
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
 
623
  with open(input_file, 'r', encoding='utf-8') as f:
624
  json_data = json.load(f)
625
 
626
+ logger.info("Extracting data using template structure...")
627
 
628
+ # Method 1: Direct extraction using template structure
629
  items = self.extract_from_json_structure(json_data)
630
+ logger.info(f"Extracted {len(items)} items using template structure")
631
 
632
  # Method 2: AI-assisted extraction if needed
633
+ if len([item for item in items if item["value_2024"] != 0 or item["value_2023"] != 0]) < 5:
634
  logger.info("Using AI for additional extraction...")
635
 
636
  # Create summary for AI
 
640
  ai_items = ai_result.get("balance_sheet_items", [])
641
  logger.info(f"AI extracted {len(ai_items)} additional items")
642
 
643
+ # Merge items (replace template items with AI items if they have values)
644
+ ai_items_dict = {item["name"]: item for item in ai_items}
645
+
646
+ for i, item in enumerate(items):
647
+ if item["name"] in ai_items_dict:
648
+ ai_item = ai_items_dict[item["name"]]
649
+ if ai_item["value_2024"] != 0 or ai_item["value_2023"] != 0:
650
+ items[i] = ai_item
 
651
 
652
  # Calculate totals
653
  totals = self.calculate_totals(items)
654
 
655
  # Display summary
656
  logger.info(f"\n BALANCE SHEET SUMMARY:")
657
+ logger.info(f"Template Items: {len(self.template.get_template_structure())}")
658
+ logger.info(f"Items with Values: {len([item for item in items if item['value_2024'] != 0 or item['value_2023'] != 0])}")
659
+ logger.info(f" EQUITY & LIABILITIES 2024:")
660
+ logger.info(f" - Shareholders' funds: Rs. {totals.shareholders_funds_2024:,.2f} Lakhs")
661
+ logger.info(f" - Share application money: Rs. {totals.share_application_money_2024:,.2f} Lakhs")
662
+ logger.info(f" - Non-current liabilities: Rs. {totals.non_current_liabilities_2024:,.2f} Lakhs")
663
+ logger.info(f" - Current liabilities: Rs. {totals.current_liabilities_2024:,.2f} Lakhs")
664
+ logger.info(f" - TOTAL: Rs. {totals.total_equity_liabilities_2024:,.2f} Lakhs")
665
+ logger.info(f" ASSETS 2024:")
666
+ logger.info(f" - Non-current assets: Rs. {totals.non_current_assets_2024:,.2f} Lakhs")
667
+ logger.info(f" - Current assets: Rs. {totals.current_assets_2024:,.2f} Lakhs")
668
+ logger.info(f" - TOTAL: Rs. {totals.total_assets_2024:,.2f} Lakhs")
669
+ logger.info(f" Balance Difference 2024: Rs. {totals.balance_difference_2024:,.2f} Lakhs")
670
+ logger.info(f" Balance Difference 2023: Rs. {totals.balance_difference_2023:,.2f} Lakhs")
671
 
672
  # Check if balanced
673
  is_balanced_2024 = totals.balance_difference_2024 < 0.01
674
  is_balanced_2023 = totals.balance_difference_2023 < 0.01
675
 
676
  if is_balanced_2024 and is_balanced_2023:
677
+ logger.info(" Balance Sheet is PERFECTLY BALANCED!")
678
  else:
679
+ logger.warning(" Balance Sheet has differences - may need adjustment")
680
 
681
+ # Generate Excel using template formatting
682
  output_file = self.generate_balance_sheet_excel(items, totals, output_dir)
683
 
684
+ logger.info(f" SUCCESS: Generated {output_file}")
685
  return output_file
686
 
687
  except Exception as e:
688
+ logger.error(f" Error processing file: {e}", exc_info=True)
689
  return None
690
 
691
  def main() -> None:
 
694
  Accepts input file and output directory from command-line arguments or environment variables.
695
  Handles errors gracefully and logs all major events.
696
  """
697
+ logger.info(" ENHANCED BALANCE SHEET GENERATOR v3.0 (Template-Based) started.")
698
  import sys
699
  api_key = settings.api_key
700
  input_file = settings.input_file
 
704
  if len(sys.argv) > 2:
705
  output_dir = sys.argv[2]
706
  if not api_key:
707
+ logger.error(" Missing OPENROUTER_API_KEY environment variable. Please set your OpenRouter API key in the .env file.")
708
  return
709
  if not os.path.exists(input_file):
710
+ logger.error(f" Input file not found: {input_file}. Please ensure your JSON data file exists.")
711
  return
712
+
713
  generator = EnhancedBalanceSheetGenerator(api_key)
714
  try:
715
  result = generator.process(input_file, output_dir)
716
  if result:
717
  abs_path = os.path.abspath(result)
718
  if os.path.exists(abs_path):
719
+ logger.info(f" COMPLETED SUCCESSFULLY! Output file: {abs_path}")
720
  print(f"Output file: {abs_path}") # For API subprocess parsing
721
  else:
722
+ logger.error(f" PROCESSING FAILED. Output file not created: {abs_path}")
723
  else:
724
+ logger.error(" PROCESSING FAILED. Please check the error messages above and try again.")
725
  except Exception as e:
726
+ logger.error(f" Fatal error: {e}", exc_info=True)
727
 
728
  if __name__ == "__main__":
729
  main()
bs/balance_sheet_template_handler.py CHANGED
@@ -30,21 +30,49 @@ class BalanceSheetTemplate:
30
  """
31
 
32
  def __init__(self):
33
- # Complete Balance Sheet Structure Template
34
  self.template_structure: List[Dict[str, Any]] = [
 
 
35
  BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Shareholders' funds", subcategory="", name="Share capital", note="2").dict(),
36
  BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Shareholders' funds", subcategory="", name="Reserves and surplus", note="3").dict(),
37
- BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Non-Current liabilities", subcategory="", name="Long term borrowings", note="4").dict(),
38
- BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Non-Current liabilities", subcategory="", name="Deferred Tax Liability (Net)", note="5").dict(),
39
- BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Current liabilities", subcategory="", name="Trade payables", note="6").dict(),
 
 
 
 
 
 
 
 
 
 
 
 
40
  BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Current liabilities", subcategory="", name="Other current liabilities", note="7").dict(),
41
- BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Current liabilities", subcategory="", name="Short term provisions", note="8").dict(),
42
- BalanceSheetItem(section="ASSETS", category="Non-current assets", subcategory="Fixed assets", name="Tangible assets", note="9", indent_level=2).dict(),
43
- BalanceSheetItem(section="ASSETS", category="Non-current assets", subcategory="Fixed assets", name="Intangible assets", note="9", indent_level=2).dict(),
44
- BalanceSheetItem(section="ASSETS", category="Non-current assets", subcategory="", name="Long Term Loans and Advances", note="10").dict(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  BalanceSheetItem(section="ASSETS", category="Current assets", subcategory="", name="Inventories", note="11").dict(),
46
  BalanceSheetItem(section="ASSETS", category="Current assets", subcategory="", name="Trade receivables", note="12").dict(),
47
- BalanceSheetItem(section="ASSETS", category="Current assets", subcategory="", name="Cash and bank balances", note="13").dict(),
48
  BalanceSheetItem(section="ASSETS", category="Current assets", subcategory="", name="Short-term loans and advances", note="14").dict(),
49
  BalanceSheetItem(section="ASSETS", category="Current assets", subcategory="", name="Other current assets", note="15").dict()
50
  ]
@@ -53,8 +81,8 @@ class BalanceSheetTemplate:
53
  self.formatting_rules: FormattingRules = FormattingRules(
54
  header={
55
  "title": "Balance Sheet as at March 31, 2024",
56
- "currency_note": "(In Lakhs)",
57
- "column_headers": ["", "Notes", "March 31, 2024", "March 31, 2023"]
58
  },
59
  sections={
60
  "EQUITY AND LIABILITIES": {"display_name": "EQUITY AND LIABILITIES", "order": 1},
@@ -62,13 +90,15 @@ class BalanceSheetTemplate:
62
  },
63
  categories={
64
  "Shareholders' funds": {"display_name": "Shareholders' funds", "show_total": True, "total_label": "", "order": 1},
65
- "Non-Current liabilities": {"display_name": "Non-Current liabilities", "show_total": True, "total_label": "", "order": 2},
66
- "Current liabilities": {"display_name": "Current liabilities", "show_total": True, "total_label": "", "order": 3},
67
- "Non-current assets": {"display_name": "Non-current assets", "show_total": True, "total_label": "", "order": 4},
68
- "Current assets": {"display_name": "Current assets", "show_total": True, "total_label": "", "order": 5}
 
69
  },
70
  subcategories={
71
- "Fixed assets": {"display_name": "Fixed assets", "show_total": True, "total_label": "", "parent_category": "Non-current assets"}
 
72
  },
73
  totals={
74
  "TOTAL_EQUITY_LIABILITIES": {"display_name": "TOTAL", "position": "after_equity_liabilities", "is_grand_total": True},
@@ -76,21 +106,33 @@ class BalanceSheetTemplate:
76
  }
77
  )
78
 
79
- # Field mapping patterns for data extraction
80
  self.field_mappings: Dict[str, List[str]] = {
81
  'share_capital': ['share capital', 'equity share', 'paid up', 'issued shares', 'authorised shares', 'subscribed', 'fully paid'],
82
  'reserves_surplus': ['reserves and surplus', 'reserves', 'surplus', 'retained earnings', 'profit and loss', 'general reserves', 'closing balance'],
 
 
83
  'long_term_borrowings': ['long term borrowings', 'long-term borrowings', 'borrowings', 'debt', 'loans', 'financial corporation', 'bank loan'],
84
- 'deferred_tax': ['deferred tax', 'tax liability', 'deferred tax liability'],
85
- 'trade_payables': ['trade payables', 'payables', 'creditors', 'sundry creditors', 'capital expenditure', 'other expenses'],
 
 
 
 
86
  'other_current_liabilities': ['other current liabilities', 'current maturities', 'outstanding liabilities', 'statutory dues', 'accrued expenses'],
87
  'short_term_provisions': ['short term provisions', 'provisions', 'provision for taxation', 'tax provision'],
88
  'tangible_assets': ['tangible assets', 'property plant', 'fixed assets', 'buildings', 'plant', 'equipment', 'net carrying value'],
89
  'intangible_assets': ['intangible assets', 'software', 'goodwill', 'intangible'],
 
 
 
 
90
  'long_term_loans_advances': ['long term loans', 'security deposits', 'long term advances'],
 
 
91
  'inventories': ['inventories', 'stock', 'consumables', 'raw materials'],
92
  'trade_receivables': ['trade receivables', 'receivables', 'debtors', 'outstanding', 'other receivables'],
93
- 'cash_bank': ['cash and bank', 'cash', 'bank balances', 'current accounts', 'cash on hand', 'fixed deposits'],
94
  'short_term_loans_advances': ['short term loans', 'prepaid expenses', 'other advances', 'advance tax', 'statutory authorities'],
95
  'other_current_assets': ['other current assets', 'accrued income', 'interest accrued']
96
  }
@@ -137,11 +179,12 @@ class BalanceSheetTemplate:
137
  # For backward compatibility - alias the class
138
  BalanceSheet = BalanceSheetTemplate
139
 
140
- # Module level constants for quick access
141
  BALANCE_SHEET_SECTIONS: List[str] = ["EQUITY AND LIABILITIES", "ASSETS"]
142
 
143
  BALANCE_SHEET_CATEGORIES: List[str] = [
144
  "Shareholders' funds",
 
145
  "Non-Current liabilities",
146
  "Current liabilities",
147
  "Non-current assets",
@@ -150,18 +193,30 @@ BALANCE_SHEET_CATEGORIES: List[str] = [
150
 
151
  STANDARD_NOTES_MAPPING: Dict[str, str] = {
152
  "Share capital": "2",
153
- "Reserves and surplus": "3",
154
- "Long term borrowings": "4",
155
- "Deferred Tax Liability (Net)": "5",
156
- "Trade payables": "6",
 
 
 
 
 
 
157
  "Other current liabilities": "7",
158
- "Short term provisions": "8",
159
  "Tangible assets": "9",
160
  "Intangible assets": "9",
161
- "Long Term Loans and Advances": "10",
 
 
 
 
 
 
162
  "Inventories": "11",
163
- "Trade receivables": "12",
164
- "Cash and bank balances": "13",
165
  "Short-term loans and advances": "14",
166
  "Other current assets": "15"
167
  }
@@ -169,17 +224,29 @@ STANDARD_NOTES_MAPPING: Dict[str, str] = {
169
  SIMPLE_TEMPLATE: List[Dict[str, Any]] = [
170
  {"category": "Shareholders' funds", "name": "Share capital", "note": "2"},
171
  {"category": "Shareholders' funds", "name": "Reserves and surplus", "note": "3"},
172
- {"category": "Non-Current liabilities", "name": "Long term borrowings", "note": "4"},
173
- {"category": "Non-Current liabilities", "name": "Deferred Tax Liability (Net)", "note": "5"},
174
- {"category": "Current liabilities", "name": "Trade payables", "note": "6"},
 
 
 
 
 
 
175
  {"category": "Current liabilities", "name": "Other current liabilities", "note": "7"},
176
- {"category": "Current liabilities", "name": "Short term provisions", "note": "8"},
177
- {"category": "Non-current assets", "subcategory": "Fixed assets", "name": "Tangible assets", "note": "9"},
178
- {"category": "Non-current assets", "subcategory": "Fixed assets", "name": "Intangible assets", "note": "9"},
179
- {"category": "Non-current assets", "name": "Long Term Loans and Advances", "note": "10"},
 
 
 
 
 
 
180
  {"category": "Current assets", "name": "Inventories", "note": "11"},
181
  {"category": "Current assets", "name": "Trade receivables", "note": "12"},
182
- {"category": "Current assets", "name": "Cash and bank balances", "note": "13"},
183
  {"category": "Current assets", "name": "Short-term loans and advances", "note": "14"},
184
  {"category": "Current assets", "name": "Other current assets", "note": "15"}
185
  ]
 
30
  """
31
 
32
  def __init__(self):
33
+ # Updated Complete Balance Sheet Structure Template
34
  self.template_structure: List[Dict[str, Any]] = [
35
+ # EQUITY AND LIABILITIES
36
+ # (1) Shareholders' funds
37
  BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Shareholders' funds", subcategory="", name="Share capital", note="2").dict(),
38
  BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Shareholders' funds", subcategory="", name="Reserves and surplus", note="3").dict(),
39
+ BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Shareholders' funds", subcategory="", name="Money received against share warrants", note=" ").dict(),
40
+
41
+ # (2) Share application money pending allotment
42
+ BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Share application money pending allotment", subcategory="", name="Share application money pending allotment", note=" ").dict(),
43
+
44
+ # (3) Non-current liabilities
45
+ BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Non-Current liabilities", subcategory="", name="Long-term borrowings", note="4").dict(),
46
+ BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Non-Current liabilities", subcategory="", name="Deferred tax liabilities (Net)", note="5").dict(),
47
+ BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Non-Current liabilities", subcategory="", name="Other Long-term liabilities", note="8").dict(),
48
+ BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Non-Current liabilities", subcategory="", name="Long-term provisions", note="9").dict(),
49
+
50
+ # (4) Current liabilities
51
+ BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Current liabilities", subcategory="", name="Short-term borrowings", note="10").dict(),
52
+ BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Current liabilities", subcategory="Trade payables", name="total outstanding dues of micro enterprises and small enterprises", note=" ", indent_level=2).dict(),
53
+ BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Current liabilities", subcategory="Trade payables", name="total outstanding dues of creditors other than micro enterprises and small enterprises", note=" ", indent_level=2).dict(),
54
  BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Current liabilities", subcategory="", name="Other current liabilities", note="7").dict(),
55
+ BalanceSheetItem(section="EQUITY AND LIABILITIES", category="Current liabilities", subcategory="", name="Short-term provisions", note="8").dict(),
56
+
57
+ # ASSETS
58
+ # Non-current assets
59
+ # (1) Property, Plant and Equipment
60
+ BalanceSheetItem(section="ASSETS", category="Non-current assets", subcategory="Property, Plant and Equipment", name="Tangible assets", note="9", indent_level=2).dict(),
61
+ BalanceSheetItem(section="ASSETS", category="Non-current assets", subcategory="Property, Plant and Equipment", name="Intangible assets", note="9", indent_level=2).dict(),
62
+ BalanceSheetItem(section="ASSETS", category="Non-current assets", subcategory="Property, Plant and Equipment", name="Capital work-in-progress", note=" ", indent_level=2).dict(),
63
+ BalanceSheetItem(section="ASSETS", category="Non-current assets", subcategory="Property, Plant and Equipment", name="Intangible assets under development", note="9", indent_level=2).dict(),
64
+
65
+ # Other Non-current assets
66
+ BalanceSheetItem(section="ASSETS", category="Non-current assets", subcategory="", name="Non-current investments", note=" ").dict(),
67
+ BalanceSheetItem(section="ASSETS", category="Non-current assets", subcategory="", name="Deferred tax assets (net)", note=" ").dict(),
68
+ BalanceSheetItem(section="ASSETS", category="Non-current assets", subcategory="", name="Long-term loans and advances", note="10").dict(),
69
+ BalanceSheetItem(section="ASSETS", category="Non-current assets", subcategory="", name="Other non-current assets", note=" ").dict(),
70
+
71
+ # (2) Current assets
72
+ BalanceSheetItem(section="ASSETS", category="Current assets", subcategory="", name="Current investments", note=" ").dict(),
73
  BalanceSheetItem(section="ASSETS", category="Current assets", subcategory="", name="Inventories", note="11").dict(),
74
  BalanceSheetItem(section="ASSETS", category="Current assets", subcategory="", name="Trade receivables", note="12").dict(),
75
+ BalanceSheetItem(section="ASSETS", category="Current assets", subcategory="", name="Cash and cash equivalents", note="13").dict(),
76
  BalanceSheetItem(section="ASSETS", category="Current assets", subcategory="", name="Short-term loans and advances", note="14").dict(),
77
  BalanceSheetItem(section="ASSETS", category="Current assets", subcategory="", name="Other current assets", note="15").dict()
78
  ]
 
81
  self.formatting_rules: FormattingRules = FormattingRules(
82
  header={
83
  "title": "Balance Sheet as at March 31, 2024",
84
+ "currency_note": "(Rupees in...........)",
85
+ "column_headers": ["Particulars", "Note No.", "Figures as at the end of current reporting period", "Figures as at the end of the previous reporting period"]
86
  },
87
  sections={
88
  "EQUITY AND LIABILITIES": {"display_name": "EQUITY AND LIABILITIES", "order": 1},
 
90
  },
91
  categories={
92
  "Shareholders' funds": {"display_name": "Shareholders' funds", "show_total": True, "total_label": "", "order": 1},
93
+ "Share application money pending allotment": {"display_name": "Share application money pending allotment", "show_total": True, "total_label": "", "order": 2},
94
+ "Non-Current liabilities": {"display_name": "Non-Current liabilities", "show_total": True, "total_label": "", "order": 3},
95
+ "Current liabilities": {"display_name": "Current liabilities", "show_total": True, "total_label": "", "order": 4},
96
+ "Non-current assets": {"display_name": "Non-current assets", "show_total": True, "total_label": "", "order": 5},
97
+ "Current assets": {"display_name": "Current assets", "show_total": True, "total_label": "", "order": 6}
98
  },
99
  subcategories={
100
+ "Property, Plant and Equipment": {"display_name": "Property, Plant and Equipment", "show_total": True, "total_label": "", "parent_category": "Non-current assets"},
101
+ "Trade payables": {"display_name": "Trade payables", "show_total": True, "total_label": "", "parent_category": "Current liabilities"}
102
  },
103
  totals={
104
  "TOTAL_EQUITY_LIABILITIES": {"display_name": "TOTAL", "position": "after_equity_liabilities", "is_grand_total": True},
 
106
  }
107
  )
108
 
109
+ # Updated Field mapping patterns for data extraction
110
  self.field_mappings: Dict[str, List[str]] = {
111
  'share_capital': ['share capital', 'equity share', 'paid up', 'issued shares', 'authorised shares', 'subscribed', 'fully paid'],
112
  'reserves_surplus': ['reserves and surplus', 'reserves', 'surplus', 'retained earnings', 'profit and loss', 'general reserves', 'closing balance'],
113
+ 'money_against_warrants': ['money received against share warrants', 'share warrants', 'warrants'],
114
+ 'share_application_money': ['share application money', 'application money pending', 'pending allotment'],
115
  'long_term_borrowings': ['long term borrowings', 'long-term borrowings', 'borrowings', 'debt', 'loans', 'financial corporation', 'bank loan'],
116
+ 'deferred_tax_liabilities': ['deferred tax liabilities', 'deferred tax liability', 'tax liability'],
117
+ 'other_long_term_liabilities': ['other long-term liabilities', 'long term liabilities', 'other long term'],
118
+ 'long_term_provisions': ['long-term provisions', 'long term provisions', 'provisions'],
119
+ 'short_term_borrowings': ['short-term borrowings', 'short term borrowings', 'current borrowings'],
120
+ 'trade_payables_micro': ['total outstanding dues of micro enterprises', 'micro enterprises', 'small enterprises dues'],
121
+ 'trade_payables_others': ['total outstanding dues of creditors other than micro', 'other creditors', 'creditors other than micro'],
122
  'other_current_liabilities': ['other current liabilities', 'current maturities', 'outstanding liabilities', 'statutory dues', 'accrued expenses'],
123
  'short_term_provisions': ['short term provisions', 'provisions', 'provision for taxation', 'tax provision'],
124
  'tangible_assets': ['tangible assets', 'property plant', 'fixed assets', 'buildings', 'plant', 'equipment', 'net carrying value'],
125
  'intangible_assets': ['intangible assets', 'software', 'goodwill', 'intangible'],
126
+ 'capital_work_progress': ['capital work-in-progress', 'work in progress', 'construction in progress', 'CWIP'],
127
+ 'intangible_under_development': ['intangible assets under development', 'intangible under development', 'development'],
128
+ 'non_current_investments': ['non-current investments', 'long term investments', 'investments'],
129
+ 'deferred_tax_assets': ['deferred tax assets', 'tax assets'],
130
  'long_term_loans_advances': ['long term loans', 'security deposits', 'long term advances'],
131
+ 'other_non_current_assets': ['other non-current assets', 'other long term assets'],
132
+ 'current_investments': ['current investments', 'short term investments', 'marketable securities'],
133
  'inventories': ['inventories', 'stock', 'consumables', 'raw materials'],
134
  'trade_receivables': ['trade receivables', 'receivables', 'debtors', 'outstanding', 'other receivables'],
135
+ 'cash_equivalents': ['cash and cash equivalents', 'cash', 'bank balances', 'current accounts', 'cash on hand', 'fixed deposits'],
136
  'short_term_loans_advances': ['short term loans', 'prepaid expenses', 'other advances', 'advance tax', 'statutory authorities'],
137
  'other_current_assets': ['other current assets', 'accrued income', 'interest accrued']
138
  }
 
179
  # For backward compatibility - alias the class
180
  BalanceSheet = BalanceSheetTemplate
181
 
182
+ # Updated Module level constants for quick access
183
  BALANCE_SHEET_SECTIONS: List[str] = ["EQUITY AND LIABILITIES", "ASSETS"]
184
 
185
  BALANCE_SHEET_CATEGORIES: List[str] = [
186
  "Shareholders' funds",
187
+ "Share application money pending allotment",
188
  "Non-Current liabilities",
189
  "Current liabilities",
190
  "Non-current assets",
 
193
 
194
  STANDARD_NOTES_MAPPING: Dict[str, str] = {
195
  "Share capital": "2",
196
+ "Reserves and surplus": "3",
197
+ "Money received against share warrants": " ",
198
+ "Share application money pending allotment": " ",
199
+ "Long-term borrowings": "4",
200
+ "Deferred tax liabilities (Net)": "5",
201
+ "Other Long-term liabilities": "8",
202
+ "Long-term provisions": "9",
203
+ "Short-term borrowings": " ",
204
+ "total outstanding dues of micro enterprises and small enterprises": " ",
205
+ "total outstanding dues of creditors other than micro enterprises and small enterprises": " ",
206
  "Other current liabilities": "7",
207
+ "Short-term provisions": "8",
208
  "Tangible assets": "9",
209
  "Intangible assets": "9",
210
+ "Capital work-in-progress": " ",
211
+ "Intangible assets under development": "9",
212
+ "Non-current investments": " ",
213
+ "Deferred tax assets (net)": " ",
214
+ "Long-term loans and advances": "10",
215
+ "Other non-current assets": " ",
216
+ "Current investments": " ",
217
  "Inventories": "11",
218
+ "Trade receivables": "12",
219
+ "Cash and cash equivalents": "13",
220
  "Short-term loans and advances": "14",
221
  "Other current assets": "15"
222
  }
 
224
  SIMPLE_TEMPLATE: List[Dict[str, Any]] = [
225
  {"category": "Shareholders' funds", "name": "Share capital", "note": "2"},
226
  {"category": "Shareholders' funds", "name": "Reserves and surplus", "note": "3"},
227
+ {"category": "Shareholders' funds", "name": "Money received against share warrants", "note": " "},
228
+ {"category": "Share application money pending allotment", "name": "Share application money pending allotment", "note": " "},
229
+ {"category": "Non-Current liabilities", "name": "Long-term borrowings", "note": "4"},
230
+ {"category": "Non-Current liabilities", "name": "Deferred tax liabilities (Net)", "note": "5"},
231
+ {"category": "Non-Current liabilities", "name": "Other Long-term liabilities", "note": "8"},
232
+ {"category": "Non-Current liabilities", "name": "Long-term provisions", "note": "9"},
233
+ {"category": "Current liabilities", "name": "Short-term borrowings", "note": " "},
234
+ {"category": "Current liabilities", "subcategory": "Trade payables", "name": "total outstanding dues of micro enterprises and small enterprises", "note": " "},
235
+ {"category": "Current liabilities", "subcategory": "Trade payables", "name": "total outstanding dues of creditors other than micro enterprises and small enterprises", "note": " "},
236
  {"category": "Current liabilities", "name": "Other current liabilities", "note": "7"},
237
+ {"category": "Current liabilities", "name": "Short-term provisions", "note": "8"},
238
+ {"category": "Non-current assets", "subcategory": "Property, Plant and Equipment", "name": "Tangible assets", "note": "9"},
239
+ {"category": "Non-current assets", "subcategory": "Property, Plant and Equipment", "name": "Intangible assets", "note": "9"},
240
+ {"category": "Non-current assets", "subcategory": "Property, Plant and Equipment", "name": "Capital work-in-progress", "note": " "},
241
+ {"category": "Non-current assets", "subcategory": "Property, Plant and Equipment", "name": "Intangible assets under development", "note": "9"},
242
+ {"category": "Non-current assets", "name": "Non-current investments", "note": " "},
243
+ {"category": "Non-current assets", "name": "Deferred tax assets (net)", "note": " "},
244
+ {"category": "Non-current assets", "name": "Long-term loans and advances", "note": "10"},
245
+ {"category": "Non-current assets", "name": "Other non-current assets", "note": " "},
246
+ {"category": "Current assets", "name": "Current investments", "note": " "},
247
  {"category": "Current assets", "name": "Inventories", "note": "11"},
248
  {"category": "Current assets", "name": "Trade receivables", "note": "12"},
249
+ {"category": "Current assets", "name": "Cash and cash equivalents", "note": "13"},
250
  {"category": "Current assets", "name": "Short-term loans and advances", "note": "14"},
251
  {"category": "Current assets", "name": "Other current assets", "note": "15"}
252
  ]
pnl/profit_loss_statement_generator.py CHANGED
@@ -218,56 +218,74 @@ class PnLGenerator:
218
  right_align = Alignment(horizontal="right", vertical="center")
219
  ws.column_dimensions["A"].width = 45
220
  ws.column_dimensions["B"].width = 8
221
- ws.column_dimensions["C"].width = 15
222
- ws.column_dimensions["D"].width = 15
223
  row = 1
224
  ws.merge_cells("A1:D1")
225
- ws["A1"] = "Statement of Profit and Loss for the year ended March 31, 2024"
226
  ws["A1"].font = title_font
227
  ws["A1"].alignment = center_align
228
- ws["A1"].border = top_bottom_border
229
  row += 2
230
- ws["C3"] = "In Lakhs"
231
- ws["C3"].font = normal_font
232
- ws["C3"].alignment = right_align
233
  row += 1
234
- headers = ["", "Notes", "Year ended March 31, 2024", "Year ended March 31, 2023"]
 
 
 
 
 
 
 
 
 
 
235
  for col, header in enumerate(headers, 1):
236
  cell = ws.cell(row=row, column=col)
237
  cell.value = header
238
  cell.font = header_font
239
- cell.border = top_bottom_border
240
- cell.alignment = center_align if col > 2 else left_align
 
 
 
 
 
 
 
 
 
 
241
  row += 1
242
 
243
  def add_data_row(description: str, note_ref: str, val_2024: float, val_2023: float,
244
- is_bold: bool = False, is_section_header: bool = False) -> None:
245
  """Add a data row with proper formatting."""
246
  nonlocal row
247
  cell_a = ws.cell(row=row, column=1)
248
  cell_a.value = description
249
- cell_a.font = bold_font if (is_bold or is_section_header) else normal_font
250
  cell_a.alignment = left_align
251
- if not is_section_header:
252
- cell_a.border = thin_border
253
  cell_b = ws.cell(row=row, column=2)
254
  cell_b.value = note_ref if note_ref else ""
255
  cell_b.font = normal_font
256
  cell_b.alignment = center_align
257
- if not is_section_header:
258
- cell_b.border = thin_border
259
  cell_c = ws.cell(row=row, column=3)
260
- cell_c.value = self.format_currency(val_2024)
261
  cell_c.font = bold_font if is_bold else normal_font
262
- cell_c.alignment = right_align
263
- if not is_section_header:
264
- cell_c.border = thin_border
265
  cell_d = ws.cell(row=row, column=4)
266
- cell_d.value = self.format_currency(val_2023)
267
  cell_d.font = bold_font if is_bold else normal_font
268
- cell_d.alignment = right_align
269
- if not is_section_header:
270
- cell_d.border = thin_border
271
  row += 1
272
 
273
  logger.info("Extracting financial data...")
@@ -280,56 +298,66 @@ class PnLGenerator:
280
  loss_sale_2024, loss_sale_2023 = self.get_loss_on_sale_data()
281
  finance_2024, finance_2023 = self.get_finance_costs_data()
282
 
283
- # INCOME SECTION
284
- add_data_row("Income", "", 0, 0, is_section_header=True)
285
- add_data_row("Revenue from operations (net)", "16", revenue_2024, revenue_2023)
286
- add_data_row("Other income", "17", other_income_2024, other_income_2023)
287
  total_revenue_2024 = revenue_2024 + other_income_2024
288
  total_revenue_2023 = revenue_2023 + other_income_2023
289
- add_data_row("Total revenue (I)", "", total_revenue_2024, total_revenue_2023, is_bold=True)
290
 
291
- # EXPENSES SECTION
292
- add_data_row("Expenses", "", 0, 0, is_section_header=True)
293
  add_data_row("Cost of materials consumed", "18", materials_2024, materials_2023)
294
- add_data_row("Employee benefit expense", "19", employee_2024, employee_2023)
295
- add_data_row("Other expenses", "20", other_exp_2024, other_exp_2023)
296
- add_data_row("Depreciation and amortisation expense", "21", depreciation_2024, depreciation_2023)
297
- add_data_row("Loss on sale of assets & investments", "22", loss_sale_2024, loss_sale_2023)
 
298
  add_data_row("Finance costs", "23", finance_2024, finance_2023)
 
 
 
299
  total_expenses_2024 = materials_2024 + employee_2024 + other_exp_2024 + depreciation_2024 + loss_sale_2024 + finance_2024
300
  total_expenses_2023 = materials_2023 + employee_2023 + other_exp_2023 + depreciation_2023 + loss_sale_2023 + finance_2023
301
- add_data_row("Total Expenses (II)", "", total_expenses_2024, total_expenses_2023, is_bold=True)
302
-
303
- # Profit before tax
304
- profit_before_tax_2024 = total_revenue_2024 - total_expenses_2024
305
- profit_before_tax_2023 = total_revenue_2023 - total_expenses_2023
306
- add_data_row("Profit before Tax (I) - (II)", "", profit_before_tax_2024, profit_before_tax_2023, is_bold=True)
307
-
308
- # Tax Expense section (placeholders)
309
- add_data_row("IV. TAX EXPENSE", "", 0, 0, is_section_header=True)
310
- add_data_row("Current Tax", "", 0.0, 0.0)
311
- add_data_row("Deferred Tax Liability/(Asset)", "", 0.0, 0.0)
312
- add_data_row("Income Tax relating to Prior Year", "", 0.0, 0.0)
313
- add_data_row("MAT Credit (Entitlement)/Utilisation", "", 0.0, 0.0)
314
- add_data_row("Total Tax Expense (IV)", "", 0.0, 0.0, is_bold=True)
315
-
316
- # Profit after Tax (assuming no tax for now)
317
- profit_after_tax_2024 = profit_before_tax_2024
318
- profit_after_tax_2023 = profit_before_tax_2023
319
- add_data_row("Profit After Tax (III - IV)", "", profit_after_tax_2024, profit_after_tax_2023, is_bold=True)
320
-
321
- # Earnings per share section (placeholders)
322
- add_data_row("Earnings per share", "", 0, 0, is_section_header=True)
323
- add_data_row("Basic and diluted", "30", 0.0, 0.0)
324
- add_data_row("Nominal value", "", 10.0, 10.0)
325
- add_data_row("Weighted average number of equity shares", "30", 0.0, 0.0)
 
326
 
327
  # Footer
328
  row += 2
329
  ws.merge_cells(f"A{row}:D{row}")
330
- ws[f"A{row}"] = "The accompanying notes are an integral part of the financial statements"
331
  ws[f"A{row}"].font = normal_font
332
  ws[f"A{row}"].alignment = left_align
 
 
 
 
 
 
333
 
334
  # Save the file
335
  try:
@@ -339,8 +367,8 @@ class PnLGenerator:
339
  self.print_financial_summary(
340
  total_revenue_2024, total_revenue_2023,
341
  total_expenses_2024, total_expenses_2023,
342
- profit_before_tax_2024, profit_before_tax_2023,
343
- profit_after_tax_2024, profit_after_tax_2023
344
  )
345
  return True
346
  except PermissionError:
 
218
  right_align = Alignment(horizontal="right", vertical="center")
219
  ws.column_dimensions["A"].width = 45
220
  ws.column_dimensions["B"].width = 8
221
+ ws.column_dimensions["C"].width = 20
222
+ ws.column_dimensions["D"].width = 20
223
  row = 1
224
  ws.merge_cells("A1:D1")
225
+ ws["A1"] = "PART II - STATEMENT OF PROFIT AND LOSS"
226
  ws["A1"].font = title_font
227
  ws["A1"].alignment = center_align
 
228
  row += 2
229
+ ws["A3"] = "Name of the Company............................"
230
+ ws["A3"].font = normal_font
231
+ ws["A3"].alignment = left_align
232
  row += 1
233
+ ws["A4"] = "Profit and loss statement for the year ended ..........................."
234
+ ws["A4"].font = normal_font
235
+ ws["A4"].alignment = left_align
236
+ row += 2
237
+ ws["D6"] = "(Rupees in...........)"
238
+ ws["D6"].font = normal_font
239
+ ws["D6"].alignment = right_align
240
+ row += 2
241
+
242
+ # Table headers
243
+ headers = ["Particulars", "Note No.", "Figures as at the end of current reporting period", "Figures as at the end of the previous reporting period"]
244
  for col, header in enumerate(headers, 1):
245
  cell = ws.cell(row=row, column=col)
246
  cell.value = header
247
  cell.font = header_font
248
+ cell.border = thin_border
249
+ cell.alignment = center_align if col > 1 else left_align
250
+ row += 1
251
+
252
+ # Column numbers
253
+ col_numbers = ["1", "2", "3", "4"]
254
+ for col, num in enumerate(col_numbers, 1):
255
+ cell = ws.cell(row=row, column=col)
256
+ cell.value = num
257
+ cell.font = header_font
258
+ cell.border = thin_border
259
+ cell.alignment = center_align
260
  row += 1
261
 
262
  def add_data_row(description: str, note_ref: str, val_2024: float, val_2023: float,
263
+ is_bold: bool = False) -> None:
264
  """Add a data row with proper formatting."""
265
  nonlocal row
266
  cell_a = ws.cell(row=row, column=1)
267
  cell_a.value = description
268
+ cell_a.font = bold_font if is_bold else normal_font
269
  cell_a.alignment = left_align
270
+ cell_a.border = thin_border
271
+
272
  cell_b = ws.cell(row=row, column=2)
273
  cell_b.value = note_ref if note_ref else ""
274
  cell_b.font = normal_font
275
  cell_b.alignment = center_align
276
+ cell_b.border = thin_border
277
+
278
  cell_c = ws.cell(row=row, column=3)
279
+ cell_c.value = "xxx" if val_2024 == 0 and description != "Total Revenue (I + II)" and description != "Total expenses" and "Total" not in description and "Profit" not in description else self.format_currency(val_2024)
280
  cell_c.font = bold_font if is_bold else normal_font
281
+ cell_c.alignment = center_align
282
+ cell_c.border = thin_border
283
+
284
  cell_d = ws.cell(row=row, column=4)
285
+ cell_d.value = "xxx" if val_2023 == 0 and description != "Total Revenue (I + II)" and description != "Total expenses" and "Total" not in description and "Profit" not in description else self.format_currency(val_2023)
286
  cell_d.font = bold_font if is_bold else normal_font
287
+ cell_d.alignment = center_align
288
+ cell_d.border = thin_border
 
289
  row += 1
290
 
291
  logger.info("Extracting financial data...")
 
298
  loss_sale_2024, loss_sale_2023 = self.get_loss_on_sale_data()
299
  finance_2024, finance_2023 = self.get_finance_costs_data()
300
 
301
+ # Revenue Section
302
+ add_data_row("I. Revenue from operations", "16", revenue_2024, revenue_2023)
303
+ add_data_row("II. Other income", "17", other_income_2024, other_income_2023)
 
304
  total_revenue_2024 = revenue_2024 + other_income_2024
305
  total_revenue_2023 = revenue_2023 + other_income_2023
306
+ add_data_row("III. Total Revenue (I + II)", "", total_revenue_2024, total_revenue_2023, is_bold=True)
307
 
308
+ # Expenses Section
309
+ add_data_row("IV. Expenses:", "", 0, 0)
310
  add_data_row("Cost of materials consumed", "18", materials_2024, materials_2023)
311
+ add_data_row("Purchases of Stock-in-Trade", "", 0, 0)
312
+ add_data_row("Changes in inventories of finished goods", "", 0, 0)
313
+ add_data_row("work-in-progress and", "", 0, 0)
314
+ add_data_row("Stock-in-Trade", "", 0, 0)
315
+ add_data_row("Employee benefits expense", "19", employee_2024, employee_2023)
316
  add_data_row("Finance costs", "23", finance_2024, finance_2023)
317
+ add_data_row("Depreciation and amortisation expense", "21", depreciation_2024, depreciation_2023)
318
+ add_data_row("Other expenses", "20", other_exp_2024, other_exp_2023)
319
+
320
  total_expenses_2024 = materials_2024 + employee_2024 + other_exp_2024 + depreciation_2024 + loss_sale_2024 + finance_2024
321
  total_expenses_2023 = materials_2023 + employee_2023 + other_exp_2023 + depreciation_2023 + loss_sale_2023 + finance_2023
322
+ add_data_row("Total expenses", "", total_expenses_2024, total_expenses_2023, is_bold=True)
323
+
324
+ profit_before_exceptional_2024 = total_revenue_2024 - total_expenses_2024
325
+ profit_before_exceptional_2023 = total_revenue_2023 - total_expenses_2023
326
+ add_data_row("V. Profit before exceptional and extraordinary items and tax (III - IV)", "", profit_before_exceptional_2024, profit_before_exceptional_2023, is_bold=True)
327
+
328
+ add_data_row("VI. Exceptional items", "", 0, 0)
329
+ add_data_row("VII. Profit before extraordinary items and tax (V - VI)", "", profit_before_exceptional_2024, profit_before_exceptional_2023, is_bold=True)
330
+ add_data_row("VIII. Extraordinary items", "", 0, 0)
331
+ add_data_row("IX. Profit before tax (VII- VIII)", "", profit_before_exceptional_2024, profit_before_exceptional_2023, is_bold=True)
332
+
333
+ # Tax Section
334
+ add_data_row("X. Tax expense:", "", 0, 0)
335
+ add_data_row("(1) Current tax", "", 0, 0)
336
+ add_data_row("(2) Deferred tax", "", 0, 0)
337
+
338
+ add_data_row("XI. Profit (Loss) for the period from continuing operations (VII-VIII)", "", profit_before_exceptional_2024, profit_before_exceptional_2023, is_bold=True)
339
+ add_data_row("XII. Profit/(loss) from discontinuing operations", "", 0, 0)
340
+ add_data_row("XIII. Tax expense of discontinuing operations", "", 0, 0)
341
+ add_data_row("XIV. Profit/(loss) from Discontinuing operations (after tax) (XII-XIII)", "", 0, 0)
342
+ add_data_row("XV. Profit (Loss) for the period (XI + XIV)", "", profit_before_exceptional_2024, profit_before_exceptional_2023, is_bold=True)
343
+
344
+ # Earnings per share
345
+ add_data_row("XVI. Earnings per equity share:", "", 0, 0)
346
+ add_data_row("(1) Basic", "", 0, 0)
347
+ add_data_row("(2) Diluted", "", 0, 0)
348
 
349
  # Footer
350
  row += 2
351
  ws.merge_cells(f"A{row}:D{row}")
352
+ ws[f"A{row}"] = "See accompanying notes to the financial statements."
353
  ws[f"A{row}"].font = normal_font
354
  ws[f"A{row}"].alignment = left_align
355
+ row += 1
356
+
357
+ ws.merge_cells(f"A{row}:D{row}")
358
+ ws[f"A{row}"] = "GENERAL INSTRUCTIONS FOR PREPARATION OF STATEMENT OF"
359
+ ws[f"A{row}"].font = title_font
360
+ ws[f"A{row}"].alignment = center_align
361
 
362
  # Save the file
363
  try:
 
367
  self.print_financial_summary(
368
  total_revenue_2024, total_revenue_2023,
369
  total_expenses_2024, total_expenses_2023,
370
+ profit_before_exceptional_2024, profit_before_exceptional_2023,
371
+ profit_before_exceptional_2024, profit_before_exceptional_2023
372
  )
373
  return True
374
  except PermissionError: