Sahil Garg commited on
Commit
c79824c
·
1 Parent(s): c3dda2f

need to test all routes once again

Browse files
.gitignore ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ venv/
2
+ .venv/
3
+ .env
4
+ __pycache__/
5
+ *.pyc
6
+ *.pyo
7
+ *.pyd
8
+ *.sqlite3
9
+ *.db
10
+ *.log
11
+ *.bak
12
+ *.swp
13
+ *.tmp
14
+ *.xlsx
15
+ *.csv
16
+ input/
17
+ output*/
18
+ csv_notes_pnl/
19
+ generated_notes*/
20
+ balancesheet_excel/
21
+ cashflow_excel/
22
+ pnl_excel/
23
+ docker-compose.override.yml
24
+ .vscode/
25
+ app/__pycache__/
26
+ pnlbs/__pycache__/
Dockerfile ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Python 3.11 as base image (you can change to 3.9 if you want)
2
+ FROM python:3.11-slim
3
+
4
+ # -------------------------------
5
+ # Set working directory
6
+ WORKDIR /app
7
+
8
+ # -------------------------------
9
+ # Install system dependencies
10
+ RUN apt-get update && apt-get install -y \
11
+ build-essential \
12
+ curl \
13
+ git \
14
+ && rm -rf /var/lib/apt/lists/*
15
+
16
+ # -------------------------------
17
+ # Copy requirements file
18
+ COPY requirements.txt .
19
+
20
+ # -------------------------------
21
+ # Install Python dependencies
22
+ RUN pip install --no-cache-dir -r requirements.txt
23
+
24
+ # -------------------------------
25
+ # Create necessary directories (customize as needed)
26
+ RUN mkdir -p /app/input \
27
+ /app/output1 \
28
+ /app/generated_notes \
29
+ && chmod -R 777 /app/input /app/output1 /app/generated_notes
30
+
31
+ # -------------------------------
32
+ # Copy the application code
33
+ COPY . /app/
34
+
35
+ # -------------------------------
36
+ # Set environment variables
37
+ ENV PYTHONPATH=/app
38
+ ENV PYTHONUNBUFFERED=1
39
+
40
+ # -------------------------------
41
+ # Expose the port the app runs on
42
+ EXPOSE 8000
43
+
44
+ # -------------------------------
45
+ # Command to run the FastAPI app
46
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
app/api.py ADDED
@@ -0,0 +1,391 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, UploadFile, File, Form, HTTPException
2
+ from fastapi.responses import JSONResponse, PlainTextResponse
3
+ from typing import Optional, Dict, Any
4
+ from app.notes import generate_notes
5
+ from app.utils import clean_value
6
+ import pandas as pd
7
+ import os
8
+ from app.pnl import generate_pnl_report
9
+ import shutil
10
+ from app.extract import extract_trial_balance_data, analyze_and_save_results
11
+ from app.new_main import FlexibleFinancialNoteGenerator
12
+ import json
13
+ from app.main16_23 import process_json
14
+ from app.json_xlsx import json_to_xlsx
15
+ from app.utils_normalize import normalize_llm_note_json, normalize_llm_notes_json
16
+ from app.bs import generate_balance_sheet_report
17
+ from app.cashflow import generate_cashflow_report
18
+ import subprocess
19
+ import logging
20
+
21
+ # Configure logging
22
+ logging.basicConfig(level=logging.INFO)
23
+ logger = logging.getLogger(__name__)
24
+
25
+ router = APIRouter()
26
+
27
+ def process_uploaded_file(file: UploadFile) -> pd.DataFrame:
28
+ """
29
+ Save uploaded file, extract trial balance, and return DataFrame.
30
+ """
31
+ os.makedirs("input", exist_ok=True)
32
+ file_location = f"input/{file.filename}"
33
+ with open(file_location, "wb") as buffer:
34
+ shutil.copyfileobj(file.file, buffer)
35
+ structured_data = extract_trial_balance_data(file_location)
36
+ output_file = "output1/parsed_trial_balance.json"
37
+ analyze_and_save_results(structured_data, output_file)
38
+ with open(output_file, "r", encoding="utf-8") as f:
39
+ parsed_data = json.load(f)
40
+ tb_df = pd.DataFrame(parsed_data if isinstance(parsed_data, list) else parsed_data.get("trial_balance", parsed_data))
41
+ tb_df['amount'] = tb_df['amount'].apply(clean_value)
42
+ return tb_df
43
+
44
+ @router.post("/notes/json")
45
+ async def post_notes_json(
46
+ file: UploadFile = File(...),
47
+ note_number: Optional[str] = Form(None)
48
+ ):
49
+ """
50
+ Generate notes as JSON from uploaded Excel file.
51
+ Optionally filter by note_number (comma-separated).
52
+ """
53
+ tb_df = process_uploaded_file(file)
54
+ notes = generate_notes(tb_df)
55
+ # Filter notes if note_number is provided
56
+ if note_number:
57
+ numbers = [n.strip() for n in note_number.split(",")]
58
+ notes = [note for note in notes if any(note['Note'].startswith(f"{n}.") or note['Note'] == n for n in numbers)]
59
+ return JSONResponse({"notes": notes})
60
+
61
+ @router.post("/notes/text")
62
+ async def post_notes_text(
63
+ file: UploadFile = File(...),
64
+ note_number: Optional[str] = Form(None)
65
+ ):
66
+ """
67
+ Generate notes as Markdown text from uploaded Excel file.
68
+ Optionally filter by note_number (comma-separated).
69
+ """
70
+ tb_df = process_uploaded_file(file)
71
+ notes = generate_notes(tb_df)
72
+ # Filter notes if note_number is provided
73
+ if note_number:
74
+ numbers = [n.strip() for n in note_number.split(",")]
75
+ notes = [note for note in notes if any(note['Note'].startswith(f"{n}.") or note['Note'] == n for n in numbers)]
76
+ # Build markdown string
77
+ md = "# Notes to Financial Statements for the Year Ended March 31, 2024\n\n"
78
+ for note in notes:
79
+ md += f"## {note['Note']}\n\n{note['Content']}\n\n"
80
+ return PlainTextResponse(md, media_type="text/plain")
81
+
82
+ @router.post("/cf")
83
+ async def generate_cashflow():
84
+ """
85
+ Generate Cash Flow Statement Excel file.
86
+ """
87
+ try:
88
+ generate_cashflow_report()
89
+ return {"message": "Cash Flow report generated successfully as 'cashflow_excel/cashflow_report.xlsx'."}
90
+ except Exception as e:
91
+ logger.error(f"Failed to generate Cash Flow report: {str(e)}")
92
+ return {"error": f"Failed to generate Cash Flow report: {str(e)}"}
93
+
94
+
95
+ @router.post("/bs")
96
+ async def generate_balancesheet():
97
+ """
98
+ Generate Balance Sheet Excel file.
99
+ """
100
+ try:
101
+ generate_balance_sheet_report()
102
+ return {"message": "Balance Sheet report generated successfully as 'balancesheet_excel/balancesheet_report.xlsx'."}
103
+ except Exception as e:
104
+ logger.error(f"Failed to generate Balance Sheet: {str(e)}")
105
+ return {"error": f"Failed to generate Balance Sheet: {str(e)}"}
106
+
107
+ @router.post("/pnl")
108
+ async def generate_pnl():
109
+ """
110
+ Generate Profit & Loss Excel file.
111
+ """
112
+ try:
113
+ generate_pnl_report()
114
+ return {"message": "P&L report generated successfully as 'pnl_excel/pnl_report.xlsx'."}
115
+ except Exception as e:
116
+ logger.error(f"Failed to generate P&L report: {str(e)}")
117
+ return {"error": f"Failed to generate P&L report: {str(e)}"}
118
+
119
+
120
+ @router.post("/new")
121
+ async def llm_generate_and_excel(
122
+ file: UploadFile = File(...),
123
+ note_number: Optional[str] = Form(None)
124
+ ):
125
+ """
126
+ Generate notes using LLM and save as Excel.
127
+ Optionally filter by note_number (comma-separated).
128
+ """
129
+ os.makedirs("input", exist_ok=True)
130
+ file_location = f"input/{file.filename}"
131
+ with open(file_location, "wb") as buffer:
132
+ shutil.copyfileobj(file.file, buffer)
133
+
134
+ # Extract trial balance and save as JSON
135
+ structured_data = extract_trial_balance_data(file_location)
136
+ output_json = "output1/parsed_trial_balance.json"
137
+ analyze_and_save_results(structured_data, output_json)
138
+
139
+ # Initialize the generator
140
+ try:
141
+ generator = FlexibleFinancialNoteGenerator()
142
+ except Exception as e:
143
+ logger.error(f"Generator init failed: {e}")
144
+ raise HTTPException(status_code=500, detail=f"Generator init failed: {e}")
145
+
146
+ os.makedirs("generated_notes_excel", exist_ok=True)
147
+ wrapped_json_path = "generated_notes/notes_wrapped.json"
148
+
149
+ if note_number:
150
+ # Support multiple note numbers (comma-separated)
151
+ note_numbers = [n.strip() for n in note_number.split(",")]
152
+ all_notes = []
153
+ for n in note_numbers:
154
+ success = generator.generate_note(n, trial_balance_path=output_json)
155
+ if success:
156
+ # Read the just-generated note
157
+ with open("generated_notes/notes.json", "r", encoding="utf-8") as f:
158
+ note_json = json.load(f)
159
+ all_notes.append(note_json)
160
+ # Now write all notes together
161
+ with open("generated_notes/notes.json", "w", encoding="utf-8") as f:
162
+ json.dump({"notes": all_notes}, f, indent=2, ensure_ascii=False)
163
+ # --- Normalize all notes ---
164
+ wrapped = normalize_llm_notes_json({"notes": all_notes})
165
+ with open(wrapped_json_path, "w", encoding="utf-8") as f2:
166
+ json.dump(wrapped, f2, ensure_ascii=False, indent=2)
167
+ # --------------------------
168
+ excel_path = "generated_notes_excel/notes.xlsx"
169
+ json_to_xlsx(wrapped_json_path, excel_path)
170
+ return {"message": f"Notes {', '.join(note_numbers)} generated. Excel saved at {excel_path}."}
171
+ else:
172
+ # Generate all notes
173
+ results = generator.generate_all_notes(trial_balance_path=output_json)
174
+ if not any(results.values()):
175
+ logger.error("Failed to generate any notes. LLM API may be down or unreachable.")
176
+ raise HTTPException(status_code=500, detail="Failed to generate any notes. LLM API may be down or unreachable.")
177
+ # Read all notes.json
178
+ with open("generated_notes/notes.json", "r", encoding="utf-8") as f:
179
+ notes_json = json.load(f)
180
+ # --- Normalize all notes ---
181
+ wrapped = normalize_llm_notes_json(notes_json)
182
+ with open(wrapped_json_path, "w", encoding="utf-8") as f2:
183
+ json.dump(wrapped, f2, ensure_ascii=False, indent=2)
184
+ # --------------------------
185
+ excel_path = "generated_notes_excel/notes.xlsx"
186
+ json_to_xlsx(wrapped_json_path, excel_path)
187
+ return {"message": f"All notes generated. Excel saved at {excel_path}."}
188
+
189
+
190
+
191
+ @router.post("/hardcoded")
192
+ async def run_full_pipeline(
193
+ file: UploadFile = File(...),
194
+ note_number: Optional[str] = Form(None)
195
+ ):
196
+ """
197
+ Run the full hardcoded pipeline: extract, process, filter, and convert to Excel.
198
+ Optionally filter by note_number (comma-separated).
199
+ """
200
+ os.makedirs("input", exist_ok=True)
201
+ file_location = f"input/{file.filename}"
202
+ with open(file_location, "wb") as buffer:
203
+ shutil.copyfileobj(file.file, buffer)
204
+
205
+ # Run extract.py logic and save to output1
206
+ os.makedirs("output1", exist_ok=True)
207
+ structured_data = extract_trial_balance_data(file_location)
208
+ output1_json = "output1/parsed_trial_balance.json"
209
+ analyze_and_save_results(structured_data, output1_json)
210
+
211
+ # Run main16-23.py logic and save to output2
212
+ os.makedirs("output2", exist_ok=True)
213
+ try:
214
+ process_json(output1_json)
215
+ except ImportError:
216
+ logger.error("main16_23.process_json not found. Please ensure 'app/main16_23.py' exists and is named correctly.")
217
+ raise HTTPException(status_code=500, detail="main16_23.process_json not found. Please ensure 'app/main16_23.py' exists and is named correctly.")
218
+ except Exception as e:
219
+ logger.error(f"main16_23.process_json failed: {e}")
220
+ raise HTTPException(status_code=500, detail=f"main16_23.process_json failed: {e}")
221
+
222
+ # Filter notes if note_number is provided
223
+ notes_json = "output2/notes_output.json"
224
+ with open(notes_json, "r", encoding="utf-8") as f:
225
+ notes_data = json.load(f)
226
+
227
+ # If notes_data is a dict with a key (e.g. "notes"), extract the list
228
+ if isinstance(notes_data, dict):
229
+ for key in ["notes", "trial_balance"]:
230
+ if key in notes_data:
231
+ notes_data = notes_data[key]
232
+ break
233
+
234
+ # Always wrap as dict for Excel conversion
235
+ def wrap_notes(notes):
236
+ return {"notes": notes}
237
+
238
+ # Filter notes if note_number is provided
239
+ if note_number:
240
+ numbers = [n.strip() for n in note_number.split(",")]
241
+ notes_data = [
242
+ note for note in notes_data
243
+ if str(note.get('note_number', '')).strip() in numbers
244
+ ]
245
+ filtered_json = "output2/notes_output_filtered.json"
246
+ with open(filtered_json, "w", encoding="utf-8") as f2:
247
+ json.dump(wrap_notes(notes_data), f2, ensure_ascii=False, indent=2)
248
+ json_input_for_excel = filtered_json
249
+ else:
250
+ temp_json = "output2/notes_output_wrapped.json"
251
+ with open(temp_json, "w", encoding="utf-8") as f2:
252
+ json.dump(wrap_notes(notes_data), f2, ensure_ascii=False, indent=2)
253
+ json_input_for_excel = temp_json
254
+
255
+ # Run json-xlsx.py logic and save to output3
256
+ os.makedirs("output3", exist_ok=True)
257
+ try:
258
+ output3_xlsx = "output3/final_output.xlsx"
259
+ json_to_xlsx(json_input_for_excel, output3_xlsx)
260
+ except ImportError:
261
+ logger.error("json_xlsx.json_to_xlsx not found")
262
+ raise HTTPException(status_code=500, detail="json_xlsx.json_to_xlsx not found")
263
+ except Exception as e:
264
+ logger.error(f"json_xlsx.json_to_xlsx failed: {e}")
265
+ raise HTTPException(status_code=500, detail=f"json_xlsx.json_to_xlsx failed: {e}")
266
+
267
+ return {"message": "Pipeline completed successfully. Excel file saved in output3."}
268
+
269
+ def run_subprocess(
270
+ script_path: str,
271
+ args: list,
272
+ env: Dict[str, str],
273
+ cwd: str
274
+ ) -> subprocess.CompletedProcess:
275
+ """
276
+ Run a subprocess and return the result.
277
+ Raises HTTPException on failure.
278
+ """
279
+ try:
280
+ logger.info(f"Running {script_path} with args {args} in {cwd}")
281
+ result = subprocess.run(
282
+ ["python", script_path] + args,
283
+ capture_output=True,
284
+ text=True,
285
+ check=True,
286
+ env=env,
287
+ cwd=cwd
288
+ )
289
+ logger.debug(f"{script_path} STDOUT:\n{result.stdout}")
290
+ logger.debug(f"{script_path} STDERR:\n{result.stderr}")
291
+ return result
292
+ except subprocess.CalledProcessError as e:
293
+ logger.error(f"{script_path} failed: {e}")
294
+ logger.error(f"STDOUT: {e.stdout}")
295
+ logger.error(f"STDERR: {e.stderr}")
296
+ raise HTTPException(
297
+ status_code=500,
298
+ detail=f"{script_path} failed: {e}\nSTDOUT:\n{e.stdout}\nSTDERR:\n{e.stderr}"
299
+ )
300
+
301
+
302
+ def extract_output_file(stdout: str, keyword: str = "Output file:") -> Optional[str]:
303
+ """
304
+ Extract output file path from subprocess stdout.
305
+ """
306
+ for line in stdout.splitlines():
307
+ if keyword in line:
308
+ return line.split(keyword)[-1].strip()
309
+ return None
310
+
311
+
312
+
313
+
314
+ @router.post("/bs_from_notes")
315
+ async def bs_from_notes(file: UploadFile = File(...)):
316
+ """
317
+ Accepts an Excel file, runs the full pipeline (sircodebs.py -> csv_json_bs.py -> bl_llm.py),
318
+ and returns the path to the generated balance sheet Excel file.
319
+ """
320
+ os.makedirs("input", exist_ok=True)
321
+ input_excel_path = os.path.join("input", file.filename)
322
+ with open(input_excel_path, "wb") as buffer:
323
+ shutil.copyfileobj(file.file, buffer)
324
+ logger.info(f"Uploaded Excel saved to: {input_excel_path}")
325
+ logger.info(f"Files in input/: {os.listdir('input')}")
326
+
327
+ env = os.environ.copy()
328
+ if os.getenv("OPENROUTER_API_KEY"):
329
+ env["OPENROUTER_API_KEY"] = os.getenv("OPENROUTER_API_KEY")
330
+ env["INPUT_FILE"] = "clean_financial_data_bs.json"
331
+ cwd = "C:/SAHIL/NOTES"
332
+
333
+ # Run sircodebs.py
334
+ run_subprocess("pnlbs/sircodebs.py", [input_excel_path], env, cwd)
335
+ logger.info(f"Files in csv_notes_bs/: {os.listdir('csv_notes_bs') if os.path.exists('csv_notes_bs') else 'csv_notes_bs does not exist'}")
336
+
337
+ # Run csv_json_bs.py
338
+ run_subprocess("pnlbs/csv_json_bs.py", [], env, cwd)
339
+ logger.info(f"clean_financial_data_bs.json exists: {os.path.exists('clean_financial_data_bs.json')}")
340
+
341
+ # Run bl_llm.py
342
+ result = run_subprocess("pnlbs/bl_llm.py", [], env, cwd)
343
+ output_file = extract_output_file(result.stdout)
344
+ if not output_file or not os.path.exists(output_file):
345
+ debug_msg = f"\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
346
+ logger.error(f"Could not determine output file from bl_llm.py output.{debug_msg}")
347
+ raise HTTPException(status_code=500, detail=f"Could not determine output file from bl_llm.py output.{debug_msg}")
348
+
349
+ logger.info(f"Pipeline completed. Output file: {output_file}")
350
+ return {"message": "Balance Sheet generated successfully.", "file": output_file}
351
+
352
+
353
+
354
+
355
+ @router.post("/pnl_from_notes")
356
+ async def pnl_from_notes(file: UploadFile = File(...)):
357
+ """
358
+ Accepts an Excel file, runs the full pipeline (sircodepnl.py -> csv_json_pnl.py -> pnl_note.py),
359
+ and returns the path to the generated P&L Excel file.
360
+ """
361
+ os.makedirs("input", exist_ok=True)
362
+ input_excel_path = os.path.join("input", file.filename)
363
+ with open(input_excel_path, "wb") as buffer:
364
+ shutil.copyfileobj(file.file, buffer)
365
+ logger.info(f"Uploaded Excel saved to: {input_excel_path}")
366
+ logger.info(f"Files in input/: {os.listdir('input')}")
367
+
368
+ env = os.environ.copy()
369
+ if os.getenv("OPENROUTER_API_KEY"):
370
+ env["OPENROUTER_API_KEY"] = os.getenv("OPENROUTER_API_KEY")
371
+ env["INPUT_FILE"] = "clean_financial_data_pnl.json"
372
+ cwd = "C:/SAHIL/NOTES"
373
+
374
+ # Run sircodepnl.py
375
+ run_subprocess("pnlbs/sircodepnl.py", [input_excel_path], env, cwd)
376
+ logger.info(f"Files in csv_notes_pnl/: {os.listdir('csv_notes_pnl') if os.path.exists('csv_notes_pnl') else 'csv_notes_pnl does not exist'}")
377
+
378
+ # Run csv_json_pnl.py
379
+ run_subprocess("pnlbs/csv_json_pnl.py", [], env, cwd)
380
+ logger.info(f"clean_financial_data_pnl.json exists: {os.path.exists('clean_financial_data_pnl.json')}")
381
+
382
+ # Run pnl_note.py
383
+ result = run_subprocess("pnlbs/pnl_note.py", [], env, cwd)
384
+ output_file = extract_output_file(result.stdout)
385
+ if not output_file or not os.path.exists(output_file):
386
+ debug_msg = f"\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
387
+ logger.error(f"Could not determine output file from pnl_note.py output.{debug_msg}")
388
+ raise HTTPException(status_code=500, detail=f"Could not determine output file from pnl_note.py output.{debug_msg}")
389
+
390
+ logger.info(f"Pipeline completed. Output file: {output_file}")
391
+ return {"message": "Profit and Loss statement generated successfully.", "file": output_file}
app/bs.py ADDED
@@ -0,0 +1,314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import logging
4
+ from typing import Optional, Dict, Any
5
+ from openpyxl import Workbook
6
+ from openpyxl.styles import Font, Border, Side, Alignment
7
+
8
+ # Configure logging
9
+ logging.basicConfig(level=logging.INFO)
10
+ logger = logging.getLogger(__name__)
11
+
12
+ # Configuration (externalized via environment variables)
13
+ BALANCE_SHEET_OUTPUT_FOLDER = os.getenv("BALANCE_SHEET_OUTPUT_FOLDER", "balancesheet_excel")
14
+ BALANCE_SHEET_OUTPUT_FILE = os.getenv("BALANCE_SHEET_OUTPUT_FILE", "balancesheet_report.xlsx")
15
+
16
+ def load_note_data(note_number: str, folder: Optional[str] = None) -> Optional[Dict[str, Any]]:
17
+ """
18
+ Load note data for a specific note_number from notes.json in the specified folder.
19
+ Returns the note dict or None if not found.
20
+ """
21
+ folder = folder or os.path.join(os.path.dirname(os.path.dirname(__file__)), "generated_notes")
22
+ file_path = os.path.join(folder, "notes.json")
23
+ try:
24
+ with open(file_path, "r", encoding="utf-8") as f:
25
+ data = json.load(f)
26
+ notes = data.get("notes", [])
27
+ for note in notes:
28
+ n_num = note.get("note_number") or note.get("metadata", {}).get("note_number")
29
+ if str(n_num) == str(note_number):
30
+ return note
31
+ logger.warning(f"Note {note_number} not found in {file_path}")
32
+ except (FileNotFoundError, json.JSONDecodeError) as e:
33
+ logger.error(f"Error loading note {note_number}: {e}")
34
+ return None
35
+
36
+ def extract_total_from_note(note_data: Optional[Dict[str, Any]], year: str = "2024") -> float:
37
+ """
38
+ Extract total value from note data for the specified year.
39
+ Returns 0.0 if not found or invalid.
40
+ """
41
+ if not note_data or "structure" not in note_data:
42
+ return 0.0
43
+ for category in note_data["structure"]:
44
+ if year == "2024" and "total" in category:
45
+ try:
46
+ value = float(category["total"])
47
+ if value > 100000:
48
+ value = value / 100000
49
+ return value
50
+ except (ValueError, TypeError):
51
+ continue
52
+ elif year == "2023" and "previous_total" in category:
53
+ try:
54
+ value = float(category["previous_total"])
55
+ if value > 100000:
56
+ value = value / 100000
57
+ return value
58
+ except (ValueError, TypeError):
59
+ continue
60
+ # If no total found, sum up subcategory values
61
+ total = 0.0
62
+ for category in note_data["structure"]:
63
+ if "subcategories" in category:
64
+ for subcat in category["subcategories"]:
65
+ if year == "2024" and "value" in subcat:
66
+ try:
67
+ value = float(subcat["value"])
68
+ if value > 100000:
69
+ value = value / 100000
70
+ total += value
71
+ except (ValueError, TypeError):
72
+ continue
73
+ elif year == "2023" and "previous_value" in subcat:
74
+ try:
75
+ value = float(subcat["previous_value"])
76
+ if value > 100000:
77
+ value = value / 100000
78
+ total += value
79
+ except (ValueError, TypeError):
80
+ continue
81
+ return total
82
+
83
+ def extract_specific_value(note_data: Optional[Dict[str, Any]], key: str, year: str = "2024") -> float:
84
+ """
85
+ Extract specific value from note data based on key and year.
86
+ Returns 0.0 if not found or invalid.
87
+ """
88
+ if not note_data or "structure" not in note_data:
89
+ return 0.0
90
+ for category in note_data["structure"]:
91
+ if "subcategories" in category:
92
+ for subcat in category["subcategories"]:
93
+ label = subcat.get("label", "").lower().replace(" ","").replace("-","").replace("/","").replace("&","")
94
+ if key.lower() in label:
95
+ if year == "2024" and "value" in subcat:
96
+ try:
97
+ value = float(subcat["value"])
98
+ if value > 100000:
99
+ value = value / 100000
100
+ return value
101
+ except (ValueError, TypeError):
102
+ continue
103
+ elif year == "2023" and "previous_value" in subcat:
104
+ try:
105
+ value = float(subcat["previous_value"])
106
+ if value > 100000:
107
+ value = value / 100000
108
+ return value
109
+ except (ValueError, TypeError):
110
+ continue
111
+ return 0.0
112
+
113
+ def format_currency(value: float) -> str:
114
+ """
115
+ Format currency value for display.
116
+ """
117
+ if isinstance(value, (int, float)) and value != 0:
118
+ return f"{value:,.2f}"
119
+ return "0.00"
120
+
121
+ def generate_balance_sheet_report() -> None:
122
+ """
123
+ Generate Balance Sheet report in Excel format using data from generated_notes folder.
124
+ Output location is configurable via environment variables.
125
+ """
126
+ wb = Workbook()
127
+ ws = wb.active
128
+ ws.title = "Balance Sheet"
129
+
130
+ # Define styles
131
+ bold_font = Font(bold=True)
132
+ thin_border = Border(left=Side(style="thin"), right=Side(style="thin"),
133
+ top=Side(style="thin"), bottom=Side(style="thin"))
134
+ center_align = Alignment(horizontal="center")
135
+ left_align = Alignment(horizontal="left")
136
+ right_align = Alignment(horizontal="right")
137
+
138
+ # Set column widths
139
+ ws.column_dimensions["A"].width = 50
140
+ ws.column_dimensions["B"].width = 10
141
+ ws.column_dimensions["C"].width = 20
142
+ ws.column_dimensions["D"].width = 20
143
+
144
+ # Header
145
+ ws["A1"] = "Balance Sheet as at March 31, 2024"
146
+ ws["A1"].font = bold_font
147
+ ws.merge_cells("A1:D1")
148
+ ws["A1"].alignment = center_align
149
+
150
+ # Units
151
+ ws["A2"] = "In Lakhs"
152
+ ws.merge_cells("A2:D2")
153
+ ws["A2"].alignment = right_align
154
+
155
+ # Table headers
156
+ headers = ["", "Notes", "March 31, 2024", "March 31, 2023"]
157
+ for col, header in enumerate(headers, 1):
158
+ cell = ws.cell(row=4, column=col)
159
+ cell.value = header
160
+ cell.font = bold_font
161
+ cell.border = thin_border
162
+ cell.alignment = center_align if col > 1 else left_align
163
+
164
+ # Load all required notes (2-15 for Balance Sheet)
165
+ notes_data = {str(note_num): load_note_data(str(note_num)) for note_num in range(2, 16)}
166
+
167
+ # Extract values from notes
168
+ share_capital_2024 = extract_total_from_note(notes_data.get("2"), "2024")
169
+ share_capital_2023 = extract_total_from_note(notes_data.get("2"), "2023")
170
+ reserves_surplus_2024 = extract_total_from_note(notes_data.get("3"), "2024")
171
+ reserves_surplus_2023 = extract_total_from_note(notes_data.get("3"), "2023")
172
+ long_term_borrowings_2024 = extract_total_from_note(notes_data.get("4"), "2024")
173
+ long_term_borrowings_2023 = extract_total_from_note(notes_data.get("4"), "2023")
174
+ deferred_tax_liability_2024 = extract_total_from_note(notes_data.get("5"), "2024")
175
+ deferred_tax_liability_2023 = extract_total_from_note(notes_data.get("5"), "2023")
176
+ trade_payables_2024 = extract_total_from_note(notes_data.get("6"), "2024")
177
+ trade_payables_2023 = extract_total_from_note(notes_data.get("6"), "2023")
178
+ other_current_liabilities_2024 = extract_total_from_note(notes_data.get("7"), "2024")
179
+ other_current_liabilities_2023 = extract_total_from_note(notes_data.get("7"), "2023")
180
+ short_term_provisions_2024 = extract_total_from_note(notes_data.get("8"), "2024")
181
+ short_term_provisions_2023 = extract_total_from_note(notes_data.get("8"), "2023")
182
+ fixed_assets_2024 = extract_total_from_note(notes_data.get("9"), "2024")
183
+ fixed_assets_2023 = extract_total_from_note(notes_data.get("9"), "2023")
184
+ long_term_loans_advances_2024 = extract_total_from_note(notes_data.get("10"), "2024")
185
+ long_term_loans_advances_2023 = extract_total_from_note(notes_data.get("10"), "2023")
186
+ inventories_2024 = extract_total_from_note(notes_data.get("11"), "2024")
187
+ inventories_2023 = extract_total_from_note(notes_data.get("11"), "2023")
188
+ trade_receivables_2024 = extract_total_from_note(notes_data.get("12"), "2024")
189
+ trade_receivables_2023 = extract_total_from_note(notes_data.get("12"), "2023")
190
+ cash_bank_balances_2024 = extract_total_from_note(notes_data.get("13"), "2024")
191
+ cash_bank_balances_2023 = extract_total_from_note(notes_data.get("13"), "2023")
192
+ short_term_loans_advances_2024 = extract_total_from_note(notes_data.get("14"), "2024")
193
+ short_term_loans_advances_2023 = extract_total_from_note(notes_data.get("14"), "2023")
194
+ other_current_assets_2024 = extract_total_from_note(notes_data.get("15"), "2024")
195
+ other_current_assets_2023 = extract_total_from_note(notes_data.get("15"), "2023")
196
+
197
+ # Calculate totals
198
+ shareholders_funds_2024 = share_capital_2024 + reserves_surplus_2024
199
+ shareholders_funds_2023 = share_capital_2023 + reserves_surplus_2023
200
+ non_current_liabilities_2024 = long_term_borrowings_2024 + deferred_tax_liability_2024
201
+ non_current_liabilities_2023 = long_term_borrowings_2023 + deferred_tax_liability_2023
202
+ current_liabilities_2024 = trade_payables_2024 + other_current_liabilities_2024 + short_term_provisions_2024
203
+ current_liabilities_2023 = trade_payables_2023 + other_current_liabilities_2023 + short_term_provisions_2023
204
+ total_equity_liabilities_2024 = shareholders_funds_2024 + non_current_liabilities_2024 + current_liabilities_2024
205
+ total_equity_liabilities_2023 = shareholders_funds_2023 + non_current_liabilities_2023 + current_liabilities_2023
206
+ non_current_assets_2024 = fixed_assets_2024 + long_term_loans_advances_2024
207
+ non_current_assets_2023 = fixed_assets_2023 + long_term_loans_advances_2023
208
+ current_assets_2024 = (inventories_2024 + trade_receivables_2024 + cash_bank_balances_2024 +
209
+ short_term_loans_advances_2024 + other_current_assets_2024)
210
+ current_assets_2023 = (inventories_2023 + trade_receivables_2023 + cash_bank_balances_2023 +
211
+ short_term_loans_advances_2023 + other_current_assets_2023)
212
+ total_assets_2024 = non_current_assets_2024 + current_assets_2024
213
+ total_assets_2023 = non_current_assets_2023 + current_assets_2023
214
+
215
+ # Balance Sheet line items
216
+ line_items = [
217
+ # Equity and Liabilities
218
+ {"label": "Equity and liabilities", "note": "", "value_2024": "", "value_2023": "", "is_header": True},
219
+ {"label": "Shareholders' funds", "note": "", "value_2024": "", "value_2023": "", "is_subheader": True},
220
+ {"label": "Share capital", "note": "2", "value_2024": share_capital_2024, "value_2023": share_capital_2023},
221
+ {"label": "Reserves and surplus", "note": "3", "value_2024": reserves_surplus_2024, "value_2023": reserves_surplus_2023},
222
+ {"label": "", "note": "", "value_2024": shareholders_funds_2024, "value_2023": shareholders_funds_2023, "is_total": True},
223
+
224
+ {"label": "Non-Current liabilities", "note": "", "value_2024": "", "value_2023": "", "is_subheader": True},
225
+ {"label": "Long term borrowings", "note": "4", "value_2024": long_term_borrowings_2024, "value_2023": long_term_borrowings_2023},
226
+ {"label": "Deferred Tax Liability (Net)", "note": "5", "value_2024": deferred_tax_liability_2024, "value_2023": deferred_tax_liability_2023},
227
+ {"label": "", "note": "", "value_2024": non_current_liabilities_2024, "value_2023": non_current_liabilities_2023, "is_total": True},
228
+
229
+ {"label": "Current liabilities", "note": "", "value_2024": "", "value_2023": "", "is_subheader": True},
230
+ {"label": "Trade payables", "note": "6", "value_2024": trade_payables_2024, "value_2023": trade_payables_2023},
231
+ {"label": "Other current liabilities", "note": "7", "value_2024": other_current_liabilities_2024, "value_2023": other_current_liabilities_2023},
232
+ {"label": "Short term provisions", "note": "8", "value_2024": short_term_provisions_2024, "value_2023": short_term_provisions_2023},
233
+ {"label": "", "note": "", "value_2024": current_liabilities_2024, "value_2023": current_liabilities_2023, "is_total": True},
234
+
235
+ {"label": "TOTAL", "note": "", "value_2024": total_equity_liabilities_2024, "value_2023": total_equity_liabilities_2023, "is_grand_total": True},
236
+
237
+ # Assets
238
+ {"label": "", "note": "", "value_2024": "", "value_2023": "", "is_spacer": True},
239
+ {"label": "Assets", "note": "", "value_2024": "", "value_2023": "", "is_header": True},
240
+ {"label": "Non-current assets", "note": "", "value_2024": "", "value_2023": "", "is_subheader": True},
241
+ {"label": "Fixed assets", "note": "9", "value_2024": fixed_assets_2024, "value_2023": fixed_assets_2023},
242
+ {"label": "Long Term Loans and Advances", "note": "10", "value_2024": long_term_loans_advances_2024, "value_2023": long_term_loans_advances_2023},
243
+ {"label": "", "note": "", "value_2024": non_current_assets_2024, "value_2023": non_current_assets_2023, "is_total": True},
244
+
245
+ {"label": "Current assets", "note": "", "value_2024": "", "value_2023": "", "is_subheader": True},
246
+ {"label": "Inventories", "note": "11", "value_2024": inventories_2024, "value_2023": inventories_2023},
247
+ {"label": "Trade receivables", "note": "12", "value_2024": trade_receivables_2024, "value_2023": trade_receivables_2023},
248
+ {"label": "Cash and bank balances", "note": "13", "value_2024": cash_bank_balances_2024, "value_2023": cash_bank_balances_2023},
249
+ {"label": "Short-term loans and advances", "note": "14", "value_2024": short_term_loans_advances_2024, "value_2023": short_term_loans_advances_2023},
250
+ {"label": "Other current assets", "note": "15", "value_2024": other_current_assets_2024, "value_2023": other_current_assets_2023},
251
+ {"label": "", "note": "", "value_2024": current_assets_2024, "value_2023": current_assets_2023, "is_total": True},
252
+
253
+ {"label": "TOTAL", "note": "", "value_2024": total_assets_2024, "value_2023": total_assets_2023, "is_grand_total": True}
254
+ ]
255
+
256
+ # Write line items to Excel
257
+ row = 5
258
+ for item in line_items:
259
+ # Skip spacer rows
260
+ if item.get("is_spacer"):
261
+ row += 1
262
+ continue
263
+
264
+ ws.cell(row=row, column=1).value = item["label"]
265
+ ws.cell(row=row, column=2).value = item["note"]
266
+
267
+ # Format values
268
+ ws.cell(row=row, column=3).value = "" if item["value_2024"] == "" else format_currency(item["value_2024"])
269
+ ws.cell(row=row, column=4).value = "" if item["value_2023"] == "" else format_currency(item["value_2023"])
270
+
271
+ # Apply formatting
272
+ for col in range(1, 5):
273
+ ws.cell(row=row, column=col).border = thin_border
274
+ ws.cell(row=row, column=col).alignment = center_align if col > 1 else left_align
275
+
276
+ # Apply special formatting based on item type
277
+ if item.get("is_header") or item.get("is_grand_total"):
278
+ ws.cell(row=row, column=col).font = bold_font
279
+ elif item.get("is_subheader"):
280
+ ws.cell(row=row, column=col).font = bold_font
281
+ elif item.get("is_total"):
282
+ ws.cell(row=row, column=col).font = bold_font
283
+
284
+ row += 1
285
+
286
+ # Add footer note
287
+ row += 1
288
+ ws.cell(row=row, column=1).value = "The accompanying notes are an integral part of the financial statements."
289
+ ws.cell(row=row, column=1).alignment = left_align
290
+ row += 1
291
+ ws.cell(row=row, column=1).value = "As per my report of even date."
292
+ ws.cell(row=row, column=1).alignment = left_align
293
+ ws.cell(row=row, column=4).value = "For and on behalf of the Board of Directors"
294
+ ws.cell(row=row, column=4).alignment = center_align
295
+
296
+ # Save Excel file with error handling
297
+ try:
298
+ os.makedirs(BALANCE_SHEET_OUTPUT_FOLDER, exist_ok=True)
299
+ output_file = os.path.join(BALANCE_SHEET_OUTPUT_FOLDER, BALANCE_SHEET_OUTPUT_FILE)
300
+ wb.save(output_file)
301
+ logger.info(f"Balance Sheet report generated successfully and saved to {output_file}")
302
+ except PermissionError:
303
+ logger.error(f"PermissionError: Unable to save to {output_file}. Trying alternative location...")
304
+ fallback_file = os.path.join(os.path.expanduser("~"), "Desktop", "balance_sheet_report_fallback.xlsx")
305
+ try:
306
+ wb.save(fallback_file)
307
+ logger.info(f"Balance Sheet report saved to alternative location: {fallback_file}")
308
+ except Exception as e:
309
+ logger.error(f"Failed to save Balance Sheet report: {str(e)}")
310
+ except Exception as e:
311
+ logger.error(f"Error saving Balance Sheet report: {str(e)}")
312
+
313
+ if __name__ == "__main__":
314
+ generate_balance_sheet_report()
app/cashflow.py ADDED
@@ -0,0 +1,362 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import logging
4
+ from typing import Optional, Dict, Any
5
+ from openpyxl import Workbook
6
+ from openpyxl.styles import Font, Border, Side, Alignment
7
+
8
+ # Configure logging
9
+ logging.basicConfig(level=logging.INFO)
10
+ logger = logging.getLogger(__name__)
11
+
12
+ # Configuration (externalized via environment variables)
13
+ CASHFLOW_OUTPUT_FOLDER = os.getenv("CASHFLOW_OUTPUT_FOLDER", "cashflow_excel")
14
+ CASHFLOW_OUTPUT_FILE = os.getenv("CASHFLOW_OUTPUT_FILE", "cashflow_report.xlsx")
15
+
16
+ def load_data(file_path: str) -> Dict[str, Any]:
17
+ """
18
+ Load data from a JSON file with error handling.
19
+ Returns an empty dict if file not found or invalid.
20
+ """
21
+ try:
22
+ with open(file_path, "r", encoding="utf-8") as f:
23
+ return json.load(f)
24
+ except (FileNotFoundError, json.JSONDecodeError) as e:
25
+ logger.warning(f"Error loading file {file_path}: {e}")
26
+ return {}
27
+
28
+ def load_note_data(note_number: str, folder: Optional[str] = None) -> Optional[Dict[str, Any]]:
29
+ """
30
+ Load note data for a specific note_number from notes.json in the specified folder.
31
+ Returns the note dict or None if not found.
32
+ """
33
+ folder = folder or os.path.join(os.path.dirname(os.path.dirname(__file__)), "generated_notes")
34
+ file_path = os.path.join(folder, "notes.json")
35
+ try:
36
+ with open(file_path, "r", encoding="utf-8") as f:
37
+ data = json.load(f)
38
+ notes = data.get("notes", [])
39
+ for note in notes:
40
+ n_num = note.get("note_number") or note.get("metadata", {}).get("note_number")
41
+ if str(n_num) == str(note_number):
42
+ return note
43
+ logger.warning(f"Note {note_number} not found in {file_path}")
44
+ except (FileNotFoundError, json.JSONDecodeError) as e:
45
+ logger.error(f"Error loading note {note_number}: {e}")
46
+ return None
47
+
48
+ def load_trail_balance(folder: str = "output1") -> Dict[str, Any]:
49
+ """
50
+ Load parsed trial balance data from JSON file in the specified folder.
51
+ Returns an empty dict if file not found or invalid.
52
+ """
53
+ return load_data(os.path.join(folder, "parsed_trail_balance.json"))
54
+
55
+ def extract_value_from_notes(note_data: Optional[Dict[str, Any]], key: str, year: str = "2024") -> Optional[float]:
56
+ """
57
+ Extract specific value from note data based on key and year.
58
+ Returns None if not found or invalid.
59
+ """
60
+ if not note_data or "structure" not in note_data:
61
+ return None
62
+ key = key.lower().replace(" ","").replace("-","").replace("/","").replace("&","")
63
+ for category in note_data["structure"]:
64
+ if "subcategories" in category:
65
+ for subcat in category["subcategories"]:
66
+ label = subcat.get("label", "").lower().replace(" ","").replace("-","").replace("/","").replace("&","")
67
+ if key in label:
68
+ if year == "2024" and "value" in subcat:
69
+ try:
70
+ value = float(subcat["value"])
71
+ if value > 100000:
72
+ value = value / 100000
73
+ return value
74
+ except (ValueError, TypeError):
75
+ continue
76
+ elif year == "2023" and "previous_value" in subcat:
77
+ try:
78
+ value = float(subcat["previous_value"])
79
+ if value > 100000:
80
+ value = value / 100000
81
+ return value
82
+ except (ValueError, TypeError):
83
+ continue
84
+ return None
85
+
86
+ def extract_value_from_tb(tb_data: Dict[str, Any], account_name: str, year: str = "2024") -> Optional[float]:
87
+ """
88
+ Extract balance from trial balance for a specific account and year.
89
+ Returns None if not found or invalid.
90
+ """
91
+ if not tb_data or "accounts" not in tb_data.get(year, {}):
92
+ return None
93
+ for account in tb_data[year]["accounts"]:
94
+ if account.get("name", "").lower() == account_name.lower():
95
+ balance = float(account.get("balance", 0))
96
+ return balance / 100000 if balance > 100000 else balance
97
+ return None
98
+
99
+ def calculate_movement(tb_2024: Dict[str, Any], tb_2023: Dict[str, Any], account_name: str) -> Optional[float]:
100
+ """
101
+ Calculate movement (2024 - 2023) from trial balance data.
102
+ Returns None if not found or invalid.
103
+ """
104
+ val_2024 = extract_value_from_tb({"2024": tb_2024}, account_name, "2024")
105
+ val_2023 = extract_value_from_tb({"2023": tb_2023}, account_name, "2023")
106
+ if val_2024 is not None and val_2023 is not None:
107
+ return val_2024 - val_2023
108
+ return None
109
+
110
+ def format_currency(value: Optional[float]) -> str:
111
+ """
112
+ Format currency value for display, handling None.
113
+ """
114
+ if value is None:
115
+ return "-"
116
+ if isinstance(value, (int, float)) and value != 0:
117
+ return f"{value:,.2f}"
118
+ return "-"
119
+
120
+ def generate_cashflow_report() -> None:
121
+ """
122
+ Generate Cash Flow Statement report in Excel format using notes and trial balance data.
123
+ Output location is configurable via environment variables.
124
+ """
125
+ wb = Workbook()
126
+ ws = wb.active
127
+ ws.title = "Cash Flow Statement"
128
+
129
+ # Define styles
130
+ bold_font = Font(bold=True)
131
+ thin_border = Border(left=Side(style="thin"), right=Side(style="thin"),
132
+ top=Side(style="thin"), bottom=Side(style="thin"))
133
+ center_align = Alignment(horizontal="center")
134
+ left_align = Alignment(horizontal="left")
135
+ right_align = Alignment(horizontal="right")
136
+
137
+ # Set column widths
138
+ ws.column_dimensions["A"].width = 50
139
+ ws.column_dimensions["B"].width = 20
140
+ ws.column_dimensions["C"].width = 20
141
+
142
+ # Header
143
+ ws["A1"] = "Statement of Cash Flows for the year ended March 31, 2024"
144
+ ws["A1"].font = bold_font
145
+ ws.merge_cells("A1:C1")
146
+ ws["A1"].alignment = center_align
147
+
148
+ # Units
149
+ ws["A2"] = "In Lakhs"
150
+ ws.merge_cells("A2:C2")
151
+ ws["A2"].alignment = right_align
152
+
153
+ # Table headers
154
+ headers = ["Particulars", "March 31, 2024", "March 31, 2023"]
155
+ for col, header in enumerate(headers, 1):
156
+ cell = ws.cell(row=4, column=col)
157
+ cell.value = header
158
+ cell.font = bold_font
159
+ cell.border = thin_border
160
+ cell.alignment = center_align if col > 1 else left_align
161
+
162
+ # Load data
163
+ note_4 = load_note_data("4") # Cash Flow Statement
164
+ note_13 = load_note_data("13") # Cash and Cash Equivalents
165
+ note_6 = load_note_data("6") # Trade Payables
166
+ note_7 = load_note_data("7") # Other Current Liabilities
167
+ note_8 = load_note_data("8") # Provisions
168
+ note_9 = load_note_data("9") # Fixed Assets
169
+ tb_data = load_trail_balance()
170
+ tb_2024 = tb_data.get("2024", {})
171
+ tb_2023 = tb_data.get("2023", {})
172
+
173
+ # Extract or calculate values
174
+ values = {
175
+ "profit_before_tax": {"2024": extract_value_from_notes(note_4, "profit before taxation", "2024") or extract_value_from_tb(tb_2024, "Profit before Taxation"),
176
+ "2023": extract_value_from_notes(note_4, "profit before taxation", "2023") or extract_value_from_tb(tb_2023, "Profit before Taxation")},
177
+ "depreciation_amortization": {"2024": extract_value_from_notes(note_4, "depreciation and amortisation expense", "2024") or extract_value_from_tb(tb_2024, "Depreciation and Amortisation Expense"),
178
+ "2023": extract_value_from_notes(note_4, "depreciation and amortisation expense", "2023") or extract_value_from_tb(tb_2023, "Depreciation and Amortisation Expense")},
179
+ "interest_income": {"2024": extract_value_from_notes(note_4, "interest income", "2024") or extract_value_from_tb(tb_2024, "Interest Income"),
180
+ "2023": extract_value_from_notes(note_4, "interest income", "2023") or extract_value_from_tb(tb_2023, "Interest Income")},
181
+ "trade_receivables": {"2024": calculate_movement(tb_2024, tb_2023, "Trade Receivables") or extract_value_from_notes(note_4, "increase/decrease in trade receivables", "2024"),
182
+ "2023": calculate_movement(tb_2023, tb_2023, "Trade Receivables") or extract_value_from_notes(note_4, "increase/decrease in trade receivables", "2023")},
183
+ "inventories": {"2024": calculate_movement(tb_2024, tb_2023, "Inventories") or extract_value_from_notes(note_4, "increase/decrease in inventories", "2024"),
184
+ "2023": calculate_movement(tb_2023, tb_2023, "Inventories") or extract_value_from_notes(note_4, "increase/decrease in inventories", "2023")},
185
+ "other_current_assets": {"2024": calculate_movement(tb_2024, tb_2023, "Other Current Assets") or extract_value_from_notes(note_4, "increase/decrease in other current assets", "2024"),
186
+ "2023": calculate_movement(tb_2023, tb_2023, "Other Current Assets") or extract_value_from_notes(note_4, "increase/decrease in other current assets", "2023")},
187
+ "short_term_loans": {"2024": calculate_movement(tb_2024, tb_2023, "Short Term Loans & Advances") or extract_value_from_notes(note_4, "increase/decrease in short term loans & advances", "2024"),
188
+ "2023": calculate_movement(tb_2023, tb_2023, "Short Term Loans & Advances") or extract_value_from_notes(note_4, "increase/decrease in short term loans & advances", "2023")},
189
+ "capital_work_progress": {"2024": calculate_movement(tb_2024, tb_2023, "Capital Work in Progress") or extract_value_from_notes(note_4, "increase/decrease in capital work in progress", "2024"),
190
+ "2023": calculate_movement(tb_2023, tb_2023, "Capital Work in Progress") or extract_value_from_notes(note_4, "increase/decrease in capital work in progress", "2023")},
191
+ "long_term_loans": {"2024": calculate_movement(tb_2024, tb_2023, "Long Term Loans & Advances") or extract_value_from_notes(note_4, "increase/decrease in long term loans & advances", "2024"),
192
+ "2023": calculate_movement(tb_2023, tb_2023, "Long Term Loans & Advances") or extract_value_from_notes(note_4, "increase/decrease in long term loans & advances", "2023")},
193
+ "short_term_provisions": {"2024": calculate_movement(tb_2024, tb_2023, "Short Term Provisions") or extract_value_from_notes(note_8, "short term provisions", "2024"),
194
+ "2023": calculate_movement(tb_2023, tb_2023, "Short Term Provisions") or extract_value_from_notes(note_8, "short term provisions", "2023")},
195
+ "trade_payables": {"2024": calculate_movement(tb_2024, tb_2023, "Trade Payables") or extract_value_from_notes(note_6, "trade payables", "2024"),
196
+ "2023": calculate_movement(tb_2023, tb_2023, "Trade Payables") or extract_value_from_notes(note_6, "trade payables", "2023")},
197
+ "other_current_liabilities": {"2024": calculate_movement(tb_2024, tb_2023, "Other Current Liabilities") or extract_value_from_notes(note_7, "other current liabilities", "2024"),
198
+ "2023": calculate_movement(tb_2023, tb_2023, "Other Current Liabilities") or extract_value_from_notes(note_7, "other current liabilities", "2023")},
199
+ "purchase_assets": {"2024": extract_value_from_notes(note_9, "purchase of assets", "2024") or calculate_movement(tb_2024, tb_2023, "Fixed Assets"),
200
+ "2023": extract_value_from_notes(note_9, "purchase of assets", "2023") or calculate_movement(tb_2023, tb_2023, "Fixed Assets")},
201
+ "sale_assets": {"2024": extract_value_from_notes(note_9, "sale of assets", "2024"),
202
+ "2023": extract_value_from_notes(note_9, "sale of assets", "2023")},
203
+ "dividend_paid": {"2024": extract_value_from_notes(note_4, "dividend paid", "2024") or extract_value_from_tb(tb_2024, "Dividend Paid"),
204
+ "2023": extract_value_from_notes(note_4, "dividend paid", "2023") or extract_value_from_tb(tb_2023, "Dividend Paid")},
205
+ "long_term_borrowings": {"2024": calculate_movement(tb_2024, tb_2023, "Long Term Borrowings") or extract_value_from_notes(note_4, "long term borrowings", "2024"),
206
+ "2023": calculate_movement(tb_2023, tb_2023, "Long Term Borrowings") or extract_value_from_notes(note_4, "long term borrowings", "2023")},
207
+ "cash_equivalents_opening": {"2024": extract_value_from_notes(note_13, "cash and cash equivalents at the beginning", "2024") or extract_value_from_tb(tb_2023, "Cash and Cash Equivalents"),
208
+ "2023": extract_value_from_notes(note_13, "cash and cash equivalents at the beginning", "2023") or extract_value_from_tb(tb_2023, "Cash and Cash Equivalents")},
209
+ "cash_equivalents_closing": {"2024": extract_value_from_notes(note_13, "cash and cash equivalents at the end", "2024") or extract_value_from_tb(tb_2024, "Cash and Cash Equivalents"),
210
+ "2023": extract_value_from_notes(note_13, "cash and cash equivalents at the end", "2023") or extract_value_from_tb(tb_2023, "Cash and Cash Equivalents")},
211
+ "cash_on_hand": {"2024": extract_value_from_notes(note_13, "cash on hand", "2024") or extract_value_from_tb(tb_2024, "Cash on Hand"),
212
+ "2023": extract_value_from_notes(note_13, "cash on hand", "2023") or extract_value_from_tb(tb_2023, "Cash on Hand")},
213
+ "bank_current_accounts": {"2024": extract_value_from_notes(note_13, "with banks in current accounts", "2024") or extract_value_from_tb(tb_2024, "Bank Current Accounts"),
214
+ "2023": extract_value_from_notes(note_13, "with banks in current accounts", "2023") or extract_value_from_tb(tb_2023, "Bank Current Accounts")},
215
+ "bank_fixed_deposits": {"2024": extract_value_from_notes(note_13, "with banks in fixed deposits", "2024") or extract_value_from_tb(tb_2024, "Bank Fixed Deposits"),
216
+ "2023": extract_value_from_notes(note_13, "with banks in fixed deposits", "2023") or extract_value_from_tb(tb_2023, "Bank Fixed Deposits")},
217
+ }
218
+
219
+ # Calculate derived values
220
+ operating_profit_2024 = (values["profit_before_tax"]["2024"] or 0) + (values["depreciation_amortization"]["2024"] or 0) - (values["interest_income"]["2024"] or 0)
221
+ operating_profit_2023 = (values["profit_before_tax"]["2023"] or 0) + (values["depreciation_amortization"]["2023"] or 0) - (values["interest_income"]["2023"] or 0)
222
+ cash_used_operations_2024 = operating_profit_2024 + sum(v["2024"] for k, v in values.items() if k.startswith("trade_") or k.startswith("inventories") or k.startswith("other_current_") or k.startswith("short_term_") or k.startswith("capital_work_") or k.startswith("long_term_") or k.startswith("short_term_provisions") or k.startswith("trade_payables") or k.startswith("other_current_liabilities") if v["2024"] is not None)
223
+ cash_used_operations_2023 = operating_profit_2023 + sum(v["2023"] for k, v in values.items() if k.startswith("trade_") or k.startswith("inventories") or k.startswith("other_current_") or k.startswith("short_term_") or k.startswith("capital_work_") or k.startswith("long_term_") or k.startswith("short_term_provisions") or k.startswith("trade_payables") or k.startswith("other_current_liabilities") if v["2023"] is not None)
224
+ direct_taxes_paid_2024 = extract_value_from_notes(note_8, "provision for taxation", "2024")
225
+ direct_taxes_paid_2023 = extract_value_from_notes(note_8, "provision for taxation", "2023")
226
+ net_cash_operating_2024 = cash_used_operations_2024 - (direct_taxes_paid_2024 or 0)
227
+ net_cash_operating_2023 = cash_used_operations_2023 - (direct_taxes_paid_2023 or 0)
228
+ net_cash_investing_2024 = -(values["purchase_assets"]["2024"] or 0) + (values["sale_assets"]["2024"] or 0) + (values["interest_income"]["2024"] or 0)
229
+ net_cash_investing_2023 = -(values["purchase_assets"]["2023"] or 0) + (values["sale_assets"]["2023"] or 0) + (values["interest_income"]["2023"] or 0)
230
+ net_cash_financing_2024 = (values["long_term_borrowings"]["2024"] or 0) - (values["dividend_paid"]["2024"] or 0)
231
+ net_cash_financing_2023 = (values["long_term_borrowings"]["2023"] or 0) - (values["dividend_paid"]["2023"] or 0)
232
+ total_cash_flow_2024 = net_cash_operating_2024 + net_cash_investing_2024 + net_cash_financing_2024
233
+ total_cash_flow_2023 = net_cash_operating_2023 + net_cash_investing_2023 + net_cash_financing_2023
234
+
235
+ # Debug logs
236
+ logger.debug(f"Total Cash Flow 2024: {format_currency(total_cash_flow_2024)}")
237
+ logger.debug(f"Total Cash Flow 2023: {format_currency(total_cash_flow_2023)}")
238
+ logger.debug(f"Net Cash from Operating Activities 2024: {format_currency(net_cash_operating_2024)}")
239
+ logger.debug(f"Net Cash from Operating Activities 2023: {format_currency(net_cash_operating_2023)}")
240
+
241
+ # Prepare line items for Excel
242
+ line_items = [
243
+ {"label": "Cash flow from operating activities", "value_2024": "", "value_2023": "", "is_header": True},
244
+ {"label": "Profit before taxation", "value_2024": values["profit_before_tax"]["2024"], "value_2023": values["profit_before_tax"]["2023"]},
245
+ {"label": "Adjustment for:", "value_2024": "", "value_2023": "", "is_subheader": True},
246
+ {"label": "Add: Depreciation and Amortisation Expense", "value_2024": values["depreciation_amortization"]["2024"], "value_2023": values["depreciation_amortization"]["2023"]},
247
+ {"label": "Less: Interest income", "value_2024": -values["interest_income"]["2024"] if values["interest_income"]["2024"] is not None else None, "value_2023": -values["interest_income"]["2023"] if values["interest_income"]["2023"] is not None else None},
248
+ {"label": "Operating profit before working capital changes", "value_2024": operating_profit_2024, "value_2023": operating_profit_2023, "is_total": True},
249
+ {"label": "Movements in working capital:", "value_2024": "", "value_2023": "", "is_subheader": True},
250
+ {"label": "(Increase)/Decrease in Trade Receivables", "value_2024": values["trade_receivables"]["2024"], "value_2023": values["trade_receivables"]["2023"]},
251
+ {"label": "(Increase)/Decrease in Inventories", "value_2024": values["inventories"]["2024"], "value_2023": values["inventories"]["2023"]},
252
+ {"label": "(Increase)/Decrease in Other Current Assets", "value_2024": values["other_current_assets"]["2024"], "value_2023": values["other_current_assets"]["2023"]},
253
+ {"label": "(Increase)/Decrease in Short Term Loans & Advances", "value_2024": values["short_term_loans"]["2024"], "value_2023": values["short_term_loans"]["2023"]},
254
+ {"label": "(Increase)/Decrease in Capital Work in Progress", "value_2024": values["capital_work_progress"]["2024"], "value_2023": values["capital_work_progress"]["2023"]},
255
+ {"label": "(Increase)/Decrease in Long Term Loans & Advances", "value_2024": values["long_term_loans"]["2024"], "value_2023": values["long_term_loans"]["2023"]},
256
+ {"label": "Increase/(Decrease) in Short Term Provisions", "value_2024": values["short_term_provisions"]["2024"], "value_2023": values["short_term_provisions"]["2023"]},
257
+ {"label": "Increase/(Decrease) in Trade Payables", "value_2024": values["trade_payables"]["2024"], "value_2023": values["trade_payables"]["2023"]},
258
+ {"label": "Increase/(Decrease) in Other Current Liabilities", "value_2024": values["other_current_liabilities"]["2024"], "value_2023": values["other_current_liabilities"]["2023"]},
259
+ {"label": "Cash used in operations", "value_2024": cash_used_operations_2024, "value_2023": cash_used_operations_2023, "is_total": True},
260
+ {"label": "Less: Direct taxes paid (net of refunds)", "value_2024": direct_taxes_paid_2024, "value_2023": direct_taxes_paid_2023},
261
+ {"label": "Net cash flow used in operating activities", "value_2024": net_cash_operating_2024, "value_2023": net_cash_operating_2023, "is_total": True},
262
+ {"label": "Cash flows from investing activities", "value_2024": "", "value_2023": "", "is_header": True},
263
+ {"label": "Purchase of Assets", "value_2024": -values["purchase_assets"]["2024"] if values["purchase_assets"]["2024"] is not None else None, "value_2023": -values["purchase_assets"]["2023"] if values["purchase_assets"]["2023"] is not None else None},
264
+ {"label": "Sale of Assets", "value_2024": values["sale_assets"]["2024"], "value_2023": values["sale_assets"]["2023"]},
265
+ {"label": "Interest income", "value_2024": values["interest_income"]["2024"], "value_2023": values["interest_income"]["2023"]},
266
+ {"label": "Net cash flow used in investing activities", "value_2024": net_cash_investing_2024, "value_2023": net_cash_investing_2023, "is_total": True},
267
+ {"label": "Cash flows from financing activities", "value_2024": "", "value_2023": "", "is_header": True},
268
+ {"label": "Dividend paid", "value_2024": -values["dividend_paid"]["2024"] if values["dividend_paid"]["2024"] is not None else None, "value_2023": -values["dividend_paid"]["2023"] if values["dividend_paid"]["2023"] is not None else None},
269
+ {"label": "Long Term Borrowings", "value_2024": values["long_term_borrowings"]["2024"], "value_2023": values["long_term_borrowings"]["2023"]},
270
+ {"label": "Net cash generated from financing activities", "value_2024": net_cash_financing_2024, "value_2023": net_cash_financing_2023, "is_total": True},
271
+ {"label": "Net increase/(decrease) in cash and cash equivalents", "value_2024": total_cash_flow_2024, "value_2023": total_cash_flow_2023, "is_total": True},
272
+ {"label": "Cash and cash equivalents at the beginning of the year", "value_2024": values["cash_equivalents_opening"]["2024"], "value_2023": values["cash_equivalents_opening"]["2023"]},
273
+ {"label": "Cash and cash equivalents at the end of the year", "value_2024": values["cash_equivalents_closing"]["2024"], "value_2023": values["cash_equivalents_closing"]["2023"], "is_grand_total": True},
274
+ {"label": "Components of cash and cash equivalents", "value_2024": "", "value_2023": "", "is_header": True},
275
+ {"label": "Cash on hand", "value_2024": values["cash_on_hand"]["2024"], "value_2023": values["cash_on_hand"]["2023"]},
276
+ {"label": "With banks in Current Accounts", "value_2024": values["bank_current_accounts"]["2024"], "value_2023": values["bank_current_accounts"]["2023"]},
277
+ {"label": "With banks in Fixed Deposits", "value_2024": values["bank_fixed_deposits"]["2024"], "value_2023": values["bank_fixed_deposits"]["2023"]},
278
+ {"label": "Total cash and cash equivalents (Refer note 13)", "value_2024": values["cash_equivalents_closing"]["2024"], "value_2023": values["cash_equivalents_closing"]["2023"], "is_total": True},
279
+ ]
280
+
281
+ # Write line items to Excel
282
+ row = 5
283
+ for item in line_items:
284
+ ws.cell(row=row, column=1).value = item["label"]
285
+ ws.cell(row=row, column=2).value = format_currency(item["value_2024"])
286
+ ws.cell(row=row, column=3).value = format_currency(item["value_2023"])
287
+ # Apply formatting
288
+ for col in range(1, 4):
289
+ ws.cell(row=row, column=col).border = thin_border
290
+ ws.cell(row=row, column=col).alignment = center_align if col > 1 else left_align
291
+ if item.get("is_header") or item.get("is_grand_total") or item.get("is_subheader") or item.get("is_total"):
292
+ ws.cell(row=row, column=col).font = bold_font
293
+ row += 1
294
+
295
+ # Add notes and footer
296
+ row += 1
297
+ ws.cell(row=row, column=1).value = "Notes:"
298
+ ws.cell(row=row, column=1).font = bold_font
299
+ ws.cell(row=row, column=1).alignment = left_align
300
+ row += 1
301
+ ws.cell(row=row, column=1).value = "1. The Cash Flow statement is prepared under 'indirect method' as set out in the Indian Accounting Standard - 7 on Cash Flow Statements. Cash and cash equivalents in the Cash Flow Statement comprise cash at bank and in hand and deposits with bank."
302
+ ws.cell(row=row, column=1).alignment = left_align
303
+ ws.merge_cells(f"A{row}:C{row}")
304
+ row += 1
305
+ ws.cell(row=row, column=1).value = "2. Previous year's figures have been regrouped, wherever necessary."
306
+ ws.cell(row=row, column=1).alignment = left_align
307
+ ws.merge_cells(f"A{row}:C{row}")
308
+ row += 1
309
+ ws.cell(row=row, column=1).value = "3. The accompanying notes form an integral part of the Financial Statements"
310
+ ws.cell(row=row, column=1).alignment = left_align
311
+ ws.merge_cells(f"A{row}:C{row}")
312
+ row += 1
313
+ ws.cell(row=row, column=1).value = "As per my report of even date."
314
+ ws.cell(row=row, column=1).alignment = left_align
315
+ ws.cell(row=row, column=3).value = "For and on behalf of the Board of Directors"
316
+ ws.cell(row=row, column=3).alignment = center_align
317
+ row += 2
318
+ ws.cell(row=row, column=1).value = "For M/s Siva Parvathi & Associates"
319
+ ws.cell(row=row, column=1).alignment = left_align
320
+ ws.cell(row=row+1, column=1).value = "ICAI Firm registration number: 020872S"
321
+ ws.cell(row=row+1, column=1).alignment = left_align
322
+ ws.cell(row=row+2, column=1).value = "Chartered Accountants"
323
+ ws.cell(row=row+2, column=1).alignment = left_align
324
+ row += 4
325
+ ws.cell(row=row, column=1).value = "S. Siva Parvathi"
326
+ ws.cell(row=row, column=1).alignment = left_align
327
+ ws.cell(row=row, column=3).value = "Director"
328
+ ws.cell(row=row, column=3).alignment = center_align
329
+ ws.cell(row=row, column=4).value = "Director"
330
+ ws.cell(row=row, column=4).alignment = center_align
331
+ row += 1
332
+ ws.cell(row=row, column=1).value = "Proprietor"
333
+ ws.cell(row=row, column=1).alignment = left_align
334
+ row += 1
335
+ ws.cell(row=row, column=1).value = "UDIN:24226087BKEECZ1200"
336
+ ws.cell(row=row, column=1).alignment = left_align
337
+ row += 1
338
+ ws.cell(row=row, column=1).value = "Place: Hyderabad"
339
+ ws.cell(row=row, column=1).alignment = left_align
340
+ row += 1
341
+ ws.cell(row=row, column=1).value = "Date: 04/09/2024"
342
+ ws.cell(row=row, column=1).alignment = left_align
343
+
344
+ # Save Excel file with error handling
345
+ try:
346
+ os.makedirs(CASHFLOW_OUTPUT_FOLDER, exist_ok=True)
347
+ output_file = os.path.join(CASHFLOW_OUTPUT_FOLDER, CASHFLOW_OUTPUT_FILE)
348
+ wb.save(output_file)
349
+ logger.info(f"Cash Flow Statement report generated successfully and saved to {output_file}")
350
+ except PermissionError:
351
+ logger.error(f"PermissionError: Unable to save to {output_file}. Trying alternative location...")
352
+ fallback_file = os.path.join(os.path.expanduser("~"), "Desktop", "cash_flow_report_fallback.xlsx")
353
+ try:
354
+ wb.save(fallback_file)
355
+ logger.info(f"Cash Flow Statement report saved to alternative location: {fallback_file}")
356
+ except Exception as e:
357
+ logger.error(f"Failed to save Cash Flow Statement report: {str(e)}")
358
+ except Exception as e:
359
+ logger.error(f"Error saving Cash Flow Statement report: {str(e)}")
360
+
361
+ if __name__ == "__main__":
362
+ generate_cashflow_report()
app/extract.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import json
3
+ import os
4
+ import re
5
+ import glob
6
+ import logging
7
+ from pathlib import Path
8
+ from typing import Any, Dict, List, Tuple, Optional
9
+ import requests
10
+ from dotenv import load_dotenv
11
+ from pydantic import BaseModel, Field, ValidationError
12
+ from pydantic_settings import BaseSettings
13
+
14
+ # Configure logging
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ class Settings(BaseSettings):
19
+ """
20
+ Application settings loaded from environment variables or .env file.
21
+ """
22
+ MAPPING_FILE: str = Field(default="mapping1.json", env="MAPPING_FILE")
23
+ RULES_FILE: str = Field(default="rules1.json", env="RULES_FILE")
24
+ OUTPUT_DIR: str = Field(default="output1", env="OUTPUT_DIR")
25
+
26
+ settings = Settings()
27
+
28
+ class TrialBalanceRecord(BaseModel):
29
+ """
30
+ Pydantic model for a trial balance record.
31
+ """
32
+ account_name: str
33
+ group: str
34
+ amount: float
35
+ mapped_by: str
36
+ source_file: str
37
+
38
+ def load_mappings(
39
+ mapping_file: str = settings.MAPPING_FILE,
40
+ rules_file: str = settings.RULES_FILE
41
+ ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
42
+ """
43
+ Loads exact mappings and keyword rules from JSON files.
44
+ Returns two dictionaries: exact_mappings, keyword_rules.
45
+ """
46
+ exact_mappings = {}
47
+ keyword_rules = {}
48
+ try:
49
+ if Path(mapping_file).exists():
50
+ with open(mapping_file, 'r', encoding='utf-8') as f:
51
+ exact_mappings = json.load(f)
52
+ if Path(rules_file).exists():
53
+ with open(rules_file, 'r', encoding='utf-8') as f:
54
+ keyword_rules = json.load(f)
55
+ except Exception as e:
56
+ logger.error(f"Error loading mappings: {e}")
57
+ return exact_mappings, keyword_rules
58
+
59
+ def get_smart_rules() -> Dict[str, List[str]]:
60
+ """
61
+ Returns a dictionary of smart rules for account classification.
62
+ """
63
+ return {
64
+ 'Cash and Cash Equivalents': [r'\b(cash|bank|petty|till|vault|fd|fixed\s*deposit)\b'],
65
+ 'Trade Receivables': [r'\b(debtor|receivable|customer|outstanding.*debtor)\b'],
66
+ 'Trade Payables': [r'\b(creditor|payable|supplier|vendor|outstanding.*creditor)\b'],
67
+ 'Inventories': [r'\b(stock|inventory|goods|raw\s*material|wip|work.*progress)\b'],
68
+ 'Property, Plant and Equipment': [r'\b(land|building|plant|machinery|equipment|furniture|vehicle|depreciation)\b'],
69
+ 'Equity Share Capital': [r'\b(capital|share.*capital|paid.*up|equity)\b'],
70
+ 'Revenue from Operations': [r'\b(sales?|revenue|turnover|service.*income)\b'],
71
+ 'Employee Benefits Expense': [r'\b(salary|wages?|staff|employee|pf|provident|gratuity)\b'],
72
+ 'Finance Costs': [r'\b(interest|finance.*cost|bank.*charge)\b'],
73
+ 'Other Current Liabilities': [r'\b(tds|gst|vat|tax.*payable|service.*tax)\b']
74
+ }
75
+
76
+ def parse_amount(amount_str: Any) -> float:
77
+ """
78
+ Parses an amount string and returns a float.
79
+ Returns 0.0 if invalid.
80
+ """
81
+ if pd.isna(amount_str) or amount_str == '':
82
+ return 0.0
83
+ amount_str = str(amount_str).strip()
84
+ is_credit = amount_str.lower().endswith('cr')
85
+ amount_str = re.sub(r'[^\d\.\-\+]', '', amount_str)
86
+ if not amount_str or amount_str in ['-', '+']:
87
+ return 0.0
88
+ try:
89
+ amount = float(amount_str)
90
+ if is_credit and amount > 0:
91
+ amount = -amount
92
+ return amount
93
+ except ValueError:
94
+ return 0.0
95
+
96
+ def classify_account(
97
+ account_name: str,
98
+ exact_mappings: Dict[str, Any],
99
+ keyword_rules: Dict[str, Any],
100
+ smart_rules: Dict[str, List[str]],
101
+ llm_model: str = "qwen/qwen3-30b-a3b"
102
+ ) -> Tuple[str, str]:
103
+ """
104
+ Classifies an account name into a category using mappings, rules, and smart patterns.
105
+ Returns (group, mapped_by).
106
+ """
107
+ account_name_clean = account_name.strip().lower()
108
+ if account_name in exact_mappings:
109
+ return exact_mappings[account_name], "mapping.json"
110
+ for mapped_name, group in exact_mappings.items():
111
+ if mapped_name.lower() == account_name_clean:
112
+ return group, "mapping.json"
113
+ for group, keywords in keyword_rules.items():
114
+ for keyword in keywords:
115
+ if keyword.lower() in account_name_clean.split():
116
+ return group, "rules.json"
117
+ for group, patterns in smart_rules.items():
118
+ for pattern in patterns:
119
+ if re.search(pattern, account_name_clean):
120
+ return group, "smart_rules"
121
+ # LLM Fallback (commented out, enable if needed)
122
+ # load_dotenv()
123
+ # api_key = os.getenv("OPENROUTER_API_KEY")
124
+ # if api_key:
125
+ # try:
126
+ # response = requests.post(
127
+ # "https://openrouter.ai/api/v1/chat/completions",
128
+ # headers={
129
+ # "Authorization": f"Bearer {api_key}",
130
+ # "Content-Type": "application/json"
131
+ # },
132
+ # json={
133
+ # "model": "mistralai/mixtral-8x7b-instruct",
134
+ # "messages": [
135
+ # {
136
+ # "role": "system",
137
+ # "content": "You are a financial expert. Classify the following account name into one of these categories: Equity, Non-Current Liability, Current Liability, Non-Current Asset, Current Asset, Revenue from Operations, Cost of Materials Consumed, Direct Expenses, Other Income, Other Expenses, Employee Benefits Expense, Finance Cost, Accumulated Depreciation, Deferred Tax Liability, Profit and Loss Account. Respond only with the category name."
138
+ # },
139
+ # {
140
+ # "role": "user",
141
+ # "content": account_name
142
+ # }
143
+ # ]
144
+ # },
145
+ # timeout=10
146
+ # )
147
+ # response.raise_for_status()
148
+ # llm_response = response.json()
149
+ # llm_suggestion = llm_response['choices'][0]['message']['content'].strip()
150
+ # return llm_suggestion, "llm_fallback"
151
+ # except requests.exceptions.RequestException as e:
152
+ # logger.error(f"LLM fallback failed: {e}")
153
+ # except Exception as e:
154
+ # logger.error(f"Unexpected error in LLM fallback: {e}")
155
+ return 'Unmapped', 'Unmapped'
156
+
157
+ def extract_trial_balance_data(
158
+ file_path: str,
159
+ sheet_name: int = 0,
160
+ header_row: int = 0
161
+ ) -> List[TrialBalanceRecord]:
162
+ """
163
+ Extracts trial balance data from an Excel file.
164
+ Returns a list of validated TrialBalanceRecord objects.
165
+ """
166
+ try:
167
+ df_raw = pd.read_excel(file_path, sheet_name=sheet_name, header=header_row)
168
+ except Exception as e:
169
+ logger.error(f"Error reading Excel file: {e}")
170
+ return []
171
+ exact_mappings, keyword_rules = load_mappings()
172
+ smart_rules = get_smart_rules()
173
+ structured_data: List[TrialBalanceRecord] = []
174
+ source_file = Path(file_path).name
175
+ for idx, row in df_raw.iterrows():
176
+ account_name = row.iloc[0] if len(row) > 0 else None
177
+ if pd.isna(account_name) or str(account_name).strip() == '':
178
+ continue
179
+ account_name = str(account_name).strip()
180
+ if len(account_name) <= 2 or account_name.replace('.', '').replace('-', '').isdigit():
181
+ continue
182
+ amount = 0.0
183
+ if len(row) > 3 and not pd.isna(row.iloc[3]):
184
+ amount = parse_amount(row.iloc[3])
185
+ elif len(row) > 2:
186
+ debit = parse_amount(row.iloc[1]) if len(row) > 1 else 0.0
187
+ credit = parse_amount(row.iloc[2]) if len(row) > 2 else 0.0
188
+ amount = debit - credit
189
+ group, mapped_by = classify_account(account_name, exact_mappings, keyword_rules, smart_rules)
190
+ try:
191
+ record = TrialBalanceRecord(
192
+ account_name=account_name,
193
+ group=group,
194
+ amount=amount,
195
+ mapped_by=mapped_by,
196
+ source_file=source_file
197
+ )
198
+ structured_data.append(record)
199
+ except ValidationError as ve:
200
+ logger.error(f"Validation error for record {account_name}: {ve}")
201
+ return structured_data
202
+
203
+ def analyze_and_save_results(structured_data: List[TrialBalanceRecord], output_file: str) -> List[TrialBalanceRecord]:
204
+ """
205
+ Analyzes and saves the extracted data to a JSON file.
206
+ Returns the structured data.
207
+ """
208
+ total_records = len(structured_data)
209
+ mapped_records = [r for r in structured_data if r.mapped_by != 'Unmapped']
210
+ unmapped_records = [r for r in structured_data if r.mapped_by == 'Unmapped']
211
+ success_rate = (len(mapped_records) / total_records * 100) if total_records > 0 else 0
212
+ total_amount = sum(abs(r.amount) for r in mapped_records)
213
+ mapping_methods: Dict[str, int] = {}
214
+ for record in mapped_records:
215
+ method = record.mapped_by
216
+ mapping_methods[method] = mapping_methods.get(method, 0) + 1
217
+ account_groups: Dict[str, Dict[str, Any]] = {}
218
+ for record in mapped_records:
219
+ group = record.group
220
+ if group not in account_groups:
221
+ account_groups[group] = {'count': 0, 'total_amount': 0}
222
+ account_groups[group]['count'] += 1
223
+ account_groups[group]['total_amount'] += abs(record.amount)
224
+ os.makedirs(settings.OUTPUT_DIR, exist_ok=True)
225
+ try:
226
+ with open(output_file, 'w', encoding='utf-8') as f:
227
+ json.dump([r.dict() for r in structured_data], f, indent=2, ensure_ascii=False)
228
+ except Exception as e:
229
+ logger.error(f"Error saving results to JSON: {e}")
230
+ return structured_data
231
+
232
+ def find_file(filename: str) -> Optional[str]:
233
+ """
234
+ Finds a file with a given name in the current directory and the input directory.
235
+ Returns the file path if found, else None.
236
+ """
237
+ possible_paths = [
238
+ filename,
239
+ f"input/{filename}",
240
+ f"./{filename}",
241
+ ]
242
+ for path in possible_paths:
243
+ if Path(path).exists():
244
+ return path
245
+ filename_lower = filename.lower()
246
+ all_files = glob.glob("*.xlsx") + glob.glob("input/*.xlsx")
247
+ for file_path in all_files:
248
+ file_name_lower = Path(file_path).name.lower()
249
+ if filename_lower in file_name_lower:
250
+ return file_path
251
+ return None
app/json_xlsx.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import logging
4
+ from typing import Any, Dict, List, Optional
5
+ from pydantic import BaseModel, ValidationError
6
+ from pydantic_settings import BaseSettings
7
+ import pandas as pd
8
+ from openpyxl import Workbook
9
+ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
10
+ from openpyxl.utils import get_column_letter
11
+
12
+ # Configure logging
13
+ logging.basicConfig(level=logging.INFO)
14
+ logger = logging.getLogger(__name__)
15
+
16
+ class Settings(BaseSettings):
17
+ """Application settings loaded from environment variables or .env file."""
18
+ input_file: str = "output2/notes_output.json"
19
+ output_folder: str = "output3"
20
+ output_file: str = "final_notes_output.xlsx"
21
+
22
+ settings = Settings()
23
+
24
+ class BreakdownItem(BaseModel):
25
+ description: str
26
+ amount: float
27
+ amount_lakhs: Optional[float] = None
28
+
29
+ class MatchedAccount(BaseModel):
30
+ account: str
31
+ amount: float
32
+ amount_lakhs: Optional[float] = None
33
+ group: Optional[str] = None
34
+
35
+ class NoteData(BaseModel):
36
+ note_number: Optional[str] = None
37
+ note_title: Optional[str] = None
38
+ full_title: Optional[str] = None
39
+ table_data: Optional[List[Dict[str, Any]]] = []
40
+ breakdown: Optional[Dict[str, BreakdownItem]] = {}
41
+ matched_accounts: Optional[List[MatchedAccount]] = []
42
+ total_amount: Optional[float] = None
43
+ total_amount_lakhs: Optional[float] = None
44
+ matched_accounts_count: Optional[int] = None
45
+ comparative_data: Optional[Dict[str, Any]] = {}
46
+ notes_and_disclosures: Optional[List[str]] = []
47
+ markdown_content: Optional[str] = ""
48
+
49
+ def create_output_folder(folder_path: str) -> None:
50
+ """Create output folder if it doesn't exist."""
51
+ if not os.path.exists(folder_path):
52
+ os.makedirs(folder_path)
53
+ logger.info(f"Created folder: {folder_path}")
54
+
55
+ def read_json_file(file_path: str) -> Optional[Dict[str, Any]]:
56
+ """Read and parse JSON file."""
57
+ try:
58
+ with open(file_path, 'r', encoding='utf-8') as file:
59
+ data = json.load(file)
60
+ logger.info(f"Successfully read JSON file: {file_path}")
61
+ return data
62
+ except FileNotFoundError:
63
+ logger.error(f"File '{file_path}' not found.")
64
+ return None
65
+ except json.JSONDecodeError as e:
66
+ logger.error(f"Invalid JSON format in '{file_path}': {e}")
67
+ return None
68
+ except Exception as e:
69
+ logger.error(f"Error reading file '{file_path}': {e}")
70
+ return None
71
+
72
+ def normalize_llm_note_json(llm_json: Dict[str, Any]) -> Dict[str, Any]:
73
+ """
74
+ Convert LLM note JSON (single note, custom structure) to the standard notes_output.json format.
75
+ """
76
+ if "note_number" in llm_json or "full_title" in llm_json or "table_data" in llm_json:
77
+ return llm_json
78
+
79
+ normalized = {
80
+ "note_number": llm_json.get("metadata", {}).get("note_number", ""),
81
+ "note_title": llm_json.get("title", ""),
82
+ "full_title": llm_json.get("full_title", ""),
83
+ "table_data": [],
84
+ "breakdown": {},
85
+ "matched_accounts": [],
86
+ "total_amount": None,
87
+ "total_amount_lakhs": None,
88
+ "matched_accounts_count": None,
89
+ "comparative_data": {},
90
+ "notes_and_disclosures": [],
91
+ "markdown_content": "",
92
+ }
93
+ if "structure" in llm_json:
94
+ for item in llm_json["structure"]:
95
+ if "category" in item and "subcategories" in item:
96
+ for sub in item["subcategories"]:
97
+ row = {
98
+ "particulars": sub.get("label", ""),
99
+ "current_year": sub.get("value", ""),
100
+ "previous_year": ""
101
+ }
102
+ normalized["table_data"].append(row)
103
+ return normalized
104
+
105
+ def create_financial_table_sheet(workbook: Workbook, sheet_name: str, note_data: Dict[str, Any]) -> None:
106
+ """Create a properly formatted financial table sheet."""
107
+ ws = workbook.create_sheet(title=sheet_name)
108
+ header_font = Font(bold=True, color="FFFFFF")
109
+ header_fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid")
110
+ bold_font = Font(bold=True)
111
+ center_alignment = Alignment(horizontal="center", vertical="center")
112
+ right_alignment = Alignment(horizontal="right", vertical="center")
113
+ thin_border = Border(
114
+ left=Side(style='thin'),
115
+ right=Side(style='thin'),
116
+ top=Side(style='thin'),
117
+ bottom=Side(style='thin')
118
+ )
119
+ current_row = 1
120
+
121
+ # Add Note Title
122
+ note_title = note_data.get('full_title', note_data.get('note_title', 'Note'))
123
+ ws.cell(row=current_row, column=1, value=note_title)
124
+ ws.cell(row=current_row, column=1).font = Font(bold=True, size=14)
125
+ current_row += 2
126
+
127
+ # Process table_data if available
128
+ if 'table_data' in note_data and note_data['table_data']:
129
+ table_data = note_data['table_data']
130
+ df = pd.DataFrame(table_data)
131
+ for col_num, column_name in enumerate(df.columns, 1):
132
+ cell = ws.cell(row=current_row, column=col_num, value=column_name.replace('_', ' ').title())
133
+ cell.font = header_font
134
+ cell.fill = header_fill
135
+ cell.alignment = center_alignment
136
+ cell.border = thin_border
137
+ current_row += 1
138
+ for _, row in df.iterrows():
139
+ for col_num, value in enumerate(row, 1):
140
+ cell = ws.cell(row=current_row, column=col_num, value=value)
141
+ cell.border = thin_border
142
+ if col_num > 1:
143
+ cell.alignment = right_alignment
144
+ if isinstance(value, str) and ('**' in value or 'Total' in value or 'Particulars' in value):
145
+ cell.font = bold_font
146
+ cell.value = value.replace('**', '')
147
+ current_row += 1
148
+ current_row += 1
149
+
150
+ # Add breakdown information if available
151
+ if 'breakdown' in note_data and note_data['breakdown']:
152
+ ws.cell(row=current_row, column=1, value="Breakdown Details:")
153
+ ws.cell(row=current_row, column=1).font = bold_font
154
+ current_row += 1
155
+ ws.cell(row=current_row, column=1, value="Description")
156
+ ws.cell(row=current_row, column=2, value="Amount")
157
+ ws.cell(row=current_row, column=3, value="Amount (Lakhs)")
158
+ for col in range(1, 4):
159
+ cell = ws.cell(row=current_row, column=col)
160
+ cell.font = header_font
161
+ cell.fill = header_fill
162
+ cell.alignment = center_alignment
163
+ cell.border = thin_border
164
+ current_row += 1
165
+ for key, value in note_data['breakdown'].items():
166
+ if isinstance(value, dict):
167
+ desc = value.get('description', key)
168
+ amount = value.get('amount', 0)
169
+ amount_lakhs = value.get('amount_lakhs', 0)
170
+ ws.cell(row=current_row, column=1, value=desc).border = thin_border
171
+ ws.cell(row=current_row, column=2, value=amount).border = thin_border
172
+ ws.cell(row=current_row, column=3, value=amount_lakhs).border = thin_border
173
+ ws.cell(row=current_row, column=2).alignment = right_alignment
174
+ ws.cell(row=current_row, column=3).alignment = right_alignment
175
+ current_row += 1
176
+ current_row += 1
177
+
178
+ # Add matched accounts if available
179
+ if 'matched_accounts' in note_data and note_data['matched_accounts']:
180
+ ws.cell(row=current_row, column=1, value="Account-wise Breakdown:")
181
+ ws.cell(row=current_row, column=1).font = bold_font
182
+ current_row += 1
183
+ headers = ["Account", "Amount", "Amount (Lakhs)", "Group"]
184
+ for col_num, header in enumerate(headers, 1):
185
+ cell = ws.cell(row=current_row, column=col_num, value=header)
186
+ cell.font = header_font
187
+ cell.fill = header_fill
188
+ cell.alignment = center_alignment
189
+ cell.border = thin_border
190
+ current_row += 1
191
+ for account in note_data['matched_accounts']:
192
+ ws.cell(row=current_row, column=1, value=account.get('account', '')).border = thin_border
193
+ ws.cell(row=current_row, column=2, value=account.get('amount', 0)).border = thin_border
194
+ ws.cell(row=current_row, column=3, value=account.get('amount_lakhs', 0)).border = thin_border
195
+ ws.cell(row=current_row, column=4, value=account.get('group', '')).border = thin_border
196
+ ws.cell(row=current_row, column=2).alignment = right_alignment
197
+ ws.cell(row=current_row, column=3).alignment = right_alignment
198
+ current_row += 1
199
+ current_row += 1
200
+
201
+ # Add summary information
202
+ if 'total_amount' in note_data:
203
+ ws.cell(row=current_row, column=1, value="Summary:")
204
+ ws.cell(row=current_row, column=1).font = bold_font
205
+ current_row += 1
206
+ ws.cell(row=current_row, column=1, value="Total Amount:")
207
+ ws.cell(row=current_row, column=2, value=note_data.get('total_amount', 0))
208
+ ws.cell(row=current_row, column=2).alignment = right_alignment
209
+ current_row += 1
210
+ ws.cell(row=current_row, column=1, value="Total Amount (Lakhs):")
211
+ ws.cell(row=current_row, column=2, value=note_data.get('total_amount_lakhs', 0))
212
+ ws.cell(row=current_row, column=2).alignment = right_alignment
213
+ current_row += 1
214
+ ws.cell(row=current_row, column=1, value="Matched Accounts Count:")
215
+ ws.cell(row=current_row, column=2, value=note_data.get('matched_accounts_count', 0))
216
+ ws.cell(row=current_row, column=2).alignment = right_alignment
217
+ current_row += 1
218
+
219
+ # Auto-adjust column widths
220
+ for column in ws.columns:
221
+ max_length = 0
222
+ column_letter = get_column_letter(column[0].column)
223
+ for cell in column:
224
+ try:
225
+ if len(str(cell.value)) > max_length:
226
+ max_length = len(str(cell.value))
227
+ except Exception:
228
+ pass
229
+ adjusted_width = min(max_length + 2, 60)
230
+ ws.column_dimensions[column_letter].width = adjusted_width
231
+
232
+ def convert_json_to_excel(input_file: str, output_file: str) -> bool:
233
+ """Main function to convert JSON to Excel."""
234
+ json_data = read_json_file(input_file)
235
+ if json_data is None:
236
+ return False
237
+
238
+ # Normalize if needed
239
+ if isinstance(json_data, dict) and "notes" not in json_data:
240
+ normalized_note = normalize_llm_note_json(json_data)
241
+ json_data = {"notes": [normalized_note]}
242
+ elif isinstance(json_data, list):
243
+ json_data = {"notes": json_data}
244
+
245
+ workbook = Workbook()
246
+ default_sheet = workbook.active
247
+ workbook.remove(default_sheet)
248
+
249
+ if 'notes' in json_data:
250
+ notes_data = json_data['notes']
251
+ for note in notes_data:
252
+ try:
253
+ validated_note = NoteData(**note)
254
+ except ValidationError as ve:
255
+ logger.warning(f"Validation error for note: {ve}")
256
+ validated_note = note # fallback to raw dict
257
+ note_title = note.get('full_title', note.get('note_title', f"Note {note.get('note_number', '')}"))
258
+ clean_sheet_name = str(note_title).replace('/', '_').replace('\\', '_').replace('*', '_')
259
+ clean_sheet_name = clean_sheet_name.replace('?', '_').replace('[', '_').replace(']', '_')
260
+ clean_sheet_name = clean_sheet_name[:31]
261
+ logger.info(f"Processing: {clean_sheet_name}")
262
+ create_financial_table_sheet(workbook, clean_sheet_name, note)
263
+ else:
264
+ for note_key, note_data in json_data.items():
265
+ clean_sheet_name = str(note_key).replace('/', '_').replace('\\', '_').replace('*', '_')
266
+ clean_sheet_name = clean_sheet_name.replace('?', '_').replace('[', '_').replace(']', '_')
267
+ clean_sheet_name = clean_sheet_name[:31]
268
+ logger.info(f"Processing: {clean_sheet_name}")
269
+ if isinstance(note_data, dict):
270
+ create_financial_table_sheet(workbook, clean_sheet_name, note_data)
271
+ else:
272
+ simple_data = {"value": note_data}
273
+ create_financial_table_sheet(workbook, clean_sheet_name, simple_data)
274
+
275
+ try:
276
+ workbook.save(output_file)
277
+ logger.info(f"Successfully saved Excel file: {output_file}")
278
+ return True
279
+ except Exception as e:
280
+ logger.error(f"Error saving Excel file: {e}")
281
+ return False
282
+
283
+ def json_to_xlsx(input_json: str, output_xlsx: str) -> None:
284
+ """
285
+ Convert the given JSON file to Excel using the existing logic.
286
+ """
287
+ convert_json_to_excel(input_json, output_xlsx)
288
+
289
+ def main() -> None:
290
+ """Main execution function."""
291
+ input_file = settings.input_file
292
+ output_folder = settings.output_folder
293
+ output_file = os.path.join(output_folder, settings.output_file)
294
+ create_output_folder(output_folder)
295
+
296
+ if not os.path.exists(input_file):
297
+ logger.error(f"Input file '{input_file}' not found. Please ensure the file exists in the correct location.")
298
+ return
299
+
300
+ success = convert_json_to_excel(input_file, output_file)
301
+
302
+ if success:
303
+ logger.info("=" * 50)
304
+ logger.info("CONVERSION COMPLETED SUCCESSFULLY!")
305
+ logger.info("=" * 50)
306
+ logger.info(f"Input file: {input_file}")
307
+ logger.info(f"Output file: {output_file}")
308
+ logger.info("The Excel file has been created with:")
309
+ logger.info("- Each note as a separate sheet")
310
+ logger.info("- Proper financial table formatting")
311
+ logger.info("- Table data displayed in tabular format")
312
+ logger.info("- Breakdown and account details included")
313
+ logger.info("- Professional styling and formatting")
314
+ else:
315
+ logger.error("=" * 50)
316
+ logger.error("CONVERSION FAILED!")
317
+ logger.error("=" * 50)
318
+ logger.error("Please check the error messages above.")
319
+
320
+ if __name__ == "__main__":
321
+ main()
app/loader.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import logging
4
+ import pandas as pd
5
+ from typing import Any
6
+ from pydantic import BaseModel, ValidationError
7
+ from pydantic_settings import BaseSettings
8
+ from app.utils import clean_value
9
+
10
+ # Configure logging
11
+ logging.basicConfig(level=logging.INFO)
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class Settings(BaseSettings):
15
+ """Application settings loaded from environment variables or .env file."""
16
+ trial_balance_json: str = "output1/parsed_trial_balance.json"
17
+
18
+ settings = Settings()
19
+
20
+ class TrialBalanceRecord(BaseModel):
21
+ account_name: str
22
+ amount: float
23
+ group: str
24
+
25
+ def load_trial_balance() -> pd.DataFrame:
26
+ """
27
+ Load trial balance data from a JSON file, validate with Pydantic, and return as a cleaned DataFrame.
28
+ Raises FileNotFoundError if the file does not exist.
29
+ """
30
+ json_file = settings.trial_balance_json
31
+ if not os.path.exists(json_file):
32
+ logger.error(f"{json_file} not found! Please run the data extraction step first.")
33
+ raise FileNotFoundError(f"{json_file} not found! Please run the data extraction step first.")
34
+
35
+ with open(json_file, "r", encoding="utf-8") as f:
36
+ parsed_data = json.load(f)
37
+
38
+ # Determine the structure and load into DataFrame
39
+ if isinstance(parsed_data, list):
40
+ records = parsed_data
41
+ else:
42
+ records = parsed_data.get("trial_balance", parsed_data)
43
+
44
+ validated_records = []
45
+ for record in records:
46
+ try:
47
+ validated = TrialBalanceRecord(**record)
48
+ validated_dict = validated.dict()
49
+ except ValidationError as ve:
50
+ logger.warning(f"Validation error for record: {ve}")
51
+ validated_dict = record # fallback to raw dict
52
+ validated_records.append(validated_dict)
53
+
54
+ tb_df = pd.DataFrame(validated_records)
55
+ tb_df['amount'] = tb_df['amount'].apply(clean_value)
56
+ logger.info(f"Loaded trial balance with {len(tb_df)} records.")
57
+ return tb_df
app/main.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from app.api import router
3
+ import logging
4
+
5
+ # Configure logging for the application
6
+ logging.basicConfig(level=logging.INFO)
7
+ logger = logging.getLogger("financial_notes_api")
8
+
9
+ app = FastAPI(
10
+ title="Financial Notes Generator API",
11
+ description="API for generating financial notes, balance sheets, cash flow statements, and P&L reports.",
12
+ version="1.0.0"
13
+ )
14
+
15
+ app.include_router(router)
16
+
17
+ @app.on_event("startup")
18
+ async def startup_event():
19
+ logger.info("Financial Notes Generator API has started.")
20
+
21
+ @app.on_event("shutdown")
22
+ async def shutdown_event():
23
+ logger.info("Financial Notes Generator API is shutting down.")
app/main16_23.py ADDED
@@ -0,0 +1,906 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import logging
4
+ from datetime import datetime
5
+ from typing import Any, Dict, List, Optional
6
+ import pandas as pd
7
+ from pydantic import BaseModel, ValidationError
8
+ from pydantic_settings import BaseSettings
9
+
10
+ # Configure logging
11
+ logging.basicConfig(level=logging.INFO)
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class Settings(BaseSettings):
15
+ """Application settings loaded from environment variables or .env file."""
16
+ trial_balance_json: str = "output1/parsed_trial_balance.json"
17
+ output_json: str = "output2/notes_output.json"
18
+ output_md: str = "output2/financial_notes_all.md"
19
+ company_name: str = "Company Name"
20
+ financial_year: str = "2024-03-31"
21
+
22
+ settings = Settings()
23
+
24
+ class MatchedAccount(BaseModel):
25
+ account: str
26
+ amount: float
27
+ amount_lakhs: float
28
+ group: str
29
+
30
+ class NoteStructure(BaseModel):
31
+ note_number: str
32
+ note_title: str
33
+ full_title: str
34
+ total_amount: float
35
+ total_amount_lakhs: float
36
+ matched_accounts_count: int
37
+ matched_accounts: List[MatchedAccount]
38
+ breakdown: Dict[str, Any]
39
+ table_data: List[Dict[str, Any]]
40
+ comparative_data: Dict[str, Any]
41
+ notes_and_disclosures: List[str]
42
+ markdown_content: str
43
+
44
+ def clean_value(value: Any) -> float:
45
+ """Clean and convert value to float."""
46
+ try:
47
+ if isinstance(value, str):
48
+ value = value.replace(',', '').strip()
49
+ return float(value) if value else 0.0
50
+ except (ValueError, TypeError):
51
+ return 0.0
52
+
53
+ def to_lakhs(value: float) -> float:
54
+ """Convert value to lakhs."""
55
+ return round(value / 100000, 2)
56
+
57
+ def find_account_col(df: pd.DataFrame) -> str:
58
+ """Find the account column in DataFrame."""
59
+ for col in df.columns:
60
+ if df[col].astype(str).str.contains('account|particulars|name', case=False, na=False).any():
61
+ return col
62
+ return df.columns[0]
63
+
64
+ def find_balance_col(df: pd.DataFrame) -> Optional[str]:
65
+ """Find the balance column in DataFrame."""
66
+ for col in df.columns:
67
+ if df[col].dtype in [float, int] and df[col].notna().any():
68
+ return col
69
+ return df.columns[1] if len(df.columns) > 1 else None
70
+
71
+ def calculate_note(
72
+ df: pd.DataFrame,
73
+ note_name: str,
74
+ keywords: List[str],
75
+ exclude: Optional[List[str]] = None
76
+ ) -> Dict[str, Any]:
77
+ """Calculate total and matched accounts for a note."""
78
+ account_col = 'account_name' if 'account_name' in df.columns else find_account_col(df)
79
+ balance_col = 'amount' if 'amount' in df.columns else find_balance_col(df)
80
+ if not balance_col:
81
+ return {'total': 0, 'matched_accounts': []}
82
+ df = df.fillna(0)
83
+ total = 0
84
+ matched_accounts = []
85
+ for idx, row in df.iterrows():
86
+ account_name = str(row[account_col]).strip().lower()
87
+ if any(kw.lower() in account_name for kw in keywords) and (not exclude or not any(ex.lower() in account_name for ex in exclude)):
88
+ amount = clean_value(row[balance_col])
89
+ total += amount
90
+ matched_accounts.append({
91
+ 'account': str(row[account_col]),
92
+ 'amount': amount,
93
+ 'amount_lakhs': to_lakhs(amount),
94
+ 'group': row.get('group', 'Unknown') if 'group' in df.columns else 'Unknown'
95
+ })
96
+ return {'total': total, 'matched_accounts': matched_accounts}
97
+
98
+ def parse_markdown_table(content: str) -> List[Dict[str, Any]]:
99
+ """Parse markdown table content into list of dicts."""
100
+ lines = [line.strip() for line in content.strip().splitlines() if line.strip()]
101
+ table_lines = [line for line in lines if "|" in line and not line.startswith("|--")]
102
+ if not table_lines:
103
+ return []
104
+ table_data = []
105
+ for line in table_lines:
106
+ cells = [cell.strip() for cell in line.split("|") if cell.strip()]
107
+ if len(cells) >= 2:
108
+ row_data = {
109
+ "particulars": cells[0],
110
+ "current_year": cells[1] if len(cells) > 1 else "",
111
+ "previous_year": cells[2] if len(cells) > 2 else ""
112
+ }
113
+ table_data.append(row_data)
114
+ return table_data
115
+
116
+ def create_detailed_note_structure(
117
+ note_name: str,
118
+ result: Dict[str, Any],
119
+ content: str,
120
+ special_data: Optional[Dict[str, Any]] = None
121
+ ) -> Dict[str, Any]:
122
+ """Create detailed note structure for output."""
123
+ note_number = note_name.split('.')[0] if '.' in note_name else note_name
124
+ note_title = note_name.split('.', 1)[1].strip() if '.' in note_name else note_name
125
+ table_data = parse_markdown_table(content)
126
+ matched_accounts = []
127
+ for acc in result.get('matched_accounts', []):
128
+ matched_accounts.append({
129
+ "account": acc['account'],
130
+ "amount": acc['amount'],
131
+ "amount_lakhs": to_lakhs(acc['amount']),
132
+ "group": acc.get('group', 'Unknown')
133
+ })
134
+ note_structure = {
135
+ "note_number": note_number,
136
+ "note_title": note_title,
137
+ "full_title": note_name,
138
+ "total_amount": result['total'],
139
+ "total_amount_lakhs": to_lakhs(result['total']),
140
+ "matched_accounts_count": len(matched_accounts),
141
+ "matched_accounts": matched_accounts,
142
+ "breakdown": special_data.get('breakdown', {}) if special_data else {},
143
+ "table_data": table_data,
144
+ "comparative_data": {
145
+ "current_year": {"year": settings.financial_year, "amount": result['total'], "amount_lakhs": to_lakhs(result['total'])},
146
+ "previous_year": {"year": "2023-03-31", "amount": 0, "amount_lakhs": 0}
147
+ },
148
+ "notes_and_disclosures": [],
149
+ "markdown_content": f"### {note_name}\n\n{content}\n\n**Account-wise breakdown:**\n"
150
+ }
151
+ for acc in matched_accounts:
152
+ note_structure["markdown_content"] += f"- {acc['account']}: ₹{acc['amount']:,.2f} ({acc['amount_lakhs']} Lakhs)\n"
153
+ if special_data:
154
+ note_structure.update(special_data)
155
+ return note_structure
156
+
157
+ def generate_notes(tb_df: pd.DataFrame) -> Dict[str, Any]:
158
+ """
159
+ Generate notes 16-26 from parsed trial balance data.
160
+ Returns a dict with metadata and notes.
161
+ """
162
+ notes = []
163
+ note_mappings = {
164
+ '2. Share Capital': {'keywords': ['Share Capital', 'share capital', 'equity share', 'paid up']},
165
+ '3. Reserves and Surplus': {'keywords': ['Reserves', 'Surplus', 'reserves', 'surplus', 'retained earnings']},
166
+ '4. Long Term Borrowings': {'keywords': ['loan', 'borrowing', 'term loan'], 'exclude': ['current maturities', 'short term']},
167
+ '5. Deferred Tax Liability': {'keywords': ['Deferred Tax', 'deferred tax']},
168
+ '6. Trade Payables': {'keywords': ['Creditors', 'creditors', 'trade payable', 'suppliers']},
169
+ '7. Other Current Liabilities': {'keywords': ['Expenses Payable', 'Current Maturities', 'payable', 'accrued']},
170
+ '8. Short Term Provisions': {'keywords': ['Provision', 'provision', 'taxation']},
171
+ '9. Fixed Assets': {'keywords': ['Equipment', 'Furniture', 'Building', 'Vehicle', 'Motor', 'Asset', 'plant', 'machinery']},
172
+ '10. Long Term Loans and Advances': {'keywords': ['Long Term', 'Security Deposits', 'advances', 'deposits']},
173
+ '11. Inventories': {'keywords': ['Stock', 'Inventory', 'stock', 'inventory', 'goods']},
174
+ '12. Trade Receivables': {'keywords': ['Receivables', 'receivables', 'debtors', 'trade receivable']},
175
+ '13. Cash and Bank Balances': {'keywords': ['Cash-in-hand', 'Bank accounts', 'Deposits']},
176
+ '14. Short Term Loans and Advances': {'keywords': ['Prepaid Expenses', 'TDS Receivables', 'Loans & Advances', 'TCS RECEIVABLES', 'TDS Advance Tax Paid', 'Advance to Perennail']},
177
+ '15. Other Current Assets': {'keywords': ['Interest accrued', 'accrued', 'current asset']},
178
+ '16. Revenue from Operations': {
179
+ 'keywords': ['Revenue', 'Sales', 'Service', 'Income', 'Consultancy', 'Gain / Loss on Sales of Fixed Assets', 'Income Tax',
180
+ 'Servicing of BA/BE PROJECTS', 'Working Standards - Export', 'SERVICING OF BA PROJECTS', 'SERVICING OF ONLY CLINICAL']
181
+ },
182
+ '17. Other Income': {
183
+ 'keywords': ['Interest on FD', 'Interest on Income Tax Refund', 'Unadjusted Forex Gain/Loss', 'Forex Gain / Loss', 'Interest']
184
+ },
185
+ '18. Cost of Materials Consumed': {
186
+ 'keywords': ['Opening Stock', 'Bio Lab Consumables', 'Non GST', 'Purchase GST', 'Closing Stock']
187
+ },
188
+ '19. Employee Benefit Expense': {
189
+ 'keywords': ['Salary', 'Wages', 'Bonus', 'Employee', 'Remuneration', 'Comp Offs', 'Retainership',
190
+ 'Employees Group Life Insurance', 'Employees Health & Personal Accident Insurance',
191
+ 'Prepaid - Employees Group Life Insurance', 'Prepaid Insurance - Employees Health & Personal Accident',
192
+ 'Staff Welfare Expenses', 'Employees Expenses Reimbursement', 'Contribution to PF', 'Contribution to ESI']
193
+ },
194
+ '20. Other Expenses': {
195
+ 'keywords': ['BA / BE NOC', 'BA Expenses', 'Payments to Volunteers', 'Other Operating Expenses', 'Laboratory testing',
196
+ 'Rent', 'Rates & Taxes', 'Fees & licenses', 'Insurance', 'Membership & Subscription',
197
+ 'Postage & Communication', 'Printing and Stationery', 'CSR Fund', 'Telephone & Internet',
198
+ 'Travelling and Conveyance', 'Translation Charges', 'Electricity Charges', 'Security Charges',
199
+ 'Annual Maintenance', 'Repairs and maintenance', 'Business Development', 'Professional & Consultancy',
200
+ 'Payment to Auditors', 'Bad Debts', 'Fire Extinguishers', 'Food Expenses', 'Diesel Expenses',
201
+ 'Interest Under 234 C', 'Loan Processing Charges', 'Sitting Fee of Directors', 'Customs Duty',
202
+ 'Transportation and Unloading', 'Software Equipment', 'Miscellaneous expenses', 'Laptop Accessories',
203
+ 'Professional Fee', 'Office Rent', 'Security Deposit']
204
+ },
205
+ '21. Depreciation and Amortisation Expense': {
206
+ 'keywords': ['Depreciation', 'Amortization', 'Accumulated Depreciation', 'Depreciation And Amortisation']
207
+ },
208
+ '22. Loss on Sale of Assets & Investments': {
209
+ 'keywords': ['Short Term Loss', 'Long term loss', 'Loss on Sale of Fixed Assets', 'Loss on Sale of Investments']
210
+ },
211
+ '23. Finance Costs': {
212
+ 'keywords': ['Bank Charges', 'Finance Charges', 'Interest', 'Loan Processing', 'Interest and penalty', 'Interest on TDS']
213
+ },
214
+ '24. Payment to Auditor': {
215
+ 'keywords': ['Payment to Auditors', 'Audit Fee', 'Tax Audit', 'Certification Fees']
216
+ },
217
+ '25. Earnings in Foreign Currency': {
218
+ 'keywords': ['Income from export of services', 'Servicing of BA/BE PROJECTS EXPORT', 'Working Standards - Export']
219
+ },
220
+ '26. Particulars of Un-hedged Foreign Currency Exposure': {
221
+ 'keywords': ['Income from export of services', 'Servicing of BA/BE PROJECTS EXPORT', 'Working Standards - Export']
222
+ }
223
+ }
224
+
225
+ logger.info("Generating notes 16-26 from parsed trial balance data...")
226
+ logger.info(f"Total records in trial balance: {len(tb_df)}")
227
+
228
+ for note_name, mapping in note_mappings.items():
229
+ keywords = mapping['keywords']
230
+ result = calculate_note(tb_df, note_name, keywords)
231
+
232
+ if result['matched_accounts']:
233
+ logger.info(f"\n{note_name}:")
234
+ logger.info(f" Total: ₹{result['total']:,.2f} ({to_lakhs(result['total'])} Lakhs)")
235
+ logger.info(f" Matched {len(result['matched_accounts'])} accounts:")
236
+ for acc in result['matched_accounts'][:3]:
237
+ logger.info(f" • {acc['account']}: ₹{acc['amount']:,.2f}")
238
+ if len(result['matched_accounts']) > 3:
239
+ logger.info(f" ... and {len(result['matched_accounts']) - 3} more")
240
+ else:
241
+ logger.warning(f"\n{note_name}: No matching accounts found")
242
+
243
+ content = ""
244
+ special_data = {}
245
+
246
+ if note_name == '2. Share Capital':
247
+ content = """
248
+ | Particulars | 2024-03-31 | 2023-03-31 |
249
+ |------------------------------|------------|------------|
250
+ | **Authorised shares** | | |
251
+ | 75,70,000 equity shares of ₹ 10/- each | 757.0 | 757.0 |
252
+ | **Issued, subscribed and fully paid-up shares** | | |
253
+ | 54,25,210 equity shares of ₹ 10/- each | {total_lakhs} | 542.52 |
254
+ | **Total issued, subscribed and fully paid-up share capital** | {total_lakhs} | 542.52 |
255
+ """.format(total_lakhs=to_lakhs(result['total']))
256
+ special_data = {
257
+ "breakdown": {
258
+ "authorised_shares": {"description": "75,70,000 equity shares of ₹ 10/- each", "amount": 75700000, "amount_lakhs": 757.0},
259
+ "issued_subscribed_paid_up": {"description": "54,25,210 equity shares of ₹ 10/- each", "amount": result['total'], "amount_lakhs": to_lakhs(result['total'])}
260
+ }
261
+ }
262
+
263
+ elif note_name == '3. Reserves and Surplus':
264
+ content = """
265
+ | Particulars | March 31, 2024 | March 31, 2023 |
266
+ |------------------------------|----------------|----------------|
267
+ | Reserves and Surplus | {total_lakhs} | - |
268
+ """.format(total_lakhs=to_lakhs(result['total']))
269
+
270
+ elif note_name == '4. Long Term Borrowings':
271
+ content = """
272
+ | Particulars | March 31, 2024 | March 31, 2023 |
273
+ |------------------------------|----------------|----------------|
274
+ | Long Term Borrowings | {total_lakhs} | - |
275
+ """.format(total_lakhs=to_lakhs(result['total']))
276
+
277
+ elif note_name == '5. Deferred Tax Liability':
278
+ content = """
279
+ | Particulars | March 31, 2024 | March 31, 2023 |
280
+ |------------------------------|----------------|----------------|
281
+ | Deferred Tax Liability | {total_lakhs} | - |
282
+ """.format(total_lakhs=to_lakhs(result['total']))
283
+
284
+ elif note_name == '6. Trade Payables':
285
+ content = """
286
+ | Particulars | March 31, 2024 | March 31, 2023 |
287
+ |------------------------------|----------------|----------------|
288
+ | Trade Payables | {total_lakhs} | - |
289
+ """.format(total_lakhs=to_lakhs(result['total']))
290
+
291
+ elif note_name == '7. Other Current Liabilities':
292
+ expenses_payable = calculate_note(tb_df, note_name, ['Expenses Payable', 'payable', 'accrued'])['total']
293
+ current_maturities = calculate_note(tb_df, note_name, ['Current Maturities', 'current portion'])['total']
294
+ statutory_dues = 7935166.72
295
+ content = """
296
+ | Particulars | March 31, 2024 | March 31, 2023 |
297
+ |------------------------------|----------------|----------------|
298
+ | Current Maturities of Long Term Borrowings | {cm_lakhs} | 139.20 |
299
+ | Outstanding Liabilities for Expenses | {ep_lakhs} | 156.88 |
300
+ | Statutory dues | {sd_lakhs} | 48.03 |
301
+ | **Total** | {total_lakhs} | 344.12 |
302
+ """.format(cm_lakhs=to_lakhs(current_maturities), ep_lakhs=to_lakhs(expenses_payable), sd_lakhs=to_lakhs(statutory_dues), total_lakhs=to_lakhs(current_maturities + expenses_payable + statutory_dues))
303
+ special_data = {
304
+ "breakdown": {
305
+ "current_maturities": {"description": "Current Maturities of Long Term Borrowings", "amount": current_maturities, "amount_lakhs": to_lakhs(current_maturities)},
306
+ "expenses_payable": {"description": "Outstanding Liabilities for Expenses", "amount": expenses_payable, "amount_lakhs": to_lakhs(expenses_payable)},
307
+ "statutory_dues": {"description": "Statutory dues", "amount": statutory_dues, "amount_lakhs": to_lakhs(statutory_dues)}
308
+ }
309
+ }
310
+
311
+ elif note_name == '8. Short Term Provisions':
312
+ content = """
313
+ | Particulars | March 31, 2024 | March 31, 2023 |
314
+ |------------------------------|----------------|----------------|
315
+ | Short Term Provisions | {total_lakhs} | - |
316
+ """.format(total_lakhs=to_lakhs(result['total']))
317
+
318
+ elif note_name == '9. Fixed Assets':
319
+ equipments = calculate_note(tb_df, note_name, ['Equipment', 'equipment'])['total']
320
+ furniture = calculate_note(tb_df, note_name, ['Furniture', 'furniture', 'fixture'])['total']
321
+ building = calculate_note(tb_df, note_name, ['Building', 'building'])['total']
322
+ vehicle = calculate_note(tb_df, note_name, ['Vehicle', 'vehicle', 'car'])['total']
323
+ content = """
324
+ | Particulars | Gross Carrying Value | Accumulated Depreciation | Net Carrying Value |
325
+ |------------------------------|----------------------|--------------------------|--------------------|
326
+ | As at 1st April 2023 | Additions | Deletion | As at 31st March 2024 | As at 1st April 2023 | For the year | Deletion | As at 31st March 2024 | As at 31st March 2024 | As at 1st April 2023 |
327
+ |------------------------------|----------------------|--------------------------|--------------------|
328
+ | Tangible Assets | | | |
329
+ | Buildings | 312.66 | {bldg_add} | 0 | {bldg_gcv} | 312.65 | 1478.81 | 0 | 1791.46 | {bldg_ncv} | 1.00 |
330
+ | Equipments | {eq_gcv} | 0 | 0 | {eq_gcv} | 0 | 0 | 0 | 0 | {eq_ncv} | {eq_ncv} |
331
+ | Furniture & Fixtures | {fur_gcv} | 0 | 0 | {fur_gcv} | 0 | 0 | 0 | 0 | {fur_ncv} | {fur_ncv} |
332
+ | Motor Vehicle | {veh_gcv} | 0 | 0 | {veh_gcv} | 0 | 752.98 | 0 | 752.98 | {veh_ncv} | {veh_gcv} |
333
+ """.format(bldg_add=to_lakhs(building), bldg_gcv=to_lakhs(312655 + building), bldg_ncv=to_lakhs(building), eq_gcv=to_lakhs(equipments), eq_ncv=to_lakhs(equipments), fur_gcv=to_lakhs(furniture), fur_ncv=to_lakhs(furniture), veh_gcv=to_lakhs(vehicle), veh_ncv=to_lakhs(vehicle - 752982.45))
334
+ special_data = {
335
+ "breakdown": {
336
+ "buildings": {"gross_value": 312655 + building, "net_value": building, "accumulated_depreciation": 1791462},
337
+ "equipments": {"gross_value": equipments, "net_value": equipments, "accumulated_depreciation": 0},
338
+ "furniture_fixtures": {"gross_value": furniture, "net_value": furniture, "accumulated_depreciation": 0},
339
+ "motor_vehicle": {"gross_value": vehicle, "net_value": vehicle - 752982.45, "accumulated_depreciation": 752982.45}
340
+ }
341
+ }
342
+
343
+ elif note_name == '10. Long Term Loans and Advances':
344
+ content = """
345
+ | Particulars | March 31, 2024 | March 31, 2023 |
346
+ |------------------------------|----------------|----------------|
347
+ | Long Term Loans and Advances | {total_lakhs} | - |
348
+ """.format(total_lakhs=to_lakhs(result['total']))
349
+
350
+ elif note_name == '11. Inventories':
351
+ content = """
352
+ | Particulars | March 31, 2024 | March 31, 2023 |
353
+ |------------------------------|----------------|----------------|
354
+ | Consumables | {total_lakhs} | - |
355
+ """.format(total_lakhs=to_lakhs(result['total']))
356
+
357
+ elif note_name == '12. Trade Receivables':
358
+ over_6m = result.get('over_6m', 0)
359
+ content = """
360
+ | Particulars | 2024-03-31 | 2023-03-31 |
361
+ |------------------------------|------------|------------|
362
+ | Unsecured, considered good | | |
363
+ | Outstanding for a period exceeding six months | {over_6m} | 104.65 |
364
+ | Total | {total_lakhs} | 1037.59 |
365
+ """.format(over_6m=to_lakhs(over_6m), total_lakhs=to_lakhs(result['total']))
366
+ special_data = {
367
+ "breakdown": {
368
+ "over_six_months": {"description": "Outstanding for a period exceeding six months", "amount": over_6m, "amount_lakhs": to_lakhs(over_6m)},
369
+ "total_receivables": {"description": "Total Trade Receivables", "amount": result['total'], "amount_lakhs": to_lakhs(result['total'])}
370
+ }
371
+ }
372
+
373
+ elif note_name == '13. Cash and Bank Balances':
374
+ cash_in_hand = calculate_note(tb_df, note_name, ['Cash-in-hand'])['total']
375
+ bank_accounts = calculate_note(tb_df, note_name, ['Bank accounts'])['total']
376
+ fixed_deposit = calculate_note(tb_df, note_name, ['Deposits'])['total']
377
+ total = cash_in_hand + bank_accounts + fixed_deposit
378
+ content = """
379
+ | Particulars | March 31, 2024 | March 31, 2023 |
380
+ |------------------------------|----------------|----------------|
381
+ | **Cash and cash equivalents**| | |
382
+ | Balances with banks in current accounts | {ba_lakhs} | - |
383
+ | Cash in hand | {cih_lakhs} | - |
384
+ | **Other Bank Balances** | | |
385
+ | Fixed Deposit | {fd_lakhs} | - |
386
+ | **Total** | {total_lakhs} | - |
387
+ """.format(ba_lakhs=to_lakhs(bank_accounts), cih_lakhs=to_lakhs(cash_in_hand), fd_lakhs=to_lakhs(fixed_deposit), total_lakhs=to_lakhs(total))
388
+ result['total'] = total
389
+ special_data = {
390
+ "breakdown": {
391
+ "cash_in_hand": {"description": "Cash in hand", "amount": cash_in_hand, "amount_lakhs": to_lakhs(cash_in_hand)},
392
+ "bank_balances": {"description": "Balances with banks in current accounts", "amount": bank_accounts, "amount_lakhs": to_lakhs(bank_accounts)},
393
+ "fixed_deposits": {"description": "Fixed Deposit", "amount": fixed_deposit, "amount_lakhs": to_lakhs(fixed_deposit)}
394
+ }
395
+ }
396
+
397
+ elif note_name == '14. Short Term Loans and Advances':
398
+ other_advances = calculate_note(tb_df, note_name, ['Loans & Advances'])['total']
399
+ prepaid_expenses = calculate_note(tb_df, note_name, ['Prepaid Expenses'])['total']
400
+ advance_tax = calculate_note(tb_df, note_name, ['TDS Advance Tax Paid'])['total']
401
+ balances = calculate_note(tb_df, note_name, ['TDS Receivables'])['total']
402
+ total = other_advances + prepaid_expenses + advance_tax + balances
403
+ content = """
404
+ | Particulars | March 31, 2024 | March 31, 2023 |
405
+ |------------------------------|----------------|----------------|
406
+ | **Unsecured, considered good**| | |
407
+ | Prepaid Expenses | {pe_lakhs} | - |
408
+ | Other Advances | {oa_lakhs} | - |
409
+ | **Other loans and advances** | | |
410
+ | Advance tax | {at_lakhs} | - |
411
+ | Balances with statutory/government authorities | {bs_lakhs} | - |
412
+ | **Total** | {total_lakhs} | - |
413
+ """.format(pe_lakhs=to_lakhs(prepaid_expenses), oa_lakhs=to_lakhs(other_advances), at_lakhs=to_lakhs(advance_tax), bs_lakhs=to_lakhs(balances), total_lakhs=to_lakhs(total))
414
+ result['total'] = total
415
+ special_data = {
416
+ "breakdown": {
417
+ "prepaid_expenses": {"description": "Prepaid Expenses", "amount": prepaid_expenses, "amount_lakhs": to_lakhs(prepaid_expenses)},
418
+ "other_advances": {"description": "Other Advances", "amount": other_advances, "amount_lakhs": to_lakhs(other_advances)},
419
+ "advance_tax": {"description": "Advance tax", "amount": advance_tax, "amount_lakhs": to_lakhs(advance_tax)},
420
+ "statutory_balances": {"description": "Balances with statutory/government authorities", "amount": balances, "amount_lakhs": to_lakhs(balances)}
421
+ }
422
+ }
423
+
424
+ elif note_name == '15. Other Current Assets':
425
+ content = """
426
+ | Particulars | March 31, 2024 | March 31, 2023 |
427
+ |------------------------------|----------------|----------------|
428
+ | Other Current Assets | {total_lakhs} | - |
429
+ """.format(total_lakhs=to_lakhs(result['total']))
430
+
431
+ elif note_name == '16. Revenue from Operations':
432
+ servicing_babe_export = calculate_note(tb_df, note_name, ['Servicing of BA/BE PROJECTS EXPORT'])['total']
433
+ working_standards_export = calculate_note(tb_df, note_name, ['Working Standards - Export'])['total']
434
+ exports = servicing_babe_export + working_standards_export
435
+ servicing_babe_inter_state = calculate_note(tb_df, note_name, ['Servicing of BA/BE PROJECTS-Inter State'])['total']
436
+ servicing_babe_intra_state = calculate_note(tb_df, note_name, ['Servicing of BA/BE PROJECTS-Intra State'])['total']
437
+ servicing_ba_intra_state = calculate_note(tb_df, note_name, ['SERVICING OF BA PROJECTS-Intra State'])['total']
438
+ servicing_clinical_intra_state = calculate_note(tb_df, note_name, ['SERVICING OF ONLY CLINICAL INTRA STATE'])['total']
439
+ domestic = servicing_babe_inter_state + servicing_babe_intra_state + servicing_ba_intra_state + servicing_clinical_intra_state
440
+ sales_other = calculate_note(tb_df, note_name, ['Sales', 'Gain / Loss on Sales of Fixed Assets', 'Consultancy & Service Fee', 'Income', 'Income Tax'])['total']
441
+ total = result['total'] # Use total from calculate_note
442
+ content = """
443
+ | Particulars | March 31, 2024 | March 31, 2023 |
444
+ |------------------------------|----------------|----------------|
445
+ | **Sale of Services** | | |
446
+ | Domestic | {dom_lakhs} | - |
447
+ | Exports | {exp_lakhs} | - |
448
+ | Sales and Other Income | {sales_lakhs} | - |
449
+ | **Total** | {total_lakhs} | - |
450
+ """.format(dom_lakhs=to_lakhs(domestic), exp_lakhs=to_lakhs(exports), sales_lakhs=to_lakhs(sales_other), total_lakhs=to_lakhs(total))
451
+ special_data = {
452
+ "breakdown": {
453
+ "domestic_revenue": {"description": "Domestic Sales", "amount": domestic, "amount_lakhs": to_lakhs(domestic), "components": {
454
+ "ba_be_interstate": servicing_babe_inter_state,
455
+ "ba_be_intrastate": servicing_babe_intra_state,
456
+ "ba_intrastate": servicing_ba_intra_state,
457
+ "clinical_intrastate": servicing_clinical_intra_state
458
+ }},
459
+ "export_revenue": {"description": "Export Sales", "amount": exports, "amount_lakhs": to_lakhs(exports), "components": {
460
+ "ba_be_export": servicing_babe_export,
461
+ "working_standards_export": working_standards_export
462
+ }},
463
+ "sales_and_other": {"description": "Sales and Other Income", "amount": sales_other, "amount_lakhs": to_lakhs(sales_other)}
464
+ }
465
+ }
466
+
467
+ elif note_name == '17. Other Income':
468
+ interest_income = calculate_note(tb_df, note_name, ['Interest on FD', 'Interest on Income Tax Refund', 'Interest'])['total']
469
+ forex_gain = calculate_note(tb_df, note_name, ['Unadjusted Forex Gain/Loss', 'Forex Gain / Loss'])['total']
470
+ total = result['total'] # Use total from calculate_note
471
+ content = """
472
+ | Particulars | March 31, 2024 | March 31, 2023 |
473
+ |------------------------------|----------------|----------------|
474
+ | Interest income | {ii_lakhs} | - |
475
+ | Foreign exchange gain (Net) | {fg_lakhs} | - |
476
+ | **Total** | {total_lakhs} | - |
477
+ """.format(ii_lakhs=to_lakhs(interest_income), fg_lakhs=to_lakhs(forex_gain), total_lakhs=to_lakhs(total))
478
+ special_data = {
479
+ "breakdown": {
480
+ "interest_income": {"description": "Interest income", "amount": interest_income, "amount_lakhs": to_lakhs(interest_income)},
481
+ "forex_gain": {"description": "Foreign exchange gain (Net)", "amount": forex_gain, "amount_lakhs": to_lakhs(forex_gain)}
482
+ }
483
+ }
484
+
485
+ elif note_name == '18. Cost of Materials Consumed':
486
+ opening_stock = calculate_note(tb_df, note_name, ['Opening Stock'])['total']
487
+ purchases = calculate_note(tb_df, note_name, ['Bio Lab Consumables', 'Non GST', 'Purchase GST'])['total']
488
+ closing_stock = calculate_note(tb_df, note_name, ['Closing Stock'])['total']
489
+ total = opening_stock + purchases - closing_stock # As per note structure
490
+ content = """
491
+ | Particulars | March 31, 2024 | March 31, 2023 |
492
+ |------------------------------|----------------|----------------|
493
+ | Opening Stock | {os_lakhs} | - |
494
+ | Add: Purchases | {pur_lakhs} | - |
495
+ | | {subtotal_lakhs}| - |
496
+ | Less: Closing Stock | {cs_lakhs} | - |
497
+ | Cost of materials consumed | {total_lakhs} | - |
498
+ """.format(os_lakhs=to_lakhs(opening_stock), pur_lakhs=to_lakhs(purchases), subtotal_lakhs=to_lakhs(opening_stock + purchases),
499
+ cs_lakhs=to_lakhs(closing_stock), total_lakhs=to_lakhs(total))
500
+ special_data = {
501
+ "breakdown": {
502
+ "opening_stock": {"description": "Opening Stock", "amount": opening_stock, "amount_lakhs": to_lakhs(opening_stock)},
503
+ "purchases": {"description": "Purchases", "amount": purchases, "amount_lakhs": to_lakhs(purchases)},
504
+ "closing_stock": {"description": "Closing Stock", "amount": closing_stock, "amount_lakhs": to_lakhs(closing_stock)},
505
+ "cost_consumed": {"description": "Cost of materials consumed", "amount": total, "amount_lakhs": to_lakhs(total)}
506
+ }
507
+ }
508
+
509
+ elif note_name == '19. Employee Benefit Expense':
510
+ salaries_wages_bonus = calculate_note(tb_df, note_name, ['Salary', 'Wages', 'Bonus', 'Remuneration', 'Comp Offs', 'Retainership'])['total']
511
+ pf_esi = calculate_note(tb_df, note_name, ['Contribution to PF', 'Contribution to ESI'])['total']
512
+ staff_welfare = calculate_note(tb_df, note_name, ['Staff Welfare Expenses', 'Employees Expenses Reimbursement'])['total']
513
+ insurance = calculate_note(tb_df, note_name, ['Employees Group Life Insurance', 'Employees Health & Personal Accident Insurance',
514
+ 'Prepaid - Employees Group Life Insurance', 'Prepaid Insurance - Employees Health & Personal Accident'])['total']
515
+ total = result['total'] # Use total from calculate_note
516
+ content = """
517
+ | Particulars | March 31, 2024 | March 31, 2023 |
518
+ |------------------------------|----------------|----------------|
519
+ | Salaries, wages and bonus | {swb_lakhs} | - |
520
+ | Contribution to PF & ESI | {pf_esi_lakhs} | - |
521
+ | Staff welfare expenses | {sw_lakhs} | - |
522
+ | **Total** | {total_lakhs} | - |
523
+ """.format(swb_lakhs=to_lakhs(salaries_wages_bonus), pf_esi_lakhs=to_lakhs(pf_esi), sw_lakhs=to_lakhs(staff_welfare),
524
+ ins_lakhs=to_lakhs(insurance), total_lakhs=to_lakhs(total))
525
+ special_data = {
526
+ "breakdown": {
527
+ "salaries_wages_bonus": {"description": "Salaries, wages and bonus", "amount": salaries_wages_bonus, "amount_lakhs": to_lakhs(salaries_wages_bonus)},
528
+ "pf_esi": {"description": "Contribution to PF & ESI", "amount": pf_esi, "amount_lakhs": to_lakhs(pf_esi)},
529
+ "staff_welfare": {"description": "Staff welfare expenses", "amount": staff_welfare, "amount_lakhs": to_lakhs(staff_welfare)},
530
+ "insurance": {"description": "Insurance Expenses", "amount": insurance, "amount_lakhs": to_lakhs(insurance)}
531
+ }
532
+ }
533
+
534
+ elif note_name == '20. Other Expenses':
535
+ ba_be_noc = calculate_note(tb_df, note_name, ['BA / BE NOC Charges'])['total']
536
+ ba_expenses = calculate_note(tb_df, note_name, ['BA Expenses'])['total']
537
+ volunteers = calculate_note(tb_df, note_name, ['Payments to Volunteers'])['total']
538
+ other_operating = calculate_note(tb_df, note_name, ['Other Operating Expenses'])['total']
539
+ lab_testing = calculate_note(tb_df, note_name, ['Laboratory testing charges'])['total']
540
+ rent = calculate_note(tb_df, note_name, ['Rent', 'Office Rent'])['total']
541
+ rates_taxes = calculate_note(tb_df, note_name, ['Rates & Taxes'])['total']
542
+ fees_licenses = calculate_note(tb_df, note_name, ['Fees & licenses'])['total']
543
+ insurance = calculate_note(tb_df, note_name, ['Insurance'])['total']
544
+ membership = calculate_note(tb_df, note_name, ['Membership & Subscription Charges'])['total']
545
+ postage = calculate_note(tb_df, note_name, ['Postage & Communication Cost'])['total']
546
+ printing = calculate_note(tb_df, note_name, ['Printing and Stationery'])['total']
547
+ csr = calculate_note(tb_df, note_name, ['CSR Fund Expenses'])['total']
548
+ telephone = calculate_note(tb_df, note_name, ['Telephone & Internet', 'Telephone Expense'])['total']
549
+ travelling = calculate_note(tb_df, note_name, ['Travelling and Conveyance'])['total']
550
+ translation = calculate_note(tb_df, note_name, ['Translation Charges'])['total']
551
+ electricity = calculate_note(tb_df, note_name, ['Electricity Charges'])['total']
552
+ security = calculate_note(tb_df, note_name, ['Security Charges', 'Security Deposit', 'Security Deposit - ESIC',
553
+ 'Security Deposits - Awfis Space Solutions Private Limited',
554
+ 'Security Deposits - Concept Classic Converge', 'Security Deposit - Hive Space'])['total']
555
+ maintenance = calculate_note(tb_df, note_name, ['Annual Maintenance Charges', 'Laptop Accessories and Maintenance',
556
+ 'Laptop Annual Maintenance Charges'])['total']
557
+ repairs_electrical = calculate_note(tb_df, note_name, ['Repairs and maintenance - Electrical'])['total']
558
+ repairs_office = calculate_note(tb_df, note_name, ['Repairs and maintenance - Office'])['total']
559
+ repairs_machinery = calculate_note(tb_df, note_name, ['Repairs and maintenance - Machinery'])['total']
560
+ repairs_vehicles = calculate_note(tb_df, note_name, ['Repairs and maintenance - Vehicles'])['total']
561
+ repairs_others = calculate_note(tb_df, note_name, ['Repairs and maintenance - Others'])['total']
562
+ business_dev = calculate_note(tb_df, note_name, ['Business Development Expenses'])['total']
563
+ professional = calculate_note(tb_df, note_name, ['Professional & Consultancy', 'Professional Fee',
564
+ 'Provision for Professional Fee', 'Professional Fee (Transfer Pricing)'])['total']
565
+ auditors = calculate_note(tb_df, note_name, ['Payment to Auditors'])['total']
566
+ bad_debts = calculate_note(tb_df, note_name, ['Bad Debts Written Off'])['total']
567
+ fire_extinguishers = calculate_note(tb_df, note_name, ['Fire Extinguishers Refilling Charges'])['total']
568
+ food_guests = calculate_note(tb_df, note_name, ['Food Expenses for Guests'])['total']
569
+ diesel = calculate_note(tb_df, note_name, ['Diesel Expenses'])['total']
570
+ interest_234c = calculate_note(tb_df, note_name, ['Interest Under 234 C'])['total']
571
+ loan_processing = calculate_note(tb_df, note_name, ['Loan Processing Charges'])['total']
572
+ sitting_fee = calculate_note(tb_df, note_name, ['Sitting Fee of Directors'])['total']
573
+ customs_duty = calculate_note(tb_df, note_name, ['Customs Duty Payment'])['total']
574
+ transportation = calculate_note(tb_df, note_name, ['Transportation and Unloading Charges'])['total']
575
+ software = calculate_note(tb_df, note_name, ['Software Equipment'])['total']
576
+ misc = calculate_note(tb_df, note_name, ['Miscellaneous expenses'])['total']
577
+ total = result['total'] # Use total from calculate_note
578
+ content = """
579
+ | Particulars | March 31, 2024 | March 31, 2023 |
580
+ |------------------------------|----------------|----------------|
581
+ | BA / BE NOC Charges | {ba_be_noc_lakhs} | - |
582
+ | BA Expenses | {ba_exp_lakhs} | - |
583
+ | Payments to Volunteers | {vol_lakhs} | - |
584
+ | Other Operating Expenses | {oo_lakhs} | - |
585
+ | Laboratory testing charges | {lab_lakhs} | - |
586
+ | Rent | {rent_lakhs} | - |
587
+ | Rates & Taxes | {rt_lakhs} | - |
588
+ | Fees & licenses | {fl_lakhs} | - |
589
+ | Insurance | {ins_lakhs} | - |
590
+ | Membership & Subscription Charges | {mem_lakhs}| - |
591
+ | Postage & Communication Cost | {post_lakhs} | - |
592
+ | Printing and stationery | {print_lakhs} | - |
593
+ | CSR Fund Expenses | {csr_lakhs} | - |
594
+ | Telephone & Internet | {tel_lakhs} | - |
595
+ | Travelling and Conveyance | {trav_lakhs} | - |
596
+ | Translation Charges | {tl_lakhs} | - |
597
+ | Electricity Charges | {elec_lakhs} | - |
598
+ | Security Charges | {sec_lakhs} | - |
599
+ | Annual Maintenance Charges | {maint_lakhs} | - |
600
+ | Repairs and maintenance | | |
601
+ | - Electrical | {relec_lakhs} | - |
602
+ | - Office | {roff_lakhs} | - |
603
+ | - Machinery | {rmach_lakhs} | - |
604
+ | - Vehicles | {rveh_lakhs} | - |
605
+ | - Others | {roth_lakhs} | - |
606
+ | Business Development Expenses| {bd_lakhs} | - |
607
+ | Professional & Consultancy Fees | {prof_lakhs}| - |
608
+ | Payment to Auditors | {aud_lakhs} | - |
609
+ | Bad Debts Written Off | {bd_debts_lakhs}| - |
610
+ | Fire Extinguishers Refilling Charges | {fire_lakhs} | - |
611
+ | Food Expenses for Guests | {food_lakhs} | - |
612
+ | Diesel Expenses | {diesel_lakhs} | - |
613
+ | Interest Under 234 C Fy 2021-22 | {int234c_lakhs} | - |
614
+ | Loan Processing Charges | {loan_lakhs} | - |
615
+ | Sitting Fee of Directors | {sit_lakhs} | - |
616
+ | Customs Duty Payment | {cust_lakhs} | - |
617
+ | Transportation and Unloading Charges | {trans_lakhs} | - |
618
+ | Software Equipment | {soft_lakhs} | - |
619
+ | Miscellaneous expenses | {misc_lakhs} | - |
620
+ | **Total** | {total_lakhs} | - |
621
+ """.format(ba_be_noc_lakhs=to_lakhs(ba_be_noc), ba_exp_lakhs=to_lakhs(ba_expenses), vol_lakhs=to_lakhs(volunteers),
622
+ oo_lakhs=to_lakhs(other_operating), lab_lakhs=to_lakhs(lab_testing), rent_lakhs=to_lakhs(rent),
623
+ rt_lakhs=to_lakhs(rates_taxes), fl_lakhs=to_lakhs(fees_licenses), ins_lakhs=to_lakhs(insurance),
624
+ mem_lakhs=to_lakhs(membership), post_lakhs=to_lakhs(postage), print_lakhs=to_lakhs(printing),
625
+ csr_lakhs=to_lakhs(csr), tel_lakhs=to_lakhs(telephone), trav_lakhs=to_lakhs(travelling),
626
+ tl_lakhs=to_lakhs(translation), elec_lakhs=to_lakhs(electricity), sec_lakhs=to_lakhs(security),
627
+ maint_lakhs=to_lakhs(maintenance), relec_lakhs=to_lakhs(repairs_electrical), roff_lakhs=to_lakhs(repairs_office),
628
+ rmach_lakhs=to_lakhs(repairs_machinery), rveh_lakhs=to_lakhs(repairs_vehicles), roth_lakhs=to_lakhs(repairs_others),
629
+ bd_lakhs=to_lakhs(business_dev), prof_lakhs=to_lakhs(professional), aud_lakhs=to_lakhs(auditors),
630
+ bd_debts_lakhs=to_lakhs(bad_debts), fire_lakhs=to_lakhs(fire_extinguishers), food_lakhs=to_lakhs(food_guests),
631
+ diesel_lakhs=to_lakhs(diesel), int234c_lakhs=to_lakhs(interest_234c), loan_lakhs=to_lakhs(loan_processing),
632
+ sit_lakhs=to_lakhs(sitting_fee), cust_lakhs=to_lakhs(customs_duty), trans_lakhs=to_lakhs(transportation),
633
+ soft_lakhs=to_lakhs(software), misc_lakhs=to_lakhs(misc), total_lakhs=to_lakhs(total))
634
+ special_data = {
635
+ "breakdown": {
636
+ "ba_be_noc": {"description": "BA / BE NOC Charges", "amount": ba_be_noc, "amount_lakhs": to_lakhs(ba_be_noc)},
637
+ "ba_expenses": {"description": "BA Expenses", "amount": ba_expenses, "amount_lakhs": to_lakhs(ba_expenses)},
638
+ "volunteers": {"description": "Payments to Volunteers", "amount": volunteers, "amount_lakhs": to_lakhs(volunteers)},
639
+ "other_operating": {"description": "Other Operating Expenses", "amount": other_operating, "amount_lakhs": to_lakhs(other_operating)},
640
+ "lab_testing": {"description": "Laboratory testing charges", "amount": lab_testing, "amount_lakhs": to_lakhs(lab_testing)},
641
+ "rent": {"description": "Rent", "amount": rent, "amount_lakhs": to_lakhs(rent)},
642
+ "rates_taxes": {"description": "Rates & Taxes", "amount": rates_taxes, "amount_lakhs": to_lakhs(rates_taxes)},
643
+ "fees_licenses": {"description": "Fees & licenses", "amount": fees_licenses, "amount_lakhs": to_lakhs(fees_licenses)},
644
+ "insurance": {"description": "Insurance", "amount": insurance, "amount_lakhs": to_lakhs(insurance)},
645
+ "membership": {"description": "Membership & Subscription Charges", "amount": membership, "amount_lakhs": to_lakhs(membership)},
646
+ "postage": {"description": "Postage & Communication Cost", "amount": postage, "amount_lakhs": to_lakhs(postage)},
647
+ "printing": {"description": "Printing and stationery", "amount": printing, "amount_lakhs": to_lakhs(printing)},
648
+ "csr": {"description": "CSR Fund Expenses", "amount": csr, "amount_lakhs": to_lakhs(csr)},
649
+ "telephone": {"description": "Telephone & Internet", "amount": telephone, "amount_lakhs": to_lakhs(telephone)},
650
+ "travelling": {"description": "Travelling and Conveyance", "amount": travelling, "amount_lakhs": to_lakhs(travelling)},
651
+ "translation": {"description": "Translation Charges", "amount": translation, "amount_lakhs": to_lakhs(translation)},
652
+ "electricity": {"description": "Electricity Charges", "amount": electricity, "amount_lakhs": to_lakhs(electricity)},
653
+ "security": {"description": "Security Charges", "amount": security, "amount_lakhs": to_lakhs(security)},
654
+ "maintenance": {"description": "Annual Maintenance Charges", "amount": maintenance, "amount_lakhs": to_lakhs(maintenance)},
655
+ "repairs_electrical": {"description": "Repairs and maintenance - Electrical", "amount": repairs_electrical, "amount_lakhs": to_lakhs(repairs_electrical)},
656
+ "repairs_office": {"description": "Repairs and maintenance - Office", "amount": repairs_office, "amount_lakhs": to_lakhs(repairs_office)},
657
+ "repairs_machinery": {"description": "Repairs and maintenance - Machinery", "amount": repairs_machinery, "amount_lakhs": to_lakhs(repairs_machinery)},
658
+ "repairs_vehicles": {"description": "Repairs and maintenance - Vehicles", "amount": repairs_vehicles, "amount_lakhs": to_lakhs(repairs_vehicles)},
659
+ "repairs_others": {"description": "Repairs and maintenance - Others", "amount": repairs_others, "amount_lakhs": to_lakhs(repairs_others)},
660
+ "business_dev": {"description": "Business Development Expenses", "amount": business_dev, "amount_lakhs": to_lakhs(business_dev)},
661
+ "professional": {"description": "Professional & Consultancy Fees", "amount": professional, "amount_lakhs": to_lakhs(professional)},
662
+ "auditors": {"description": "Payment to Auditors", "amount": auditors, "amount_lakhs": to_lakhs(auditors)},
663
+ "bad_debts": {"description": "Bad Debts Written Off", "amount": bad_debts, "amount_lakhs": to_lakhs(bad_debts)},
664
+ "fire_extinguishers": {"description": "Fire Extinguishers Refilling Charges", "amount": fire_extinguishers, "amount_lakhs": to_lakhs(fire_extinguishers)},
665
+ "food_guests": {"description": "Food Expenses for Guests", "amount": food_guests, "amount_lakhs": to_lakhs(food_guests)},
666
+ "diesel": {"description": "Diesel Expenses", "amount": diesel, "amount_lakhs": to_lakhs(diesel)},
667
+ "interest_234c": {"description": "Interest Under 234 C Fy 2021-22", "amount": interest_234c, "amount_lakhs": to_lakhs(interest_234c)},
668
+ "loan_processing": {"description": "Loan Processing Charges", "amount": loan_processing, "amount_lakhs": to_lakhs(loan_processing)},
669
+ "sitting_fee": {"description": "Sitting Fee of Directors", "amount": sitting_fee, "amount_lakhs": to_lakhs(sitting_fee)},
670
+ "customs_duty": {"description": "Customs Duty Payment", "amount": customs_duty, "amount_lakhs": to_lakhs(customs_duty)},
671
+ "transportation": {"description": "Transportation and Unloading Charges", "amount": transportation, "amount_lakhs": to_lakhs(transportation)},
672
+ "software": {"description": "Software Equipment", "amount": software, "amount_lakhs": to_lakhs(software)},
673
+ "misc": {"description": "Miscellaneous expenses", "amount": misc, "amount_lakhs": to_lakhs(misc)}
674
+ }
675
+ }
676
+ content += "\n* Fees is net of GST which is taken as input tax credit."
677
+
678
+ elif note_name == '21. Depreciation and Amortisation Expense':
679
+ depreciation = calculate_note(tb_df, note_name, ['Depreciation', 'Accumulated Depreciation', 'Depreciation And Amortisation'])['total']
680
+ amortization = calculate_note(tb_df, note_name, ['Amortization'])['total']
681
+ total = result['total'] # Use total from calculate_note
682
+ content = """
683
+ | Particulars | March 31, 2024 | March 31, 2023 |
684
+ |------------------------------|----------------|----------------|
685
+ | Depreciation and amortisation | {total_lakhs} | - |
686
+ | **Total** | {total_lakhs} | - |
687
+ """.format(total_lakhs=to_lakhs(total))
688
+ special_data = {
689
+ "breakdown": {
690
+ "depreciation": {"description": "Depreciation", "amount": depreciation, "amount_lakhs": to_lakhs(depreciation)},
691
+ "amortization": {"description": "Amortization", "amount": amortization, "amount_lakhs": to_lakhs(amortization)}
692
+ }
693
+ }
694
+
695
+ elif note_name == '22. Loss on Sale of Assets & Investments':
696
+ short_term_loss = calculate_note(tb_df, note_name, ['Short Term Loss on Sale of Investments'])['total']
697
+ long_term_loss = calculate_note(tb_df, note_name, ['Long term loss on sale of investments'])['total']
698
+ fixed_assets_loss = calculate_note(tb_df, note_name, ['Loss on Sale of Fixed Assets'])['total']
699
+ total = result['total'] # Use total from calculate_note
700
+ content = """
701
+ | Particulars | March 31, 2024 | March 31, 2023 |
702
+ |------------------------------|----------------|----------------|
703
+ | Short Term Loss on Sale of Investments (Non Derivative Loss) | {stl_lakhs} | - |
704
+ | Long term loss on sale of investments | {ltl_lakhs} | - |
705
+ | Loss on Sale of Fixed Assets | {fal_lakhs} | - |
706
+ | **Total** | {total_lakhs} | - |
707
+ """.format(stl_lakhs=to_lakhs(short_term_loss), ltl_lakhs=to_lakhs(long_term_loss), fal_lakhs=to_lakhs(fixed_assets_loss), total_lakhs=to_lakhs(total))
708
+ special_data = {
709
+ "breakdown": {
710
+ "short_term_loss": {"description": "Short Term Loss on Sale of Investments", "amount": short_term_loss, "amount_lakhs": to_lakhs(short_term_loss)},
711
+ "long_term_loss": {"description": "Long term loss on sale of investments", "amount": long_term_loss, "amount_lakhs": to_lakhs(long_term_loss)},
712
+ "fixed_assets_loss": {"description": "Loss on Sale of Fixed Assets", "amount": fixed_assets_loss, "amount_lakhs": to_lakhs(fixed_assets_loss)}
713
+ }
714
+ }
715
+
716
+ elif note_name == '23. Finance Costs':
717
+ bank_finance = calculate_note(tb_df, note_name, ['Bank Charges', 'Finance Charges', 'Interest', 'Interest and penalty', 'Interest on TDS'])['total']
718
+ loan_processing = calculate_note(tb_df, note_name, ['Loan Processing'])['total']
719
+ total = result['total'] # Use total from calculate_note
720
+ content = """
721
+ | Particulars | March 31, 2024 | March 31, 2023 |
722
+ |------------------------------|----------------|----------------|
723
+ | Bank and Finance Charges | {bf_lakhs} | - |
724
+ | **Total** | {total_lakhs} | - |
725
+ """.format(bf_lakhs=to_lakhs(bank_finance), lp_lakhs=to_lakhs(loan_processing), total_lakhs=to_lakhs(total))
726
+ special_data = {
727
+ "breakdown": {
728
+ "bank_finance": {"description": "Bank & Finance Charges", "amount": bank_finance, "amount_lakhs": to_lakhs(bank_finance)},
729
+ "loan_processing": {"description": "Loan Processing Charges", "amount": loan_processing, "amount_lakhs": to_lakhs(loan_processing)}
730
+ }
731
+ }
732
+
733
+ elif note_name == '24. Payment to Auditor':
734
+ audit_fee = calculate_note(tb_df, note_name, ['Audit Fee', 'Payment to Auditors'])['total']
735
+ tax_audit = calculate_note(tb_df, note_name, ['Tax Audit', 'Certification Fees'])['total']
736
+ total = result['total'] # Use total from calculate_note
737
+ content = """
738
+ | Particulars | March 31, 2024 | March 31, 2023 |
739
+ |------------------------------|----------------|----------------|
740
+ | - For Audit fee | {audit_lakhs} | - |
741
+ | - For Tax Audit / Certification Fees | {tax_lakhs} | - |
742
+ | **Total** | {total_lakhs} | - |
743
+ """.format(audit_lakhs=to_lakhs(audit_fee), tax_lakhs=to_lakhs(tax_audit), total_lakhs=to_lakhs(total))
744
+ special_data = {
745
+ "breakdown": {
746
+ "audit_fee": {"description": "For Audit fee", "amount": audit_fee, "amount_lakhs": to_lakhs(audit_fee)},
747
+ "tax_audit": {"description": "For Tax Audit / Certification Fees", "amount": tax_audit, "amount_lakhs": to_lakhs(tax_audit)}
748
+ }
749
+ }
750
+
751
+ elif note_name == '25. Earnings in Foreign Currency':
752
+ export_income = calculate_note(tb_df, note_name, ['Income from export of services', 'Servicing of BA/BE PROJECTS EXPORT', 'Working Standards - Export'])['total']
753
+ total = result['total'] # Use total from calculate_note
754
+ content = """
755
+ | Particulars | March 31, 2024 | March 31, 2023 |
756
+ |------------------------------|----------------|----------------|
757
+ | **Inflow :** | | |
758
+ | Income from export of services | {exp_lakhs} | - |
759
+ | **Total** | {total_lakhs} | - |
760
+ """.format(exp_lakhs=to_lakhs(export_income), total_lakhs=to_lakhs(total))
761
+ special_data = {
762
+ "breakdown": {
763
+ "export_income": {"description": "Income from export of services", "amount": export_income, "amount_lakhs": to_lakhs(export_income)}
764
+ }
765
+ }
766
+
767
+ elif note_name == '26. Particulars of Un-hedged Foreign Currency Exposure':
768
+ export_income = calculate_note(tb_df, note_name, ['Income from export of services', 'Servicing of BA/BE PROJECTS EXPORT', 'Working Standards - Export'])['total']
769
+ total = result['total'] # Use total from calculate_note
770
+ content = """
771
+ "(i) There is no derivate contract outstanding as at the Balance Sheet date.
772
+ (ii) Particulars of un-hedged foreign currency exposure as at the Balance Sheet date"
773
+
774
+ | Particulars | March 31, 2024 | March 31, 2023 |
775
+ |------------------------------|----------------|----------------|
776
+ | **Inflow :** | | |
777
+ | Income from export of services | {exp_lakhs} | - |
778
+ | **Total** | {total_lakhs} | - |
779
+ """.format(exp_lakhs=to_lakhs(export_income), total_lakhs=to_lakhs(total))
780
+ special_data = {
781
+ "breakdown": {
782
+ "export_income": {"description": "Income from export of services", "amount": export_income, "amount_lakhs": to_lakhs(export_income)}
783
+ }
784
+ }
785
+ elif note_name == '28. Earnings per Share':
786
+ content = """
787
+ | Particulars | March 31, 2024 | March 31, 2023 |
788
+ |------------------------------|----------------|----------------|
789
+ | Earnings per Share | {total_lakhs} | - |
790
+ """.format(total_lakhs=to_lakhs(result['total']))
791
+
792
+ elif note_name == '29. Related Party Disclosures':
793
+ content = """
794
+ | Particulars | March 31, 2024 | March 31, 2023 |
795
+ |------------------------------|----------------|----------------|
796
+ | Related Party Disclosures | {total_lakhs} | - |
797
+ """.format(total_lakhs=to_lakhs(result['total']))
798
+
799
+ elif note_name == '30. Financial Ratios':
800
+ current_assets = sum(calculate_note(tb_df, note_name, [kw])['total'] for kw in ['Stock', 'Cash', 'Bank', 'Receivables', 'Prepaid'])
801
+ current_liabilities = sum(calculate_note(tb_df, note_name, [kw])['total'] for kw in ['Creditors', 'Payable'])
802
+ current_ratio = current_assets / abs(current_liabilities) if current_liabilities != 0 else 0
803
+ content = """
804
+ | Particulars | 2024-03-31 | 2023-03-31 |
805
+ |------------------------------|------------|------------|
806
+ | Current Ratio | {cr} | 2.52 |
807
+ | Current Assets | {ca_lakhs} | - |
808
+ | Current Liabilities | {cl_lakhs} | - |
809
+ """.format(cr=round(current_ratio, 2), ca_lakhs=to_lakhs(current_assets), cl_lakhs=to_lakhs(abs(current_liabilities)))
810
+ special_data = {
811
+ "breakdown": {
812
+ "current_assets": {"description": "Current Assets", "amount": current_assets, "amount_lakhs": to_lakhs(current_assets)},
813
+ "current_liabilities": {"description": "Current Liabilities", "amount": abs(current_liabilities), "amount_lakhs": to_lakhs(abs(current_liabilities))},
814
+ "current_ratio": {"description": "Current Ratio", "value": round(current_ratio, 2)}
815
+ }
816
+ }
817
+
818
+ else:
819
+ content = """
820
+ | Particulars | March 31, 2024 | March 31, 2023 |
821
+ |------------------------------|----------------|----------------|
822
+ | {title} | {total_lakhs} | - |
823
+ """.format(title=note_name.split('.', 1)[1].strip() if '.' in note_name else note_name, total_lakhs=to_lakhs(result['total']))
824
+
825
+
826
+ detailed_note = create_detailed_note_structure(note_name, result, content, special_data)
827
+ notes.append(detailed_note)
828
+
829
+ return {
830
+ "metadata": {
831
+ "generated_on": datetime.now().isoformat(),
832
+ "financial_year": settings.financial_year,
833
+ "company_name": settings.company_name,
834
+ "total_notes": len(notes)
835
+ },
836
+ "notes": notes
837
+ }
838
+
839
+ def process_json(json_path: str) -> None:
840
+ """
841
+ Loads the JSON file, processes it, and writes the output as in your main().
842
+ """
843
+ if not os.path.exists(json_path):
844
+ logger.error(f"{json_path} not found!")
845
+ raise FileNotFoundError(f"{json_path} not found!")
846
+ with open(json_path, "r", encoding="utf-8") as f:
847
+ parsed_data = json.load(f)
848
+ if isinstance(parsed_data, list):
849
+ tb_df = pd.DataFrame(parsed_data)
850
+ else:
851
+ tb_records = parsed_data.get("trial_balance", parsed_data)
852
+ tb_df = pd.DataFrame(tb_records)
853
+ if 'amount' in tb_df.columns:
854
+ tb_df['amount'] = tb_df['amount'].apply(clean_value)
855
+ notes_data = generate_notes(tb_df)
856
+ os.makedirs(os.path.dirname(settings.output_json), exist_ok=True)
857
+ with open(settings.output_json, "w", encoding="utf-8") as f:
858
+ json.dump(notes_data, f, ensure_ascii=False, indent=2)
859
+ logger.info(f"Notes output written to {settings.output_json}")
860
+
861
+ def main() -> None:
862
+ """Main execution function."""
863
+ try:
864
+ json_file = settings.trial_balance_json
865
+ if not os.path.exists(json_file):
866
+ logger.error(f"{json_file} not found! Please run test_mapping.py first.")
867
+ raise FileNotFoundError(f"{json_file} not found! Please run test_mapping.py first.")
868
+ logger.info(f"Loading data from {json_file}...")
869
+ with open(json_file, "r", encoding="utf-8") as f:
870
+ parsed_data = json.load(f)
871
+ if isinstance(parsed_data, list):
872
+ tb_df = pd.DataFrame(parsed_data)
873
+ else:
874
+ tb_records = parsed_data.get("trial_balance", parsed_data)
875
+ tb_df = pd.DataFrame(tb_records)
876
+ logger.info(f"Loaded {len(tb_df)} records from trial balance")
877
+ logger.info(f"Columns available: {tb_df.columns.tolist()}")
878
+ if 'account_name' not in tb_df.columns or 'amount' not in tb_df.columns:
879
+ logger.error("JSON must have 'account_name' and 'amount' columns")
880
+ raise ValueError("JSON must have 'account_name' and 'amount' columns")
881
+ tb_df['amount'] = tb_df['amount'].apply(clean_value)
882
+ notes_data = generate_notes(tb_df)
883
+ os.makedirs(os.path.dirname(settings.output_md), exist_ok=True)
884
+ output_md = "# Notes to Financial Statements for the Year Ended March 31, 2024\n\n"
885
+ logger.info(f"Generated {len(notes_data['notes'])} notes")
886
+ for note in notes_data['notes']:
887
+ output_md += f"{note['markdown_content']}\n"
888
+ if note['total_amount'] != 0:
889
+ logger.info(f"{note['full_title']}: ₹{note['total_amount']:,.2f} ({note['matched_accounts_count']} accounts)")
890
+ else:
891
+ logger.warning(f"{note['full_title']}: No matching accounts found")
892
+ with open(settings.output_md, "w", encoding="utf-8") as f:
893
+ f.write(output_md)
894
+ with open(settings.output_json, "w", encoding="utf-8") as f:
895
+ json.dump(notes_data, f, ensure_ascii=False, indent=2)
896
+ logger.info("Notes generated successfully!")
897
+ logger.info(f"Markdown: {settings.output_md}")
898
+ logger.info(f"JSON: {settings.output_json}")
899
+ except Exception as e:
900
+ logger.error(f"Error: {str(e)}")
901
+ if 'tb_df' in locals():
902
+ logger.info("Sample trial balance data:")
903
+ logger.info(tb_df.head().to_string())
904
+
905
+ if __name__ == "__main__":
906
+ main()
app/new.py ADDED
@@ -0,0 +1,1799 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import logging
3
+ from datetime import datetime
4
+ from typing import Dict, Any, List, Optional
5
+ from pydantic import BaseModel, Field, ValidationError
6
+ from pydantic_settings import BaseSettings
7
+
8
+ # Configure logging
9
+ logging.basicConfig(level=logging.INFO)
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class Settings(BaseSettings):
13
+ """Application settings loaded from environment variables or .env file."""
14
+ generated_on: str = datetime.now().isoformat()
15
+
16
+ settings = Settings()
17
+
18
+ class Subcategory(BaseModel):
19
+ label: str
20
+ value: Optional[str] = None
21
+ previous_value: Optional[str] = None
22
+ sub_label: Optional[str] = None
23
+ columns: Optional[List[Dict[str, Any]]] = None
24
+ values: Optional[List[Dict[str, Any]]] = None
25
+
26
+ class Category(BaseModel):
27
+ category: str
28
+ subcategories: List[Subcategory]
29
+ total: Optional[str] = None
30
+ previous_total: Optional[str] = None
31
+
32
+ class NoteMetadata(BaseModel):
33
+ note_number: str
34
+ generated_on: str
35
+
36
+ class NoteTemplate(BaseModel):
37
+ title: str
38
+ full_title: str
39
+ structure: List[Category]
40
+ metadata: NoteMetadata
41
+ notes_and_disclosures: Optional[List[str]] = None
42
+
43
+ def validate_note_templates(note_templates: Dict[str, Any]) -> Dict[str, NoteTemplate]:
44
+ """
45
+ Validate and parse note_templates dict into Pydantic models.
46
+ Returns a dict of validated NoteTemplate objects.
47
+ """
48
+ validated_templates = {}
49
+ for key, value in note_templates.items():
50
+ try:
51
+ # Ensure generated_on is set from settings if not present
52
+ if "metadata" in value and "generated_on" in value["metadata"]:
53
+ value["metadata"]["generated_on"] = settings.generated_on
54
+ validated_templates[key] = NoteTemplate(**value)
55
+ except ValidationError as ve:
56
+ logger.warning(f"Validation error for note {key}: {ve}")
57
+ return validated_templates
58
+
59
+ # The original note_templates dict (unchanged, but can be loaded from a JSON file if preferred)
60
+ note_templates = {
61
+ "2": {
62
+ "title": "Share Capital",
63
+ "full_title": "2. Share Capital",
64
+ "structure": [
65
+ {
66
+ "category": "In Lakhs",
67
+ "subcategories": [
68
+ {
69
+ "label": "March 31, 2024",
70
+ "value": "{march_2024_total}"
71
+ },
72
+ {
73
+ "label": "March 31, 2023",
74
+ "value": "{march_2023_total}"
75
+ }
76
+ ]
77
+ },
78
+ {
79
+ "category": "Authorised Share Capital",
80
+ "subcategories": [
81
+ {
82
+ "label": "Equity Shares of ₹10 each",
83
+ "value": "{authorised_equity_2024}",
84
+ "previous_value": "{authorised_equity_2023}"
85
+ }
86
+ ],
87
+ "total": "{authorised_total_2024}",
88
+ "previous_total": "{authorised_total_2023}"
89
+ },
90
+ {
91
+ "category": "Issued, Subscribed and Paid-up Share Capital",
92
+ "subcategories": [
93
+ {
94
+ "label": "Equity Shares of ₹10 each fully paid up",
95
+ "value": "{issued_equity_2024}",
96
+ "previous_value": "{issued_equity_2023}"
97
+ }
98
+ ],
99
+ "total": "{issued_total_2024}",
100
+ "previous_total": "{issued_total_2023}"
101
+ },
102
+ {
103
+ "category": "Reconciliation of Shares",
104
+ "subcategories": [
105
+ {
106
+ "label": "Number of Shares at the beginning",
107
+ "value": "{shares_beginning_2024}",
108
+ "previous_value": "{shares_beginning_2023}"
109
+ },
110
+ {
111
+ "label": "Changes during the year",
112
+ "value": "{shares_changes_2024}",
113
+ "previous_value": "{shares_changes_2023}"
114
+ },
115
+ {
116
+ "label": "Number of Shares at the end",
117
+ "value": "{shares_end_2024}",
118
+ "previous_value": "{shares_end_2023}"
119
+ }
120
+ ]
121
+ }
122
+ ],
123
+ "metadata": {
124
+ "note_number": "2",
125
+ "generated_on": "{generated_on}"
126
+ }
127
+ },
128
+ "3": {
129
+ "title": "Reserves and Surplus",
130
+ "full_title": "3. Reserves and Surplus",
131
+ "structure": [
132
+ {
133
+ "category": "In Lakhs",
134
+ "subcategories": [
135
+ {
136
+ "label": "March 31, 2024",
137
+ "value": "{march_2024_total}"
138
+ },
139
+ {
140
+ "label": "March 31, 2023",
141
+ "value": "{march_2023_total}"
142
+ }
143
+ ]
144
+ },
145
+ {
146
+ "category": "",
147
+ "subcategories": [
148
+ {
149
+ "label": "Share Premium Account",
150
+ "value": "{share_premium_2024}",
151
+ "previous_value": "{share_premium_2023}"
152
+ },
153
+ {
154
+ "label": "Surplus in Statement of Profit and Loss",
155
+ "value": "{surplus_profit_loss_2024}",
156
+ "previous_value": "{surplus_profit_loss_2023}"
157
+ }
158
+ ],
159
+ "total": "{total_2024}",
160
+ "previous_total": "{total_2023}"
161
+ }
162
+ ],
163
+ "metadata": {
164
+ "note_number": "3",
165
+ "generated_on": "{generated_on}"
166
+ }
167
+ },
168
+ "4": {
169
+ "title": "Long Term Borrowings",
170
+ "full_title": "4. Long Term Borrowings",
171
+ "structure": [
172
+ {
173
+ "category": "In Lakhs",
174
+ "subcategories": [
175
+ {
176
+ "label": "March 31, 2024",
177
+ "value": "{march_2024_total}"
178
+ },
179
+ {
180
+ "label": "March 31, 2023",
181
+ "value": "{march_2023_total}"
182
+ }
183
+ ]
184
+ },
185
+ {
186
+ "category": "Secured",
187
+ "subcategories": [
188
+ {
189
+ "label": "Term Loans from Banks",
190
+ "value": "{term_loans_banks_2024}",
191
+ "previous_value": "{term_loans_banks_2023}"
192
+ }
193
+ ],
194
+ "total": "{secured_total_2024}",
195
+ "previous_total": "{secured_total_2023}"
196
+ }
197
+ ],
198
+ "metadata": {
199
+ "note_number": "4",
200
+ "generated_on": "{generated_on}"
201
+ }
202
+ },
203
+ "5": {
204
+ "title": "Deferred Tax Liability (Net)",
205
+ "full_title": "5. Deferred Tax Liability (Net)",
206
+ "structure": [
207
+ {
208
+ "category": "In Lakhs",
209
+ "subcategories": [
210
+ {
211
+ "label": "March 31, 2024",
212
+ "value": "{march_2024_total}"
213
+ },
214
+ {
215
+ "label": "March 31, 2023",
216
+ "value": "{march_2023_total}"
217
+ }
218
+ ]
219
+ },
220
+ {
221
+ "category": "",
222
+ "subcategories": [
223
+ {
224
+ "label": "Deferred Tax Liability",
225
+ "value": "{deferred_tax_liability_2024}",
226
+ "previous_value": "{deferred_tax_liability_2023}"
227
+ }
228
+ ],
229
+ "total": "{total_2024}",
230
+ "previous_total": "{total_2023}"
231
+ }
232
+ ],
233
+ "metadata": {
234
+ "note_number": "5",
235
+ "generated_on": "{generated_on}"
236
+ }
237
+ },
238
+ "6": {
239
+ "title": "Trade Payables",
240
+ "full_title": "6. Trade Payables",
241
+ "structure": [
242
+ {
243
+ "category": "In Lakhs",
244
+ "subcategories": [
245
+ {
246
+ "label": "March 31, 2024",
247
+ "value": "{march_2024_total}"
248
+ },
249
+ {
250
+ "label": "March 31, 2023",
251
+ "value": "{march_2023_total}"
252
+ }
253
+ ]
254
+ },
255
+ {
256
+ "category": "Unsecured, considered good",
257
+ "subcategories": [
258
+ {
259
+ "label": "Outstanding for a period exceeding six months from the date they are due for payment",
260
+ "value": "{over_six_months_2024}",
261
+ "previous_value": "{over_six_months_2023}"
262
+ },
263
+ {
264
+ "label": "Other payables",
265
+ "value": "{other_payables_2024}",
266
+ "previous_value": "{other_payables_2023}"
267
+ }
268
+ ],
269
+ "total": "{unsecured_total_2024}",
270
+ "previous_total": "{unsecured_total_2023}"
271
+ },
272
+ {
273
+ "category": "Age wise analysis of Trade payables as on 31.03.2024",
274
+ "subcategories": [
275
+ {
276
+ "label": "Particulars",
277
+ "sub_label": "Outstanding for following periods from due date of payment",
278
+ "columns": [
279
+ {"header": "0 - 6 months", "value": "{zero_six_2024}"},
280
+ {"header": "6 months - 1 Year", "value": "{six_one_2024}"},
281
+ {"header": "1 - 2 Years", "value": "{one_two_2024}"},
282
+ {"header": "2 - 3 Years", "value": "{two_three_2024}"},
283
+ {"header": "More than 3 Years", "value": "{more_three_2024}"},
284
+ {"header": "Total", "value": "{age_total_2024}"}
285
+ ]
286
+ },
287
+ {
288
+ "label": "Undisputed",
289
+ "sub_label": "- Considered good",
290
+ "values": [
291
+ {"period": "0 - 6 months", "value": "{undisputed_good_zero_six_2024}"},
292
+ {"period": "6 months - 1 Year", "value": "{undisputed_good_six_one_2024}"},
293
+ {"period": "1 - 2 Years", "value": "{undisputed_good_one_two_2024}"},
294
+ {"period": "2 - 3 Years", "value": "{undisputed_good_two_three_2024}"},
295
+ {"period": "More than 3 Years", "value": "{undisputed_good_more_three_2024}"},
296
+ {"period": "Total", "value": "{undisputed_good_total_2024}"}
297
+ ]
298
+ },
299
+ {
300
+ "label": "Disputed",
301
+ "sub_label": "- Considered good",
302
+ "values": [
303
+ {"period": "0 - 6 months", "value": "{disputed_good_zero_six_2024}"},
304
+ {"period": "6 months - 1 Year", "value": "{disputed_good_six_one_2024}"},
305
+ {"period": "1 - 2 Years", "value": "{disputed_good_one_two_2024}"},
306
+ {"period": "2 - 3 Years", "value": "{disputed_good_two_three_2024}"},
307
+ {"period": "More than 3 Years", "value": "{disputed_good_more_three_2024}"},
308
+ {"period": "Total", "value": "{disputed_good_total_2024}"}
309
+ ]
310
+ },
311
+ {
312
+ "label": "Total",
313
+ "values": [
314
+ {"period": "0 - 6 months", "value": "{total_zero_six_2024}"},
315
+ {"period": "6 months - 1 Year", "value": "{total_six_one_2024}"},
316
+ {"period": "1 - 2 Years", "value": "{total_one_two_2024}"},
317
+ {"period": "2 - 3 Years", "value": "{total_two_three_2024}"},
318
+ {"period": "More than 3 Years", "value": "{total_more_three_2024}"},
319
+ {"period": "Total", "value": "{age_total_2024}"}
320
+ ]
321
+ }
322
+ ]
323
+ },
324
+ {
325
+ "category": "Age wise analysis of Trade payables as on 31.03.2023",
326
+ "subcategories": [
327
+ {
328
+ "label": "Particulars",
329
+ "sub_label": "Outstanding for following periods from due date of payment",
330
+ "columns": [
331
+ {"header": "0 - 6 months", "value": "{zero_six_2023}"},
332
+ {"header": "6 months - 1 Year", "value": "{six_one_2023}"},
333
+ {"header": "1 - 2 Years", "value": "{one_two_2023}"},
334
+ {"header": "2 - 3 Years", "value": "{two_three_2023}"},
335
+ {"header": "More than 3 Years", "value": "{more_three_2023}"},
336
+ {"header": "Total", "value": "{age_total_2023}"}
337
+ ]
338
+ },
339
+ {
340
+ "label": "Undisputed",
341
+ "sub_label": "- Considered good",
342
+ "values": [
343
+ {"period": "0 - 6 months", "value": "{undisputed_good_zero_six_2023}"},
344
+ {"period": "6 months - 1 Year", "value": "{undisputed_good_six_one_2023}"},
345
+ {"period": "1 - 2 Years", "value": "{undisputed_good_one_two_2023}"},
346
+ {"period": "2 - 3 Years", "value": "{undisputed_good_two_three_2023}"},
347
+ {"period": "More than 3 Years", "value": "{undisputed_good_more_three_2023}"},
348
+ {"period": "Total", "value": "{undisputed_good_total_2023}"}
349
+ ]
350
+ },
351
+ {
352
+ "label": "Disputed",
353
+ "sub_label": "- Considered good",
354
+ "values": [
355
+ {"period": "0 - 6 months", "value": "{disputed_good_zero_six_2023}"},
356
+ {"period": "6 months - 1 Year", "value": "{disputed_good_six_one_2023}"},
357
+ {"period": "1 - 2 Years", "value": "{disputed_good_one_two_2023}"},
358
+ {"period": "2 - 3 Years", "value": "{disputed_good_two_three_2023}"},
359
+ {"period": "More than 3 Years", "value": "{disputed_good_more_three_2023}"},
360
+ {"period": "Total", "value": "{disputed_good_total_2023}"}
361
+ ]
362
+ },
363
+ {
364
+ "label": "Total",
365
+ "values": [
366
+ {"period": "0 - 6 months", "value": "{total_zero_six_2023}"},
367
+ {"period": "6 months - 1 Year", "value": "{total_six_one_2023}"},
368
+ {"period": "1 - 2 Years", "value": "{total_one_two_2023}"},
369
+ {"period": "2 - 3 Years", "value": "{total_two_three_2023}"},
370
+ {"period": "More than 3 Years", "value": "{total_more_three_2023}"},
371
+ {"period": "Total", "value": "{age_total_2023}"}
372
+ ]
373
+ }
374
+ ]
375
+ }
376
+ ],
377
+ "metadata": {
378
+ "note_number": "6",
379
+ "generated_on": "{generated_on}"
380
+ }
381
+ },
382
+ "7": {
383
+ "title": "Other Current Liabilities",
384
+ "full_title": "7. Other Current Liabilities",
385
+ "structure": [
386
+ {
387
+ "category": "In Lakhs",
388
+ "subcategories": [
389
+ {
390
+ "label": "March 31, 2024",
391
+ "value": "{march_2024_total}"
392
+ },
393
+ {
394
+ "label": "March 31, 2023",
395
+ "value": "{march_2023_total}"
396
+ }
397
+ ]
398
+ },
399
+ {
400
+ "category": "",
401
+ "subcategories": [
402
+ {
403
+ "label": "Current Maturities of Long Term Borrowings",
404
+ "value": "{current_maturities_2024}",
405
+ "previous_value": "{current_maturities_2023}"
406
+ },
407
+ {
408
+ "label": "Outstanding Liabilities for Expenses",
409
+ "value": "{outstanding_liabilities_2024}",
410
+ "previous_value": "{outstanding_liabilities_2023}"
411
+ }
412
+ ],
413
+ "total": "{total_2024}",
414
+ "previous_total": "{total_2023}"
415
+ }
416
+ ],
417
+ "metadata": {
418
+ "note_number": "7",
419
+ "generated_on": "{generated_on}"
420
+ }
421
+ },
422
+ "8": {
423
+ "title": "Short Term Provisions",
424
+ "full_title": "8. Short Term Provisions",
425
+ "structure": [
426
+ {
427
+ "category": "In Lakhs",
428
+ "subcategories": [
429
+ {
430
+ "label": "March 31, 2024",
431
+ "value": "{march_2024_total}"
432
+ },
433
+ {
434
+ "label": "March 31, 2023",
435
+ "value": "{march_2023_total}"
436
+ }
437
+ ]
438
+ },
439
+ {
440
+ "category": "",
441
+ "subcategories": [
442
+ {
443
+ "label": "Provision for Taxation",
444
+ "value": "{provision_taxation_2024}",
445
+ "previous_value": "{provision_taxation_2023}"
446
+ }
447
+ ],
448
+ "total": "{total_2024}",
449
+ "previous_total": "{total_2023}"
450
+ }
451
+ ],
452
+ "metadata": {
453
+ "note_number": "8",
454
+ "generated_on": "{generated_on}"
455
+ }
456
+ },
457
+ "9": {
458
+ "title": "Fixed Assets",
459
+ "full_title": "9. Fixed Assets",
460
+ "structure": [
461
+ {
462
+ "category": "In Lakhs",
463
+ "subcategories": [
464
+ {
465
+ "label": "March 31, 2024",
466
+ "value": "{march_2024_total}"
467
+ },
468
+ {
469
+ "label": "March 31, 2023",
470
+ "value": "{march_2023_total}"
471
+ }
472
+ ]
473
+ },
474
+ {
475
+ "category": "Tangible Assets",
476
+ "subcategories": [
477
+ {
478
+ "label": "Gross Block",
479
+ "value": "{gross_block_2024}",
480
+ "previous_value": "{gross_block_2023}"
481
+ },
482
+ {
483
+ "label": "Accumulated Depreciation",
484
+ "value": "{accumulated_depreciation_2024}",
485
+ "previous_value": "{accumulated_depreciation_2023}"
486
+ },
487
+ {
488
+ "label": "Net Block",
489
+ "value": "{net_block_2024}",
490
+ "previous_value": "{net_block_2023}"
491
+ }
492
+ ],
493
+ "total": "{tangible_total_2024}",
494
+ "previous_total": "{tangible_total_2023}"
495
+ },
496
+ {
497
+ "category": "Intangible Assets",
498
+ "subcategories": [
499
+ {
500
+ "label": "Gross Block",
501
+ "value": "{intangible_gross_block_2024}",
502
+ "previous_value": "{intangible_gross_block_2023}"
503
+ },
504
+ {
505
+ "label": "Accumulated Amortisation",
506
+ "value": "{accumulated_amortisation_2024}",
507
+ "previous_value": "{accumulated_amortisation_2023}"
508
+ },
509
+ {
510
+ "label": "Net Block",
511
+ "value": "{intangible_net_block_2024}",
512
+ "previous_value": "{intangible_net_block_2023}"
513
+ }
514
+ ],
515
+ "total": "{intangible_total_2024}",
516
+ "previous_total": "{intangible_total_2023}"
517
+ },
518
+ {
519
+ "category": "Capital Work-in-Progress",
520
+ "subcategories": [
521
+ {
522
+ "label": "Capital Work-in-Progress",
523
+ "value": "{cwip_2024}",
524
+ "previous_value": "{cwip_2023}"
525
+ }
526
+ ],
527
+ "total": "{cwip_total_2024}",
528
+ "previous_total": "{cwip_total_2023}"
529
+ }
530
+ ],
531
+ "metadata": {
532
+ "note_number": "9",
533
+ "generated_on": "{generated_on}"
534
+ }
535
+ },
536
+ "10": {
537
+ "title": "Long Term Loans and Advances",
538
+ "full_title": "10. Long Term Loans and Advances",
539
+ "structure": [
540
+ {
541
+ "category": "In Lakhs",
542
+ "subcategories": [
543
+ {
544
+ "label": "March 31, 2024",
545
+ "value": "{march_2024_total}"
546
+ },
547
+ {
548
+ "label": "March 31, 2023",
549
+ "value": "{march_2023_total}"
550
+ }
551
+ ]
552
+ },
553
+ {
554
+ "category": "Unsecured, considered good",
555
+ "subcategories": [
556
+ {
557
+ "label": "Long Term - Security Deposits",
558
+ "value": "{security_deposits_2024}",
559
+ "previous_value": "{security_deposits_2023}"
560
+ }
561
+ ],
562
+ "total": "{unsecured_total_2024}",
563
+ "previous_total": "{unsecured_total_2023}"
564
+ }
565
+ ],
566
+ "metadata": {
567
+ "note_number": "10",
568
+ "generated_on": "{generated_on}"
569
+ }
570
+ },
571
+ "11": {
572
+ "title": "Inventories",
573
+ "full_title": "11. Inventories",
574
+ "structure": [
575
+ {
576
+ "category": "",
577
+ "subcategories": [
578
+ {
579
+ "label": "March 31, 2024",
580
+ "value": "{march_2024_total}"
581
+ },
582
+ {
583
+ "label": "March 31, 2023",
584
+ "value": "{march_2023_total}"
585
+ }
586
+ ]
587
+ },
588
+ {
589
+ "category": "Consumables",
590
+ "subcategories": [],
591
+ "total": "{consumables_2024}",
592
+ "previous_total": "{consumables_2023}"
593
+ }
594
+ ],
595
+ "metadata": {
596
+ "note_number": "11",
597
+ "generated_on": "{generated_on}"
598
+ }
599
+ },
600
+ "12": {
601
+ "title": "Trade Receivables",
602
+ "full_title": "12. Trade Receivables",
603
+ "structure": [
604
+ {
605
+ "category": "",
606
+ "subcategories": [
607
+ {
608
+ "label": "March 31, 2024",
609
+ "value": "{march_2024_total}"
610
+ },
611
+ {
612
+ "label": "March 31, 2023",
613
+ "value": "{march_2023_total}"
614
+ }
615
+ ]
616
+ },
617
+ {
618
+ "category": "Unsecured, considered good",
619
+ "subcategories": [
620
+ {
621
+ "label": "Outstanding for a period exceeding six months from the date they are due for payment",
622
+ "value": "{over_six_months_2024}",
623
+ "previous_value": "{over_six_months_2023}"
624
+ },
625
+ {
626
+ "label": "Other receivables",
627
+ "value": "{other_receivables_2024}",
628
+ "previous_value": "{other_receivables_2023}"
629
+ }
630
+ ],
631
+ "total": "{unsecured_total_2024}",
632
+ "previous_total": "{unsecured_total_2023}"
633
+ },
634
+ {
635
+ "category": "Age wise analysis of Trade receivables as on 31.03.2024",
636
+ "subcategories": [
637
+ {
638
+ "label": "Particulars",
639
+ "sub_label": "Outstanding for following periods from due date of payment",
640
+ "columns": [
641
+ {"header": "0 - 6 months", "value": "{zero_six_2024}"},
642
+ {"header": "6 months - 1 Year", "value": "{six_one_2024}"},
643
+ {"header": "1 - 2 Years", "value": "{one_two_2024}"},
644
+ {"header": "2 - 3 Years", "value": "{two_three_2024}"},
645
+ {"header": "More than 3 Years", "value": "{more_three_2024}"},
646
+ {"header": "Total", "value": "{age_total_2024}"}
647
+ ]
648
+ },
649
+ {
650
+ "label": "Undisputed",
651
+ "sub_label": "- Considered good",
652
+ "values": [
653
+ {"period": "0 - 6 months", "value": "{undisputed_good_zero_six_2024}"},
654
+ {"period": "6 months - 1 Year", "value": "{undisputed_good_six_one_2024}"},
655
+ {"period": "1 - 2 Years", "value": "{undisputed_good_one_two_2024}"},
656
+ {"period": "2 - 3 Years", "value": "{undisputed_good_two_three_2024}"},
657
+ {"period": "More than 3 Years", "value": "{undisputed_good_more_three_2024}"},
658
+ {"period": "Total", "value": "{undisputed_good_total_2024}"}
659
+ ]
660
+ },
661
+ {
662
+ "label": "Undisputed",
663
+ "sub_label": "- Considered doubtful",
664
+ "values": [
665
+ {"period": "0 - 6 months", "value": "{undisputed_doubtful_zero_six_2024}"},
666
+ {"period": "6 months - 1 Year", "value": "{undisputed_doubtful_six_one_2024}"},
667
+ {"period": "1 - 2 Years", "value": "{undisputed_doubtful_one_two_2024}"},
668
+ {"period": "2 - 3 Years", "value": "{undisputed_doubtful_two_three_2024}"},
669
+ {"period": "More than 3 Years", "value": "{undisputed_doubtful_more_three_2024}"},
670
+ {"period": "Total", "value": "{undisputed_doubtful_total_2024}"}
671
+ ]
672
+ },
673
+ {
674
+ "label": "Disputed",
675
+ "sub_label": "- Considered good",
676
+ "values": [
677
+ {"period": "0 - 6 months", "value": "{disputed_good_zero_six_2024}"},
678
+ {"period": "6 months - 1 Year", "value": "{disputed_good_six_one_2024}"},
679
+ {"period": "1 - 2 Years", "value": "{disputed_good_one_two_2024}"},
680
+ {"period": "2 - 3 Years", "value": "{disputed_good_two_three_2024}"},
681
+ {"period": "More than 3 Years", "value": "{disputed_good_more_three_2024}"},
682
+ {"period": "Total", "value": "{disputed_good_total_2024}"}
683
+ ]
684
+ },
685
+ {
686
+ "label": "Disputed",
687
+ "sub_label": "- Considered doubtful",
688
+ "values": [
689
+ {"period": "0 - 6 months", "value": "{disputed_doubtful_zero_six_2024}"},
690
+ {"period": "6 months - 1 Year", "value": "{disputed_doubtful_six_one_2024}"},
691
+ {"period": "1 - 2 Years", "value": "{disputed_doubtful_one_two_2024}"},
692
+ {"period": "2 - 3 Years", "value": "{disputed_doubtful_two_three_2024}"},
693
+ {"period": "More than 3 Years", "value": "{disputed_doubtful_more_three_2024}"},
694
+ {"period": "Total", "value": "{disputed_doubtful_total_2024}"}
695
+ ]
696
+ },
697
+ {
698
+ "label": "Total",
699
+ "values": [
700
+ {"period": "0 - 6 months", "value": "{total_zero_six_2024}"},
701
+ {"period": "6 months - 1 Year", "value": "{total_six_one_2024}"},
702
+ {"period": "1 - 2 Years", "value": "{total_one_two_2024}"},
703
+ {"period": "2 - 3 Years", "value": "{total_two_three_2024}"},
704
+ {"period": "More than 3 Years", "value": "{total_more_three_2024}"},
705
+ {"period": "Total", "value": "{age_total_2024}"}
706
+ ]
707
+ }
708
+ ]
709
+ },
710
+ {
711
+ "category": "Age wise analysis of Trade receivables as on 31.03.2023",
712
+ "subcategories": [
713
+ {
714
+ "label": "Particulars",
715
+ "sub_label": "Outstanding for following periods from due date of payment",
716
+ "columns": [
717
+ {"header": "0 - 6 months", "value": "{zero_six_2023}"},
718
+ {"header": "6 months - 1 Year", "value": "{six_one_2023}"},
719
+ {"header": "1 - 2 Years", "value": "{one_two_2023}"},
720
+ {"header": "2 - 3 Years", "value": "{two_three_2023}"},
721
+ {"header": "More than 3 Years", "value": "{more_three_2023}"},
722
+ {"header": "Total", "value": "{age_total_2023}"}
723
+ ]
724
+ },
725
+ {
726
+ "label": "Undisputed",
727
+ "sub_label": "- Considered good",
728
+ "values": [
729
+ {"period": "0 - 6 months", "value": "{undisputed_good_zero_six_2023}"},
730
+ {"period": "6 months - 1 Year", "value": "{undisputed_good_six_one_2023}"},
731
+ {"period": "1 - 2 Years", "value": "{undisputed_good_one_two_2023}"},
732
+ {"period": "2 - 3 Years", "value": "{undisputed_good_two_three_2023}"},
733
+ {"period": "More than 3 Years", "value": "{undisputed_good_more_three_2023}"},
734
+ {"period": "Total", "value": "{undisputed_good_total_2023}"}
735
+ ]
736
+ },
737
+ {
738
+ "label": "Undisputed",
739
+ "sub_label": "- Considered doubtful",
740
+ "values": [
741
+ {"period": "0 - 6 months", "value": "{undisputed_doubtful_zero_six_2023}"},
742
+ {"period": "6 months - 1 Year", "value": "{undisputed_doubtful_six_one_2023}"},
743
+ {"period": "1 - 2 Years", "value": "{undisputed_doubtful_one_two_2023}"},
744
+ {"period": "2 - 3 Years", "value": "{undisputed_doubtful_two_three_2023}"},
745
+ {"period": "More than 3 Years", "value": "{undisputed_doubtful_more_three_2023}"},
746
+ {"period": "Total", "value": "{undisputed_doubtful_total_2023}"}
747
+ ]
748
+ },
749
+ {
750
+ "label": "Disputed",
751
+ "sub_label": "- Considered good",
752
+ "values": [
753
+ {"period": "0 - 6 months", "value": "{disputed_good_zero_six_2023}"},
754
+ {"period": "6 months - 1 Year", "value": "{disputed_good_six_one_2023}"},
755
+ {"period": "1 - 2 Years", "value": "{disputed_good_one_two_2023}"},
756
+ {"period": "2 - 3 Years", "value": "{disputed_good_two_three_2023}"},
757
+ {"period": "More than 3 Years", "value": "{disputed_good_more_three_2023}"},
758
+ {"period": "Total", "value": "{disputed_good_total_2023}"}
759
+ ]
760
+ },
761
+ {
762
+ "label": "Disputed",
763
+ "sub_label": "- Considered doubtful",
764
+ "values": [
765
+ {"period": "0 - 6 months", "value": "{disputed_doubtful_zero_six_2023}"},
766
+ {"period": "6 months - 1 Year", "value": "{disputed_doubtful_six_one_2023}"},
767
+ {"period": "1 - 2 Years", "value": "{disputed_doubtful_one_two_2023}"},
768
+ {"period": "2 - 3 Years", "value": "{disputed_doubtful_two_three_2023}"},
769
+ {"period": "More than 3 Years", "value": "{disputed_doubtful_more_three_2023}"},
770
+ {"period": "Total", "value": "{disputed_doubtful_total_2023}"}
771
+ ]
772
+ },
773
+ {
774
+ "label": "Total",
775
+ "values": [
776
+ {"period": "0 - 6 months", "value": "{total_zero_six_2023}"},
777
+ {"period": "6 months - 1 Year", "value": "{total_six_one_2023}"},
778
+ {"period": "1 - 2 Years", "value": "{total_one_two_2023}"},
779
+ {"period": "2 - 3 Years", "value": "{total_two_three_2023}"},
780
+ {"period": "More than 3 Years", "value": "{total_more_three_2023}"},
781
+ {"period": "Total", "value": "{age_total_2023}"}
782
+ ]
783
+ }
784
+ ]
785
+ }
786
+ ],
787
+ "metadata": {
788
+ "note_number": "12",
789
+ "generated_on": "{generated_on}"
790
+ }
791
+ },
792
+ "13": {
793
+ "title": "Cash and Bank Balances",
794
+ "full_title": "13. Cash and Bank Balances",
795
+ "structure": [
796
+ {
797
+ "category": "",
798
+ "subcategories": [
799
+ {
800
+ "label": "March 31, 2024",
801
+ "value": "{march_2024_total}"
802
+ },
803
+ {
804
+ "label": "March 31, 2023",
805
+ "value": "{march_2023_total}"
806
+ }
807
+ ]
808
+ },
809
+ {
810
+ "category": "Cash and cash equivalents",
811
+ "subcategories": [
812
+ {
813
+ "label": "Balances with banks in current accounts",
814
+ "value": "{bank_balances_2024}",
815
+ "previous_value": "{bank_balances_2023}"
816
+ },
817
+ {
818
+ "label": "Cash on hand",
819
+ "value": "{cash_on_hand_2024}",
820
+ "previous_value": "{cash_on_hand_2023}"
821
+ }
822
+ ]
823
+ },
824
+ {
825
+ "category": "Other Bank Balances",
826
+ "subcategories": [
827
+ {
828
+ "label": "Fixed Deposits with ICICI Bank",
829
+ "value": "{fixed_deposits_2024}",
830
+ "previous_value": "{fixed_deposits_2023}"
831
+ }
832
+ ]
833
+ },
834
+ {
835
+ "category": "Total",
836
+ "subcategories": [],
837
+ "total": "{total_2024}",
838
+ "previous_total": "{total_2023}"
839
+ }
840
+ ],
841
+ "metadata": {
842
+ "note_number": "13",
843
+ "generated_on": "{generated_on}"
844
+ }
845
+ },
846
+ "14": {
847
+ "title": "Short Term Loans and Advances",
848
+ "full_title": "14. Short Term Loans and Advances",
849
+ "structure": [
850
+ {
851
+ "category": "",
852
+ "subcategories": [
853
+ {
854
+ "label": "March 31, 2024",
855
+ "value": "{march_2024_total}"
856
+ },
857
+ {
858
+ "label": "March 31, 2023",
859
+ "value": "{march_2023_total}"
860
+ }
861
+ ]
862
+ },
863
+ {
864
+ "category": "Unsecured, considered good",
865
+ "subcategories": [
866
+ {
867
+ "label": "Prepaid Expenses",
868
+ "value": "{prepaid_expenses_2024}",
869
+ "previous_value": "{prepaid_expenses_2023}"
870
+ },
871
+ {
872
+ "label": "Other Advances",
873
+ "value": "{other_advances_2024}",
874
+ "previous_value": "{other_advances_2023}"
875
+ }
876
+ ]
877
+ },
878
+ {
879
+ "category": "Other loans and advances",
880
+ "subcategories": [
881
+ {
882
+ "label": "Advance tax",
883
+ "value": "{advance_tax_2024}",
884
+ "previous_value": "{advance_tax_2023}"
885
+ },
886
+ {
887
+ "label": "Balances with statutory/government authorities",
888
+ "value": "{statutory_balances_2024}",
889
+ "previous_value": "{statutory_balances_2023}"
890
+ }
891
+ ]
892
+ },
893
+ {
894
+ "category": "Total",
895
+ "subcategories": [],
896
+ "total": "{total_2024}",
897
+ "previous_total": "{total_2023}"
898
+ }
899
+ ],
900
+ "metadata": {
901
+ "note_number": "14",
902
+ "generated_on": "{generated_on}"
903
+ }
904
+ },
905
+ "15": {
906
+ "title": "Other Current Assets",
907
+ "full_title": "15. Other Current Assets",
908
+ "structure": [
909
+ {
910
+ "category": "",
911
+ "subcategories": [
912
+ {
913
+ "label": "March 31, 2024",
914
+ "value": "{march_2024_total}"
915
+ },
916
+ {
917
+ "label": "March 31, 2023",
918
+ "value": "{march_2023_total}"
919
+ }
920
+ ]
921
+ },
922
+ {
923
+ "category": "Interest accrued on fixed deposits",
924
+ "subcategories": [],
925
+ "total": "{interest_accrued_2024}",
926
+ "previous_total": "{interest_accrued_2023}"
927
+ }
928
+ ],
929
+ "metadata": {
930
+ "note_number": "15",
931
+ "generated_on": "{generated_on}"
932
+ }
933
+ },
934
+ "16": {
935
+ "title": "Revenue from Operations",
936
+ "full_title": "16. Revenue from Operations",
937
+ "structure": [
938
+ {
939
+ "category": "In Lakhs",
940
+ "subcategories": [
941
+ {
942
+ "label": "March 31, 2024",
943
+ "value": "{march_2024_total}"
944
+ },
945
+ {
946
+ "label": "March 31, 2023",
947
+ "value": "{march_2023_total}"
948
+ }
949
+ ]
950
+ },
951
+ {
952
+ "category": "Sale of Services",
953
+ "subcategories": [
954
+ {
955
+ "label": "Domestic",
956
+ "value": "{domestic_2024}",
957
+ "previous_value": "{domestic_2023}"
958
+ },
959
+ {
960
+ "label": "Exports",
961
+ "value": "{exports_2024}",
962
+ "previous_value": "{exports_2023}"
963
+ }
964
+ ],
965
+ "total": "{total_2024}",
966
+ "previous_total": "{total_2023}"
967
+ }
968
+ ],
969
+ "metadata": {
970
+ "note_number": "16",
971
+ "generated_on": "{generated_on}"
972
+ }
973
+ },
974
+ "17": {
975
+ "title": "Other Income",
976
+ "full_title": "17. Other Income",
977
+ "structure": [
978
+ {
979
+ "category": "",
980
+ "subcategories": [
981
+ {
982
+ "label": "March 31, 2024",
983
+ "value": "{march_2024_total}"
984
+ },
985
+ {
986
+ "label": "March 31, 2023",
987
+ "value": "{march_2023_total}"
988
+ }
989
+ ]
990
+ },
991
+ {
992
+ "category": "",
993
+ "subcategories": [
994
+ {
995
+ "label": "Interest income",
996
+ "value": "{interestincome_2024}",
997
+ "previous_value": "{interestincome_2023}"
998
+ },
999
+ {
1000
+ "label": "Foreign exchange gain (Net)",
1001
+ "value": "{foreignexchangegainnet_2024}",
1002
+ "previous_value": "{foreignexchangegainnet_2023}"
1003
+ }
1004
+ ],
1005
+ "total": "{total_2024}",
1006
+ "previous_total": "{total_2023}"
1007
+ }
1008
+ ],
1009
+ "metadata": {
1010
+ "note_number": "17",
1011
+ "generated_on": "{generated_on}"
1012
+ }
1013
+ },
1014
+ "18": {
1015
+ "title": "Cost of Materials Consumed",
1016
+ "full_title": "18. Cost of Materials Consumed",
1017
+ "structure": [
1018
+ {
1019
+ "category": "",
1020
+ "subcategories": [
1021
+ {
1022
+ "label": "March 31, 2024",
1023
+ "value": "{march_2024_total}"
1024
+ },
1025
+ {
1026
+ "label": "March 31, 2023",
1027
+ "value": "{march_2023_total}"
1028
+ }
1029
+ ]
1030
+ },
1031
+ {
1032
+ "category": "",
1033
+ "subcategories": [
1034
+ {
1035
+ "label": "Opening Stock",
1036
+ "value": "{openingstock_2024}",
1037
+ "previous_value": "{openingstock_2023}"
1038
+ },
1039
+ {
1040
+ "label": "Add: Purchases",
1041
+ "value": "{purchases_2024}",
1042
+ "previous_value": "{purchases_2023}"
1043
+ },
1044
+ {
1045
+ "label": "",
1046
+ "value": "{subtotal_2024}",
1047
+ "previous_value": "{subtotal_2023}"
1048
+ },
1049
+ {
1050
+ "label": "Less: Closing Stock",
1051
+ "value": "{closingstock_2024}",
1052
+ "previous_value": "{closingstock_2023}"
1053
+ }
1054
+ ],
1055
+ "total": "{costmaterialsconsumed_2024}",
1056
+ "previous_total": "{costmaterialsconsumed_2023}"
1057
+ }
1058
+ ],
1059
+ "metadata": {
1060
+ "note_number": "18",
1061
+ "generated_on": "{generated_on}"
1062
+ }
1063
+ },
1064
+ "19": {
1065
+ "title": "Employee Benefit Expense",
1066
+ "full_title": "19. Employee Benefit Expense",
1067
+ "structure": [
1068
+ {
1069
+ "category": "",
1070
+ "subcategories": [
1071
+ {
1072
+ "label": "March 31, 2024",
1073
+ "value": "{march_2024_total}"
1074
+ },
1075
+ {
1076
+ "label": "March 31, 2023",
1077
+ "value": "{march_2023_total}"
1078
+ }
1079
+ ]
1080
+ },
1081
+ {
1082
+ "category": "",
1083
+ "subcategories": [
1084
+ {
1085
+ "label": "Salaries, wages and bonus",
1086
+ "value": "{salarieswagesandbonus_2024}",
1087
+ "previous_value": "{salarieswagesandbonus_2023}"
1088
+ },
1089
+ {
1090
+ "label": "Contribution to PF & ESI",
1091
+ "value": "{contributiontopfesi_2024}",
1092
+ "previous_value": "{contributiontopfesi_2023}"
1093
+ },
1094
+ {
1095
+ "label": "Staff welfare expenses",
1096
+ "value": "{staffwelfareexpenses_2024}",
1097
+ "previous_value": "{staffwelfareexpenses_2023}"
1098
+ }
1099
+ ],
1100
+ "total": "{total_2024}",
1101
+ "previous_total": "{total_2023}"
1102
+ }
1103
+ ],
1104
+ "metadata": {
1105
+ "note_number": "19",
1106
+ "generated_on": "{generated_on}"
1107
+ }
1108
+ },
1109
+ "20": {
1110
+ "title": "Other Expenses",
1111
+ "full_title": "20. Other Expenses",
1112
+ "structure": [
1113
+ {
1114
+ "category": "",
1115
+ "subcategories": [
1116
+ {
1117
+ "label": "March 31, 2024",
1118
+ "value": "{march_2024_total}"
1119
+ },
1120
+ {
1121
+ "label": "March 31, 2023",
1122
+ "value": "{march_2023_total}"
1123
+ }
1124
+ ]
1125
+ },
1126
+ {
1127
+ "category": "",
1128
+ "subcategories": [
1129
+ {
1130
+ "label": "BA / BE NOC Charges",
1131
+ "value": "{babenoccharges_2024}",
1132
+ "previous_value": "{babenoccharges_2023}"
1133
+ },
1134
+ {
1135
+ "label": "BA Expenses",
1136
+ "value": "{baexpenses_2024}",
1137
+ "previous_value": "{baexpenses_2023}"
1138
+ },
1139
+ {
1140
+ "label": "Payments to Volunteers",
1141
+ "value": "{paymentstovolunteers_2024}",
1142
+ "previous_value": "{paymentstovolunteers_2023}"
1143
+ },
1144
+ {
1145
+ "label": "Other Operating Expenses",
1146
+ "value": "{otheroperatingexpenses_2024}",
1147
+ "previous_value": "{otheroperatingexpenses_2023}"
1148
+ },
1149
+ {
1150
+ "label": "Laboratory testing charges",
1151
+ "value": "{laboratorytestingcharges_2024}",
1152
+ "previous_value": "{laboratorytestingcharges_2023}"
1153
+ },
1154
+ {
1155
+ "label": "Rent",
1156
+ "value": "{rent_2024}",
1157
+ "previous_value": "{rent_2023}"
1158
+ },
1159
+ {
1160
+ "label": "Rates & Taxes",
1161
+ "value": "{ratesandtaxes_2024}",
1162
+ "previous_value": "{ratesandtaxes_2023}"
1163
+ },
1164
+ {
1165
+ "label": "Fees & licenses",
1166
+ "value": "{feesandlicenses_2024}",
1167
+ "previous_value": "{feesandlicenses_2023}"
1168
+ },
1169
+ {
1170
+ "label": "Insurance",
1171
+ "value": "{insurance_2024}",
1172
+ "previous_value": "{insurance_2023}"
1173
+ },
1174
+ {
1175
+ "label": "Membership & Subscription Charges",
1176
+ "value": "{membershipandsubscriptioncharges_2024}",
1177
+ "previous_value": "{membershipandsubscriptioncharges_2023}"
1178
+ },
1179
+ {
1180
+ "label": "Postage & Communication Cost",
1181
+ "value": "{postageandcommunicationcost_2024}",
1182
+ "previous_value": "{postageandcommunicationcost_2023}"
1183
+ },
1184
+ {
1185
+ "label": "Printing and stationery",
1186
+ "value": "{printingandstationery_2024}",
1187
+ "previous_value": "{printingandstationery_2023}"
1188
+ },
1189
+ {
1190
+ "label": "CSR Fund Expenses",
1191
+ "value": "{csrfundexpenses_2024}",
1192
+ "previous_value": "{csrfundexpenses_2023}"
1193
+ },
1194
+ {
1195
+ "label": "Telephone & Internet",
1196
+ "value": "{telephoneandinternet_2024}",
1197
+ "previous_value": "{telephoneandinternet_2023}"
1198
+ },
1199
+ {
1200
+ "label": "Travelling and Conveyance",
1201
+ "value": "{travellingandconveyance_2024}",
1202
+ "previous_value": "{travellingandconveyance_2023}"
1203
+ },
1204
+ {
1205
+ "label": "Translation Charges",
1206
+ "value": "{translationcharges_2024}",
1207
+ "previous_value": "{translationcharges_2023}"
1208
+ },
1209
+ {
1210
+ "label": "Electricity Charges",
1211
+ "value": "{electricitycharges_2024}",
1212
+ "previous_value": "{electricitycharges_2023}"
1213
+ },
1214
+ {
1215
+ "label": "Security Charges",
1216
+ "value": "{securitycharges_2024}",
1217
+ "previous_value": "{securitycharges_2023}"
1218
+ },
1219
+ {
1220
+ "label": "Annual Maintenance Charges",
1221
+ "value": "{annualmaintenancecharges_2024}",
1222
+ "previous_value": "{annualmaintenancecharges_2023}"
1223
+ },
1224
+ {
1225
+ "label": "Repairs and maintenance - Electrical",
1226
+ "value": "{repairsandmaintenanceelectrical_2024}",
1227
+ "previous_value": "{repairsandmaintenanceelectrical_2023}"
1228
+ },
1229
+ {
1230
+ "label": "Repairs and maintenance - Office",
1231
+ "value": "{repairsandmaintenanceoffice_2024}",
1232
+ "previous_value": "{repairsandmaintenanceoffice_2023}"
1233
+ },
1234
+ {
1235
+ "label": "Repairs and maintenance - Machinery",
1236
+ "value": "{repairsandmaintenancemachinery_2024}",
1237
+ "previous_value": "{repairsandmaintenancemachinery_2023}"
1238
+ },
1239
+ {
1240
+ "label": "Repairs and maintenance - Vehicles",
1241
+ "value": "{repairsandmaintenancevehicles_2024}",
1242
+ "previous_value": "{repairsandmaintenancevehicles_2023}"
1243
+ },
1244
+ {
1245
+ "label": "Repairs and maintenance - Others",
1246
+ "value": "{repairsandmaintenanceothers_2024}",
1247
+ "previous_value": "{repairsandmaintenanceothers_2023}"
1248
+ },
1249
+ {
1250
+ "label": "Business Development Expenses",
1251
+ "value": "{businessdevelopmentexpenses_2024}",
1252
+ "previous_value": "{businessdevelopmentexpenses_2023}"
1253
+ },
1254
+ {
1255
+ "label": "Professional & Consultancy Fees",
1256
+ "value": "{professionalandconsultancyfees_2024}",
1257
+ "previous_value": "{professionalandconsultancyfees_2023}"
1258
+ },
1259
+ {
1260
+ "label": "Payment to Auditors",
1261
+ "value": "{paymenttoauditors_2024}",
1262
+ "previous_value": "{paymenttoauditors_2023}"
1263
+ },
1264
+ {
1265
+ "label": "Bad Debts Written Off",
1266
+ "value": "{baddebtswrittenoff_2024}",
1267
+ "previous_value": "{baddebtswrittenoff_2023}"
1268
+ },
1269
+ {
1270
+ "label": "Fire Extinguishers Refilling Charges",
1271
+ "value": "{fireextinguishersrefillingcharges_2024}",
1272
+ "previous_value": "{fireextinguishersrefillingcharges_2023}"
1273
+ },
1274
+ {
1275
+ "label": "Food Expenses for Guests",
1276
+ "value": "{foodexpensesforguests_2024}",
1277
+ "previous_value": "{foodexpensesforguests_2023}"
1278
+ },
1279
+ {
1280
+ "label": "Diesel Expenses",
1281
+ "value": "{dieselexpenses_2024}",
1282
+ "previous_value": "{dieselexpenses_2023}"
1283
+ },
1284
+ {
1285
+ "label": "Interest Under 234 C Fy 2021-22",
1286
+ "value": "{interestunder234cfy202122_2024}",
1287
+ "previous_value": "{interestunder234cfy202122_2023}"
1288
+ },
1289
+ {
1290
+ "label": "Loan Processing Charges",
1291
+ "value": "{loanprocessingcharges_2024}",
1292
+ "previous_value": "{loanprocessingcharges_2023}"
1293
+ },
1294
+ {
1295
+ "label": "Sitting Fee of Directors",
1296
+ "value": "{sittingfeeofdirectors_2024}",
1297
+ "previous_value": "{sittingfeeofdirectors_2023}"
1298
+ },
1299
+ {
1300
+ "label": "Customs Duty Payment",
1301
+ "value": "{customsdutypayment_2024}",
1302
+ "previous_value": "{customsdutypayment_2023}"
1303
+ },
1304
+ {
1305
+ "label": "Transportation and Unloading Charges",
1306
+ "value": "{transportationandunloadingcharges_2024}",
1307
+ "previous_value": "{transportationandunloadingcharges_2023}"
1308
+ },
1309
+ {
1310
+ "label": "Software Equipment",
1311
+ "value": "{softwareequipment_2024}",
1312
+ "previous_value": "{softwareequipment_2023}"
1313
+ },
1314
+ {
1315
+ "label": "Miscellaneous expenses",
1316
+ "value": "{miscellaneousexpenses_2024}",
1317
+ "previous_value": "{miscellaneousexpenses_2023}"
1318
+ }
1319
+ ],
1320
+ "total": "{total_2024}",
1321
+ "previous_total": "{total_2023}"
1322
+ }
1323
+ ],
1324
+ "metadata": {
1325
+ "note_number": "20",
1326
+ "generated_on": "{generated_on}"
1327
+ },
1328
+ "notes_and_disclosures": [
1329
+ "* Fees is net of GST which is taken as input tax credit."
1330
+ ]
1331
+ },
1332
+ "21": {
1333
+ "title": "Depreciation and Amortisation Expense",
1334
+ "full_title": "21. Depreciation and Amortisation Expense",
1335
+ "structure": [
1336
+ {
1337
+ "category": "",
1338
+ "subcategories": [
1339
+ {
1340
+ "label": "March 31, 2024",
1341
+ "value": "{march_2024_total}"
1342
+ },
1343
+ {
1344
+ "label": "March 31, 2023",
1345
+ "value": "{march_2023_total}"
1346
+ }
1347
+ ]
1348
+ },
1349
+ {
1350
+ "category": "",
1351
+ "subcategories": [
1352
+ {
1353
+ "label": "Depreciation & amortisation",
1354
+ "value": "{depreciationamortisation_2024}",
1355
+ "previous_value": "{depreciationamortisation_2023}"
1356
+ }
1357
+ ],
1358
+ "total": "{total_2024}",
1359
+ "previous_total": "{total_2023}"
1360
+ }
1361
+ ],
1362
+ "metadata": {
1363
+ "note_number": "21",
1364
+ "generated_on": "{generated_on}"
1365
+ }
1366
+ },
1367
+ "22": {
1368
+ "title": "Loss on Sale of Assets & Investments",
1369
+ "full_title": "22. Loss on Sale of Assets & Investments",
1370
+ "structure": [
1371
+ {
1372
+ "category": "",
1373
+ "subcategories": [
1374
+ {
1375
+ "label": "March 31, 2024",
1376
+ "value": "{march_2024_total}"
1377
+ },
1378
+ {
1379
+ "label": "March 31, 2023",
1380
+ "value": "{march_2023_total}"
1381
+ }
1382
+ ]
1383
+ },
1384
+ {
1385
+ "category": "",
1386
+ "subcategories": [
1387
+ {
1388
+ "label": "Short Term Loss on Sale of Investments (Non Derivative Loss)",
1389
+ "value": "{shorttermlossonSaleofinvestmentsnonderivativeLoss_2024}",
1390
+ "previous_value": "{shorttermlossonSaleofinvestmentsnonderivativeLoss_2023}"
1391
+ },
1392
+ {
1393
+ "label": "Long term loss on sale of investments",
1394
+ "value": "{longtermlossonSaleofinvestments_2024}",
1395
+ "previous_value": "{longtermlossonSaleofinvestments_2023}"
1396
+ },
1397
+ {
1398
+ "label": "Loss on Sale of Fixed Assets",
1399
+ "value": "{lossonSaleoffixedassets_2024}",
1400
+ "previous_value": "{lossonSaleoffixedassets_2023}"
1401
+ }
1402
+ ],
1403
+ "total": "{total_2024}",
1404
+ "previous_total": "{total_2023}"
1405
+ }
1406
+ ],
1407
+ "metadata": {
1408
+ "note_number": "22",
1409
+ "generated_on": "{generated_on}"
1410
+ }
1411
+ },
1412
+ "23": {
1413
+ "title": "Finance Costs",
1414
+ "full_title": "23. Finance Costs",
1415
+ "structure": [
1416
+ {
1417
+ "category": "",
1418
+ "subcategories": [
1419
+ {
1420
+ "label": "March 31, 2024",
1421
+ "value": "{march_2024_total}"
1422
+ },
1423
+ {
1424
+ "label": "March 31, 2023",
1425
+ "value": "{march_2023_total}"
1426
+ }
1427
+ ]
1428
+ },
1429
+ {
1430
+ "category": "",
1431
+ "subcategories": [
1432
+ {
1433
+ "label": "Bank & Finance Charges",
1434
+ "value": "{bankfinancecharges_2024}",
1435
+ "previous_value": "{bankfinancecharges_2023}"
1436
+ }
1437
+ ],
1438
+ "total": "{total_2024}",
1439
+ "previous_total": "{total_2023}"
1440
+ }
1441
+ ],
1442
+ "metadata": {
1443
+ "note_number": "23",
1444
+ "generated_on": "{generated_on}"
1445
+ }
1446
+ },
1447
+ "24": {
1448
+ "title": "Payment to Auditor",
1449
+ "full_title": "24. Payment to Auditor",
1450
+ "structure": [
1451
+ {
1452
+ "category": "",
1453
+ "subcategories": [
1454
+ {
1455
+ "label": "March 31, 2024",
1456
+ "value": "{march_2024_total}"
1457
+ },
1458
+ {
1459
+ "label": "March 31, 2023",
1460
+ "value": "{march_2023_total}"
1461
+ }
1462
+ ]
1463
+ },
1464
+ {
1465
+ "category": "",
1466
+ "subcategories": [
1467
+ {
1468
+ "label": "For Audit fee",
1469
+ "value": "{forauditfee_2024}",
1470
+ "previous_value": "{forauditfee_2023}"
1471
+ },
1472
+ {
1473
+ "label": "For Tax Audit / Certification Fees",
1474
+ "value": "{fortaxauditcertificationfees_2024}",
1475
+ "previous_value": "{fortaxauditcertificationfees_2023}"
1476
+ }
1477
+ ],
1478
+ "total": "{total_2024}",
1479
+ "previous_total": "{total_2023}"
1480
+ }
1481
+ ],
1482
+ "metadata": {
1483
+ "note_number": "24",
1484
+ "generated_on": "{generated_on}"
1485
+ }
1486
+ },
1487
+ "25": {
1488
+ "title": "Earnings in Foreign Currency",
1489
+ "full_title": "25. Earnings in Foreign Currency",
1490
+ "structure": [
1491
+ {
1492
+ "category": "",
1493
+ "subcategories": [
1494
+ {
1495
+ "label": "March 31, 2024",
1496
+ "value": "{march_2024_total}"
1497
+ },
1498
+ {
1499
+ "label": "March 31, 2023",
1500
+ "value": "{march_2023_total}"
1501
+ }
1502
+ ]
1503
+ },
1504
+ {
1505
+ "category": "Inflow",
1506
+ "subcategories": [
1507
+ {
1508
+ "label": "Income from export of services",
1509
+ "value": "{incomefromexportofservices_2024}",
1510
+ "previous_value": "{incomefromexportofservices_2023}"
1511
+ }
1512
+ ],
1513
+ "total": "{total_2024}",
1514
+ "previous_total": "{total_2023}"
1515
+ }
1516
+ ],
1517
+ "metadata": {
1518
+ "note_number": "25",
1519
+ "generated_on": "{generated_on}"
1520
+ }
1521
+ },
1522
+ "26": {
1523
+ "title": "Particulars of Un-hedged Foreign Currency Exposure",
1524
+ "full_title": "26. Particulars of Un-hedged Foreign Currency Exposure",
1525
+ "structure": [
1526
+ {
1527
+ "category": "",
1528
+ "subcategories": [
1529
+ {
1530
+ "label": "March 31, 2024",
1531
+ "value": "{march_2024_total}"
1532
+ },
1533
+ {
1534
+ "label": "March 31, 2023",
1535
+ "value": "{march_2023_total}"
1536
+ }
1537
+ ]
1538
+ },
1539
+ {
1540
+ "category": "Inflow",
1541
+ "subcategories": [
1542
+ {
1543
+ "label": "Income from export of services",
1544
+ "value": "{incomefromexportofservices_2024}",
1545
+ "previous_value": "{incomefromexportofservices_2023}"
1546
+ }
1547
+ ],
1548
+ "total": "{total_2024}",
1549
+ "previous_total": "{total_2023}"
1550
+ }
1551
+ ],
1552
+ "metadata": {
1553
+ "note_number": "26",
1554
+ "generated_on": "{generated_on}"
1555
+ },
1556
+ "notes_and_disclosures": [
1557
+ "(i) There is no derivate contract outstanding as at the Balance Sheet date.",
1558
+ "(ii) Particulars of un-hedged foreign currency exposure as at the Balance Sheet date"
1559
+ ]
1560
+ },
1561
+ "27": {
1562
+ "title": "Contingent Liabilities",
1563
+ "full_title": "27. Contingent Liabilities",
1564
+ "structure": [
1565
+ {
1566
+ "category": "In Lakhs",
1567
+ "subcategories": [
1568
+ {
1569
+ "label": "March 31, 2024",
1570
+ "value": "{march_2024_total}"
1571
+ },
1572
+ {
1573
+ "label": "March 31, 2023",
1574
+ "value": "{march_2023_total}"
1575
+ }
1576
+ ]
1577
+ },
1578
+ {
1579
+ "category": "",
1580
+ "subcategories": [
1581
+ {
1582
+ "label": "Claims against the company not acknowledged as debts",
1583
+ "value": "{claims_not_acknowledged_2024}",
1584
+ "previous_value": "{claims_not_acknowledged_2023}"
1585
+ }
1586
+ ],
1587
+ "total": "{total_2024}",
1588
+ "previous_total": "{total_2023}"
1589
+ }
1590
+ ],
1591
+ "metadata": {
1592
+ "note_number": "27",
1593
+ "generated_on": "{generated_on}"
1594
+ }
1595
+ },
1596
+ "28": {
1597
+ "title": "Commitments",
1598
+ "full_title": "28. Commitments",
1599
+ "structure": [
1600
+ {
1601
+ "category": "In Lakhs",
1602
+ "subcategories": [
1603
+ {
1604
+ "label": "March 31, 2024",
1605
+ "value": "{march_2024_total}"
1606
+ },
1607
+ {
1608
+ "label": "March 31, 2023",
1609
+ "value": "{march_2023_total}"
1610
+ }
1611
+ ]
1612
+ },
1613
+ {
1614
+ "category": "",
1615
+ "subcategories": [
1616
+ {
1617
+ "label": "Capital Commitments",
1618
+ "value": "{capital_commitments_2024}",
1619
+ "previous_value": "{capital_commitments_2023}"
1620
+ }
1621
+ ],
1622
+ "total": "{total_2024}",
1623
+ "previous_total": "{total_2023}"
1624
+ }
1625
+ ],
1626
+ "metadata": {
1627
+ "note_number": "28",
1628
+ "generated_on": "{generated_on}"
1629
+ }
1630
+ },
1631
+ "29": {
1632
+ "title": "Related Party Disclosures",
1633
+ "full_title": "29. Related Party Disclosures",
1634
+ "structure": [
1635
+ {
1636
+ "category": "",
1637
+ "subcategories": [
1638
+ {
1639
+ "label": "March 31, 2024",
1640
+ "value": "{march_2024_total}"
1641
+ },
1642
+ {
1643
+ "label": "March 31, 2023",
1644
+ "value": "{march_2023_total}"
1645
+ }
1646
+ ]
1647
+ },
1648
+ {
1649
+ "category": "Key Management Personnel",
1650
+ "subcategories": [
1651
+ {
1652
+ "label": "Remuneration",
1653
+ "value": "{remuneration_kmp_2024}",
1654
+ "previous_value": "{remuneration_kmp_2023}"
1655
+ }
1656
+ ],
1657
+ "total": "{total_kmp_2024}",
1658
+ "previous_total": "{total_kmp_2023}"
1659
+ }
1660
+ ],
1661
+ "metadata": {
1662
+ "note_number": "29",
1663
+ "generated_on": "{generated_on}"
1664
+ }
1665
+ },
1666
+ "30": {
1667
+ "title": "Segment Reporting",
1668
+ "full_title": "30. Segment Reporting",
1669
+ "structure": [
1670
+ {
1671
+ "category": "In Lakhs",
1672
+ "subcategories": [
1673
+ {
1674
+ "label": "March 31, 2024",
1675
+ "value": "{march_2024_total}"
1676
+ },
1677
+ {
1678
+ "label": "March 31, 2023",
1679
+ "value": "{march_2023_total}"
1680
+ }
1681
+ ]
1682
+ },
1683
+ {
1684
+ "category": "Segment Revenue",
1685
+ "subcategories": [
1686
+ {
1687
+ "label": "Segment A",
1688
+ "value": "{segment_a_revenue_2024}",
1689
+ "previous_value": "{segment_a_revenue_2023}"
1690
+ },
1691
+ {
1692
+ "label": "Segment B",
1693
+ "value": "{segment_b_revenue_2024}",
1694
+ "previous_value": "{segment_b_revenue_2023}"
1695
+ }
1696
+ ],
1697
+ "total": "{segment_total_2024}",
1698
+ "previous_total": "{segment_total_2023}"
1699
+ }
1700
+ ],
1701
+ "metadata": {
1702
+ "note_number": "30",
1703
+ "generated_on": "{generated_on}"
1704
+ }
1705
+ },
1706
+ "31": {
1707
+ "title": "Earnings Per Share (EPS)",
1708
+ "full_title": "31. Earnings Per Share (EPS)",
1709
+ "structure": [
1710
+ {
1711
+ "category": "",
1712
+ "subcategories": [
1713
+ {
1714
+ "label": "March 31, 2024",
1715
+ "value": "{march_2024_total}"
1716
+ },
1717
+ {
1718
+ "label": "March 31, 2023",
1719
+ "value": "{march_2023_total}"
1720
+ }
1721
+ ]
1722
+ },
1723
+ {
1724
+ "category": "",
1725
+ "subcategories": [
1726
+ {
1727
+ "label": "Basic EPS",
1728
+ "value": "{basic_eps_2024}",
1729
+ "previous_value": "{basic_eps_2023}"
1730
+ },
1731
+ {
1732
+ "label": "Diluted EPS",
1733
+ "value": "{diluted_eps_2024}",
1734
+ "previous_value": "{diluted_eps_2023}"
1735
+ }
1736
+ ],
1737
+ "total": "{total_eps_2024}",
1738
+ "previous_total": "{total_eps_2023}"
1739
+ }
1740
+ ],
1741
+ "metadata": {
1742
+ "note_number": "31",
1743
+ "generated_on": "{generated_on}"
1744
+ }
1745
+ },
1746
+ "32": {
1747
+ "title": "Additional Disclosures",
1748
+ "full_title": "32. Additional Disclosures",
1749
+ "structure": [
1750
+ {
1751
+ "category": "",
1752
+ "subcategories": [
1753
+ {
1754
+ "label": "March 31, 2024",
1755
+ "value": "{march_2024_total}"
1756
+ },
1757
+ {
1758
+ "label": "March 31, 2023",
1759
+ "value": "{march_2023_total}"
1760
+ }
1761
+ ]
1762
+ },
1763
+ {
1764
+ "category": "",
1765
+ "subcategories": [
1766
+ {
1767
+ "label": "Additional Information A",
1768
+ "value": "{additional_info_a_2024}",
1769
+ "previous_value": "{additional_info_a_2023}"
1770
+ },
1771
+ {
1772
+ "label": "Additional Information B",
1773
+ "value": "{additional_info_b_2024}",
1774
+ "previous_value": "{additional_info_b_2023}"
1775
+ }
1776
+ ],
1777
+ "total": "{total_additional_2024}",
1778
+ "previous_total": "{total_additional_2023}"
1779
+ }
1780
+ ],
1781
+ "metadata": {
1782
+ "note_number": "32",
1783
+ "generated_on": "{generated_on}"
1784
+ }
1785
+ }
1786
+ }
1787
+
1788
+ # Validate note_templates on import
1789
+ validated_note_templates = validate_note_templates(note_templates)
1790
+
1791
+ # Optionally, expose validated_note_templates for import elsewhere
1792
+ __all__ = ["validated_note_templates"]
1793
+
1794
+ # Example usage (for testing or debugging)
1795
+ if __name__ == "__main__":
1796
+ logger.info(f"Loaded {len(validated_note_templates)} validated note templates.")
1797
+ # Print one example note template structure
1798
+ example_key = next(iter(validated_note_templates))
1799
+ logger.info(f"Example Note Template [{example_key}]:\n{validated_note_templates[example_key].json(indent=2)}")
app/new_main.py ADDED
@@ -0,0 +1,517 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import logging
4
+ import requests
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+ from dotenv import load_dotenv
8
+ import re
9
+ import sys
10
+ from typing import Dict, List, Any, Optional, Tuple
11
+ import pandas as pd
12
+ from pydantic import BaseModel, ValidationError
13
+ from pydantic_settings import BaseSettings
14
+ from app.utils import convert_note_json_to_lakhs
15
+
16
+
17
+ # Load environment variables
18
+ load_dotenv()
19
+
20
+ # Configure logging
21
+ logging.basicConfig(level=logging.INFO)
22
+ logger = logging.getLogger(__name__)
23
+
24
+ class Settings(BaseSettings):
25
+ """Application settings loaded from environment variables or .env file."""
26
+ openrouter_api_key: str = os.getenv('OPENROUTER_API_KEY', '')
27
+ api_url: str = "https://openrouter.ai/api/v1/chat/completions"
28
+ output_dir: str = "generated_notes"
29
+ trial_balance_json: str = "output1/parsed_trial_balance.json"
30
+
31
+ settings = Settings()
32
+
33
+ class Account(BaseModel):
34
+ account_name: str
35
+ amount: float
36
+ group: Optional[str] = None
37
+
38
+ class NoteTemplate(BaseModel):
39
+ title: str
40
+ full_title: str
41
+ # Add other fields as needed for your template structure
42
+
43
+ class GeneratedNote(BaseModel):
44
+ note_number: str
45
+ markdown_content: str
46
+ grand_total_lakhs: float
47
+ generated_on: str
48
+ assumptions: Optional[str] = None
49
+ # Add other fields as needed
50
+
51
+ class FlexibleFinancialNoteGenerator:
52
+ def __init__(self):
53
+ self.openrouter_api_key = settings.openrouter_api_key
54
+ if not self.openrouter_api_key:
55
+ logger.error("OPENROUTER_API_KEY not found in .env file")
56
+ raise ValueError("OPENROUTER_API_KEY not found in .env file")
57
+ self.api_url = settings.api_url
58
+ self.headers = {
59
+ "Authorization": f"Bearer {self.openrouter_api_key}",
60
+ "Content-Type": "application/json",
61
+ "HTTP-Referer": "https://localhost:3000",
62
+ "X-Title": "Financial Note Generator"
63
+ }
64
+ self.note_templates = self.load_note_templates()
65
+ self.account_patterns = self._init_account_patterns()
66
+ self.recommended_models = [
67
+ "mistralai/mixtral-8x7b-instruct",
68
+ "mistralai/mistral-7b-instruct-v0.2"
69
+ ]
70
+
71
+ def _init_account_patterns(self) -> Dict[str, Dict[str, Any]]:
72
+ """Initialize account classification patterns."""
73
+ return {
74
+ "10": {
75
+ "keywords": ["security deposit", "long term advance", "deposit", "advance recoverable"],
76
+ "groups": ["Long Term Loans and Advances", "Non-Current Assets"],
77
+ "exclude_keywords": ["short term", "current", "trade"]
78
+ },
79
+ "11": {
80
+ "keywords": ["inventory", "stock", "raw material", "finished goods", "work in progress", "consumables"],
81
+ "groups": ["Inventories", "Current Assets"],
82
+ "exclude_keywords": ["advance", "deposit"]
83
+ },
84
+ "12": {
85
+ "keywords": ["trade receivable", "debtors", "accounts receivable", "sundry debtors"],
86
+ "groups": ["Trade Receivables", "Current Assets"],
87
+ "exclude_keywords": ["advance", "deposit"]
88
+ },
89
+ "13": {
90
+ "keywords": ["cash", "bank", "petty cash", "cash on hand", "current account", "savings account", "fixed deposit"],
91
+ "groups": ["Cash and Bank Balances", "Current Assets"],
92
+ "exclude_keywords": ["advance", "loan"]
93
+ },
94
+ "14": {
95
+ "keywords": ["prepaid", "advance", "short term", "employee advance", "supplier advance", "advance tax", "tds", "gst", "statutory"],
96
+ "groups": ["Short Term Loans and Advances", "Current Assets"],
97
+ "exclude_keywords": ["long term", "security deposit"]
98
+ },
99
+ "15": {
100
+ "keywords": ["interest accrued", "accrued income", "other current", "miscellaneous current"],
101
+ "groups": ["Other Current Assets", "Current Assets"],
102
+ "exclude_keywords": ["trade", "advance"]
103
+ }
104
+ }
105
+
106
+ def load_note_templates(self) -> Dict[str, Any]:
107
+ """Load note templates from app.new.py file."""
108
+ try:
109
+ from .new import note_templates
110
+ return note_templates
111
+ except ImportError as e:
112
+ logger.error(f"Error importing note_templates from app.new: {e}")
113
+ return {}
114
+ except Exception as e:
115
+ logger.error(f"Unexpected error loading note_templates: {e}")
116
+ return {}
117
+
118
+ def load_trial_balance(self, file_path: str = settings.trial_balance_json) -> Optional[Dict[str, Any]]:
119
+ """Load the classified trial balance from Excel or JSON."""
120
+ try:
121
+ if file_path.endswith('.json'):
122
+ with open(file_path, 'r', encoding='utf-8') as f:
123
+ data = json.load(f)
124
+ if isinstance(data, list):
125
+ accounts = data
126
+ elif isinstance(data, dict):
127
+ accounts = data.get('accounts', [])
128
+ else:
129
+ logger.error(f"Unexpected trial balance format: {type(data)}")
130
+ return None
131
+ logger.info(f"Loaded trial balance with {len(accounts)} accounts")
132
+ return {"accounts": accounts}
133
+ elif file_path.endswith('.xlsx'):
134
+ from app.extract import extract_trial_balance_data
135
+ accounts = extract_trial_balance_data(file_path)
136
+ logger.info(f"Extracted trial balance with {len(accounts)} accounts from Excel")
137
+ return {"accounts": accounts}
138
+ else:
139
+ logger.error(f"Unsupported file type: {file_path}")
140
+ return None
141
+ except FileNotFoundError:
142
+ logger.error(f"Trial balance file not found: {file_path}")
143
+ return None
144
+ except Exception as e:
145
+ logger.error(f"Error loading trial balance: {e}")
146
+ return None
147
+
148
+ def classify_accounts_by_note(self, trial_balance_data: Dict[str, Any], note_number: str) -> List[Dict[str, Any]]:
149
+ """Classify accounts based on note number and patterns"""
150
+ if not trial_balance_data or "accounts" not in trial_balance_data:
151
+ return []
152
+
153
+ classified_accounts = []
154
+ patterns = self.account_patterns.get(note_number, {})
155
+ keywords = patterns.get("keywords", [])
156
+ groups = patterns.get("groups", [])
157
+ exclude_keywords = patterns.get("exclude_keywords", [])
158
+
159
+ for account in trial_balance_data["accounts"]:
160
+ account_name = account.get("account_name", "").lower()
161
+ account_group = account.get("group", "")
162
+
163
+ if any(exclude_word.lower() in account_name for exclude_word in exclude_keywords):
164
+ continue
165
+
166
+ keyword_match = any(keyword.lower() in account_name for keyword in keywords)
167
+ group_match = account_group in groups
168
+
169
+ if keyword_match or group_match:
170
+ classified_accounts.append(account)
171
+
172
+ logger.info(f"Classified {len(classified_accounts)} accounts for Note {note_number}")
173
+ return classified_accounts
174
+
175
+ def safe_amount_conversion(self, amount: Any, conversion_factor: float = 100000) -> float:
176
+ """Safely convert amount to lakhs"""
177
+ try:
178
+ if isinstance(amount, str):
179
+ cleaned = re.sub(r'[^\d.-]', '', amount)
180
+ amount_float = float(cleaned) if cleaned else 0.0
181
+ else:
182
+ amount_float = float(amount) if amount is not None else 0.0
183
+ return round(amount_float / conversion_factor, 2)
184
+ except (ValueError, TypeError):
185
+ return 0.0
186
+
187
+ def calculate_totals(self, accounts: List[Dict[str, Any]], conversion_factor: float = 100000) -> Tuple[float, float]:
188
+ """Calculate totals with safe amount conversion"""
189
+ total_amount = 0.0
190
+ for account in accounts:
191
+ amount = self.safe_amount_conversion(account.get("amount", 0), 1)
192
+ total_amount += amount
193
+ total_lakhs = round(total_amount / conversion_factor, 2)
194
+ return total_amount, total_lakhs
195
+
196
+ def categorize_accounts(self, accounts: List[Dict[str, Any]], note_number: str) -> Dict[str, List[Dict[str, Any]]]:
197
+ """Categorize accounts based on note-specific rules"""
198
+ categories = {
199
+ "prepaid_expenses": [],
200
+ "other_advances": [],
201
+ "advance_tax": [],
202
+ "statutory_balances": [],
203
+ "uncategorized": []
204
+ } if note_number == "14" else {}
205
+
206
+ for account in accounts:
207
+ account_name = account.get("account_name", "").lower()
208
+ categorized = False
209
+
210
+ if note_number == "14":
211
+ if "prepaid" in account_name:
212
+ categories["prepaid_expenses"].append(account)
213
+ categorized = True
214
+ elif any(word in account_name for word in ["advance tax", "tax advance", "income tax"]):
215
+ categories["advance_tax"].append(account)
216
+ categorized = True
217
+ elif any(word in account_name for word in ["tds", "gst", "statutory", "government", "vat", "pf", "esi"]):
218
+ categories["statutory_balances"].append(account)
219
+ categorized = True
220
+ elif any(word in account_name for word in ["advance", "deposit", "recoverable", "employee advance", "supplier advance"]):
221
+ categories["other_advances"].append(account)
222
+ categorized = True
223
+
224
+ if not categorized:
225
+ categories["uncategorized"].append(account)
226
+
227
+ return categories
228
+
229
+ def calculate_category_totals(self, categories: Dict[str, List[Dict[str, Any]]], conversion_factor: float = 100000) -> Tuple[Dict[str, Dict[str, Any]], float]:
230
+ """Calculate totals for each category"""
231
+ category_totals = {}
232
+ grand_total = 0.0
233
+
234
+ for category_name, accounts in categories.items():
235
+ if not isinstance(accounts, list):
236
+ continue
237
+ total_amount = 0.0
238
+ for account in accounts:
239
+ amount = self.safe_amount_conversion(account.get("amount", 0), 1)
240
+ total_amount += amount
241
+ total_lakhs = round(total_amount / conversion_factor, 2)
242
+ category_totals[category_name] = {
243
+ "amount": total_amount,
244
+ "lakhs": total_lakhs,
245
+ "count": len(accounts),
246
+ "accounts": [acc.get("account_name", "") for acc in accounts]
247
+ }
248
+ grand_total += total_amount
249
+
250
+ return category_totals, round(grand_total / conversion_factor, 2)
251
+
252
+ def build_llm_prompt(self, note_number: str, trial_balance_data: Dict[str, Any], classified_accounts: List[Dict[str, Any]]) -> Optional[str]:
253
+ """Build dynamic LLM prompt based on note template and classified accounts"""
254
+ if note_number not in self.note_templates:
255
+ return None
256
+
257
+ template = self.note_templates[note_number]
258
+ total_amount, total_lakhs = self.calculate_totals(classified_accounts)
259
+ categories = self.categorize_accounts(classified_accounts, note_number)
260
+ category_totals, grand_total_lakhs = self.calculate_category_totals(categories)
261
+
262
+ context = {
263
+ "note_info": {
264
+ "number": note_number,
265
+ "title": template.get("title", ""),
266
+ "full_title": template.get("full_title", "")
267
+ },
268
+ "financial_data": {
269
+ "total_accounts": len(classified_accounts),
270
+ "total_amount": total_amount,
271
+ "total_lakhs": total_lakhs,
272
+ "grand_total_lakhs": grand_total_lakhs
273
+ },
274
+ "categories": category_totals,
275
+ "trial_balance": trial_balance_data,
276
+ "current_date": datetime.now().strftime("%Y-%m-%d"),
277
+ "financial_year": "2023-24"
278
+ }
279
+
280
+ prompt = (
281
+ f"\nYou are a financial reporting AI system with two roles:\n"
282
+ f"1. ACCOUNTANT — You extract, compute, and classify data from the financial context and trial balance.\n"
283
+ f"2. AUDITOR — You review the Accountant’s output for accuracy, assumptions, and consistency with reporting standards.\n"
284
+ f"\nYour task is to generate a financial note titled: \"{template['full_title']}\" strictly following the JSON structure below, based on the provided financial context and trial balance data.\n"
285
+ f"\n---\n**CRITICAL RULES**\n"
286
+ f"- Respond ONLY with a valid JSON object (no markdown, no explanations).\n"
287
+ f"- If a value is unavailable or not calculable, use `0.0`.\n"
288
+ f"- Strictly Convert all ₹ amounts to lakhs by dividing by 100000 and round to 2 decimal places.\n"
289
+ f"- Ensure that category subtotals **match** the grand total.\n"
290
+ f"- Return a key `markdown_content` containing a markdown-formatted table for this note.\n"
291
+ f"- Validate that your JSON structure matches the `TEMPLATE STRUCTURE` exactly.\n"
292
+ f"- Perform intelligent classification: if an entry from the trial balance clearly fits a category, assign it logically.\n"
293
+ f"- If data is ambiguous, make a conservative estimate, and record it in an `assumptions` field inside the JSON.\n"
294
+ f"\n---\n**REFLECTION**\n"
295
+ f"- After generating the financial note, reflect on the process: Did you miss any data? Are there any uncertainties or assumptions that should be highlighted?\n"
296
+ f"- Explicitly mention any limitations, ambiguities, or areas where further information would improve accuracy in the `assumptions` field.\n"
297
+ f"\n**REFLEXION**\n"
298
+ f"- Before finalizing the output, review your own reasoning and calculations. Double-check that all ₹ amounts are converted to lakhs and that category subtotals match the grand total.\n"
299
+ f"- If you spot any inconsistencies or possible errors, correct them and note your corrections in the `assumptions` field.\n"
300
+ f"\n**TALES**\n"
301
+ f"- For each major category or unusual entry, briefly narrate (in the `assumptions` field) the story or logic behind its classification, especially if it required inference or was ambiguous.\n"
302
+ f"- Use the `assumptions` field to share any tales of how you mapped trial balance entries to categories, including any conservative estimates or judgment calls.\n"
303
+ f"\n---\n**TEMPLATE STRUCTURE**\n{json.dumps(template, indent=2)}\n"
304
+ f"\n---\n**TRIAL BALANCE & CONTEXT**\n{json.dumps(context, indent=2)}\n"
305
+ f"\n---\n**CATEGORY RULES FOR NOTE 14 (Short Term Loans and Advances):**\n"
306
+ f"- Categorize entries under:\n"
307
+ f" - Unsecured, considered good:\n"
308
+ f" - Prepaid Expenses\n"
309
+ f" - Other Advances\n"
310
+ f" - Other loans and advances:\n"
311
+ f" - Advance Tax\n"
312
+ f" - Balances with statutory/government authorities\n"
313
+ f"- Use logical inference to map trial balance entries into these subcategories\n"
314
+ f"- If values for March 31, 2023 are missing, default to 0\n"
315
+ f"- Ensure the sum of all subcategories = `Total`\n"
316
+ f"\n---\n**REQUIRED OUTPUT JSON FORMAT**\n"
317
+ f"- The JSON must include:\n"
318
+ f" - All categories and subcategories with March 2024 and March 2023 values\n"
319
+ f" - A computed `grand_total_lakhs`\n"
320
+ f" - A `markdown_content` with the financial note table\n"
321
+ f" - A `generated_on` timestamp\n"
322
+ f" - An `assumptions` field (optional, if any data was inferred or missing)\n"
323
+ f"\n---\nGenerate the final JSON now:\n"
324
+ )
325
+
326
+ return prompt
327
+
328
+ def call_openrouter_api(self, prompt: str) -> Optional[str]:
329
+ """Make API call to OpenRouter with model fallback"""
330
+ for model in self.recommended_models:
331
+ logger.info(f"Trying model: {model}")
332
+ payload = {
333
+ "model": model,
334
+ "messages": [
335
+ {"role": "system", "content": "You are a financial reporting expert. Always respond with valid JSON only."},
336
+ {"role": "user", "content": prompt}
337
+ ],
338
+ "max_tokens": 8000,
339
+ "temperature": 0.1,
340
+ "top_p": 0.9
341
+ }
342
+ try:
343
+ response = requests.post(
344
+ self.api_url,
345
+ headers=self.headers,
346
+ json=payload,
347
+ timeout=30 # <-- Add timeout here!
348
+ )
349
+ response.raise_for_status()
350
+ result = response.json()
351
+ content = result['choices'][0]['message']['content']
352
+ logger.info(f"Successful response from {model}")
353
+ return content
354
+ except Exception as e:
355
+ logger.error(f"Failed with {model}: {e}")
356
+ continue
357
+ logger.error("All models failed")
358
+ return None
359
+
360
+ def extract_json_from_markdown(self, response_text: str) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
361
+ """Extract JSON from response, handling markdown code blocks"""
362
+ response_text = response_text.strip()
363
+ json_patterns = [
364
+ r'```json\s*(.*?)\s*```',
365
+ r'```\s*(.*?)\s*```',
366
+ r'(\{.*\})'
367
+ ]
368
+
369
+ for pattern in json_patterns:
370
+ match = re.search(pattern, response_text, re.DOTALL)
371
+ if match:
372
+ try:
373
+ json_data = json.loads(match.group(1))
374
+ return json_data, match.group(1)
375
+ except json.JSONDecodeError:
376
+ continue
377
+
378
+ try:
379
+ json_data = json.loads(response_text)
380
+ return json_data, response_text
381
+ except json.JSONDecodeError:
382
+ return None, None
383
+
384
+ def save_generated_note(self, note_data: str, note_number: str, output_dir: str = settings.output_dir) -> bool:
385
+ """Save the generated note to file in both JSON and markdown formats"""
386
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
387
+ json_output_path = f"{output_dir}/notes.json"
388
+ raw_output_path = f"{output_dir}/notes_raw.txt"
389
+ formatted_md_path = f"{output_dir}/notes_formatted.md"
390
+
391
+ try:
392
+ with open(raw_output_path, 'w', encoding='utf-8') as f:
393
+ f.write(note_data)
394
+ json_data, json_string = self.extract_json_from_markdown(note_data)
395
+ if json_data:
396
+ json_data = convert_note_json_to_lakhs(json_data)
397
+ with open(json_output_path, 'w', encoding='utf-8') as f:
398
+ json.dump(json_data, f, indent=2, ensure_ascii=False)
399
+ logger.info(f"JSON saved to {json_output_path}")
400
+ md_content = json_data.get('markdown_content')
401
+ if not md_content:
402
+ md_content = f"# Note {note_number}\n\n```json\n{json.dumps(json_data, indent=2)}\n```"
403
+ with open(formatted_md_path, 'w', encoding='utf-8') as f:
404
+ f.write(md_content)
405
+ return True
406
+ else:
407
+ fallback_json = {
408
+ "note_number": note_number,
409
+ "raw_response": note_data,
410
+ "error": "Could not parse JSON from response",
411
+ "generated_on": datetime.now().isoformat()
412
+ }
413
+ with open(json_output_path, 'w', encoding='utf-8') as f:
414
+ json.dump(fallback_json, f, indent=2, ensure_ascii=False)
415
+ logger.warning(f"Fallback JSON saved to {json_output_path}")
416
+ return False
417
+ except Exception as e:
418
+ logger.error(f"Error saving files: {e}")
419
+ return False
420
+
421
+ def generate_note(self, note_number: str, trial_balance_path: str = settings.trial_balance_json) -> bool:
422
+ """Generate a specific note based on note number"""
423
+ if note_number not in self.note_templates:
424
+ logger.error(f"Note template {note_number} not found")
425
+ return False
426
+
427
+ logger.info(f"Starting Note {note_number} generation...")
428
+ trial_balance = self.load_trial_balance(trial_balance_path)
429
+ if not trial_balance:
430
+ return False
431
+
432
+ classified_accounts = self.classify_accounts_by_note(trial_balance, note_number)
433
+ prompt = self.build_llm_prompt(note_number, trial_balance, classified_accounts)
434
+ if not prompt:
435
+ logger.error("Failed to build prompt")
436
+ return False
437
+
438
+ response = self.call_openrouter_api(prompt)
439
+ if not response:
440
+ logger.error("Failed to get API response")
441
+ return False
442
+
443
+ success = self.save_generated_note(response, note_number)
444
+ logger.info(f"Note {note_number} {'generated successfully' if success else 'generated with issues'}")
445
+ return success
446
+
447
+ def generate_all_notes(self, trial_balance_path: str = settings.trial_balance_json) -> Dict[str, bool]:
448
+ """Generate all available notes and save them in a single notes.json file."""
449
+ logger.info(f"Starting generation of all {len(self.note_templates)} notes...")
450
+ results = {}
451
+ all_notes = []
452
+ for note_number in self.note_templates.keys():
453
+ logger.info(f"Processing Note {note_number}")
454
+ trial_balance = self.load_trial_balance(trial_balance_path)
455
+ if not trial_balance:
456
+ results[note_number] = False
457
+ continue
458
+ classified_accounts = self.classify_accounts_by_note(trial_balance, note_number)
459
+ prompt = self.build_llm_prompt(note_number, trial_balance, classified_accounts)
460
+ if not prompt:
461
+ results[note_number] = False
462
+ continue
463
+ response = self.call_openrouter_api(prompt)
464
+ if not response:
465
+ results[note_number] = False
466
+ continue
467
+ json_data, _ = self.extract_json_from_markdown(response)
468
+ if json_data:
469
+ all_notes.append(json_data)
470
+ results[note_number] = True
471
+ else:
472
+ results[note_number] = False
473
+ import time
474
+ time.sleep(1)
475
+ # Save all notes in one file
476
+ output_dir = settings.output_dir
477
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
478
+ with open(f"{output_dir}/notes.json", "w", encoding="utf-8") as f:
479
+ json.dump({"notes": all_notes}, f, indent=2, ensure_ascii=False)
480
+ successful = sum(1 for success in results.values() if success)
481
+ total = len(results)
482
+ logger.info(f"GENERATION SUMMARY: {successful}/{total} notes generated successfully")
483
+ logger.info(f"All notes saved to {output_dir}/notes.json")
484
+ return results
485
+
486
+ def main() -> None:
487
+ """Main function to run the flexible note generator"""
488
+ try:
489
+ generator = FlexibleFinancialNoteGenerator()
490
+ if not generator.note_templates:
491
+ logger.error("No note templates loaded. Check app/new.py")
492
+ return
493
+
494
+ logger.info(f"Loaded {len(generator.note_templates)} note templates")
495
+ choice = input("\nGenerate (1) specific note or (2) all notes? Enter 1 or 2: ").strip()
496
+
497
+ if choice == "1":
498
+ available_notes = list(generator.note_templates.keys())
499
+ print(f"Available notes: {', '.join(available_notes)}")
500
+ note_number = input("Enter note number: ").strip()
501
+ if note_number in available_notes:
502
+ success = generator.generate_note(note_number)
503
+ logger.info(f"Note {note_number} {'generated successfully' if success else 'generated with issues'}")
504
+ else:
505
+ logger.error(f"Note {note_number} not found")
506
+ elif choice == "2":
507
+ results = generator.generate_all_notes()
508
+ successful = sum(1 for success in results.values() if success)
509
+ total = len(results)
510
+ logger.info(f"{successful}/{total} notes generated successfully")
511
+ else:
512
+ logger.error("Invalid choice. Enter 1 or 2.")
513
+ except Exception as e:
514
+ logger.error(f"Error: {e}", exc_info=True)
515
+
516
+ if __name__ == "__main__":
517
+ main()
app/notes.py ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import Any, Dict, List, Optional
3
+ from pydantic import BaseModel, ValidationError
4
+ from pydantic_settings import BaseSettings
5
+ from app.utils import clean_value, to_lakhs
6
+
7
+ # Configure logging
8
+ logging.basicConfig(level=logging.INFO)
9
+ logger = logging.getLogger(__name__)
10
+
11
+ class Settings(BaseSettings):
12
+ """Application settings loaded from environment variables or .env file."""
13
+ statutory_dues: float = 7935166.72
14
+
15
+ settings = Settings()
16
+
17
+ class MatchedAccount(BaseModel):
18
+ account: str
19
+ amount: float
20
+ group: str
21
+
22
+ class NoteResult(BaseModel):
23
+ total: float
24
+ matched_accounts: List[MatchedAccount]
25
+ over_6m: Optional[float] = None
26
+
27
+ def calculate_note(
28
+ df: Any,
29
+ note_name: str,
30
+ keywords: List[str],
31
+ exclude: Optional[List[str]] = None,
32
+ other_df: Optional[Any] = None
33
+ ) -> Dict[str, Any]:
34
+ """
35
+ Calculate total and matched accounts for a note.
36
+ Handles special cases for Trade Receivables and Other Current Liabilities.
37
+ """
38
+ account_col = 'account_name' if 'account_name' in df.columns else df.columns[0]
39
+ balance_col = 'amount' if 'amount' in df.columns else (df.columns[1] if len(df.columns) > 1 else None)
40
+
41
+ if not balance_col:
42
+ return {'total': 0, 'matched_accounts': []}
43
+
44
+ df = df.fillna(0)
45
+ total = 0
46
+ matched_accounts = []
47
+
48
+ for idx, row in df.iterrows():
49
+ account_name = str(row[account_col]).strip().lower()
50
+ if any(kw.lower() in account_name for kw in keywords) and (not exclude or not any(ex.lower() in account_name for ex in exclude)):
51
+ amount = clean_value(row[balance_col])
52
+ matched = {
53
+ 'account': str(row[account_col]),
54
+ 'amount': amount,
55
+ 'group': row.get('group', 'Unknown') if 'group' in df.columns else 'Unknown'
56
+ }
57
+ try:
58
+ validated = MatchedAccount(**matched)
59
+ matched_accounts.append(validated)
60
+ except ValidationError as ve:
61
+ logger.warning(f"Validation error for matched account: {ve}")
62
+ matched_accounts.append(matched)
63
+ total += amount
64
+
65
+ # Special case for Trade Receivables
66
+ if other_df is not None and note_name == '12. Trade Receivables':
67
+ if 'Pending' in other_df.columns:
68
+ total = other_df['Pending'].sum()
69
+ over_6m = other_df[['360 to 720 days', '720 to 1440 days', '(> 1440 days )']].sum().sum()
70
+ else:
71
+ over_6m = 0
72
+ return {'total': total, 'over_6m': over_6m, 'matched_accounts': matched_accounts}
73
+
74
+ # Special case for Other Current Liabilities
75
+ if note_name == '7. Other Current Liabilities' and other_df is None:
76
+ total += settings.statutory_dues
77
+
78
+ return {'total': total, 'matched_accounts': matched_accounts}
79
+
80
+ def generate_notes(
81
+ tb_df: Any,
82
+ debtors_df: Optional[Any] = None,
83
+ creditors_df: Optional[Any] = None
84
+ ) -> List[Dict[str, Any]]:
85
+ """
86
+ Generate notes from trial balance DataFrame.
87
+ Returns a list of notes with content and matched account count.
88
+ """
89
+ notes = []
90
+ note_mappings: Dict[str, Dict[str, Any]] = {
91
+ '2. Share Capital': {'keywords': ['Share Capital', 'share capital', 'equity share', 'paid up']},
92
+ '3. Reserves and Surplus': {'keywords': ['Reserves', 'Surplus', 'reserves', 'surplus', 'retained earnings']},
93
+ '4. Long Term Borrowings': {'keywords': ['loan', 'borrowing', 'term loan'], 'exclude': ['current maturities', 'short term']},
94
+ '5. Deferred Tax Liability': {'keywords': ['Deferred Tax', 'deferred tax']},
95
+ '6. Trade Payables': {'keywords': ['Creditors', 'creditors', 'trade payable', 'suppliers']},
96
+ '7. Other Current Liabilities': {'keywords': ['Expenses Payable', 'Current Maturities', 'payable', 'accrued']},
97
+ '8. Short Term Provisions': {'keywords': ['Provision', 'provision', 'taxation']},
98
+ '9. Fixed Assets': {'keywords': ['Equipment', 'Furniture', 'Building', 'Vehicle', 'Motor', 'Asset', 'plant', 'machinery']},
99
+ '10. Long Term Loans and Advances': {'keywords': ['Long Term', 'Security Deposits', 'advances', 'deposits']},
100
+ '11. Inventories': {'keywords': ['Stock', 'Inventory', 'stock', 'inventory', 'goods']},
101
+ '12. Trade Receivables': {'keywords': ['Receivables', 'receivables', 'debtors', 'trade receivable']},
102
+ '13. Cash and Bank Balances': {'keywords': ['Cash-in-hand', 'Bank accounts','Deposits']},
103
+ '14. Short Term Loans and Advances': {'keywords': ['Prepaid Expenses', 'TDS Receivables', 'Loans & Advances', 'TCS RECEIVABLES', 'TDS Advance Tax Paid', 'Advance to Perennail']},
104
+ '15. Other Current Assets': {'keywords': ['Interest accrued', 'accrued', 'current asset']},
105
+ '16. Revenue from Operations': {'keywords': ['Revenue', 'Sales', 'Service', 'income', 'operations']},
106
+ '17. Other Income': {'keywords': ['Interest on FD', 'Interest on Income Tax Refund', 'Unadjusted Forex Gain/Loss', 'Forex Gain / Loss']},
107
+ '18. Cost of Materials Consumed': {'keywords': ['opening stock', 'Bio Lab Consumables', 'Non GST', 'Purchase GST','closing stock']},
108
+ '28. Earnings per Share': {'keywords': ['Profit', 'Loss', 'profit', 'loss']},
109
+ '29. Related Party Disclosures': {'keywords': []},
110
+ '30. Financial Ratios': {'keywords': ['Stock', 'Cash', 'Bank', 'Receivables', 'Creditors', 'Payable']}
111
+ }
112
+
113
+ for note_name, mapping in note_mappings.items():
114
+ keywords = mapping['keywords']
115
+ exclude = mapping.get('exclude', [])
116
+ other_df = debtors_df if note_name == '12. Trade Receivables' else creditors_df if note_name == '6. Trade Payables' else None
117
+ result = calculate_note(tb_df, note_name, keywords, exclude, other_df)
118
+
119
+ content = ""
120
+ if note_name == '2. Share Capital':
121
+ content = f"""
122
+ | Particulars | 2024-03-31 | 2023-03-31 |
123
+ |-------------|------------|------------|
124
+ | Authorised shares | | |
125
+ | 75,70,000 equity shares of ₹ 10/- each | {to_lakhs(75700000)} | {to_lakhs(75700000)} |
126
+ | Issued, subscribed and fully paid-up shares | | |
127
+ | 54,25,210 equity shares of ₹ 10/- each | {to_lakhs(result['total'])} | {to_lakhs(54252100)} |
128
+ | Total issued, subscribed and fully paid-up share capital | {to_lakhs(result['total'])} | {to_lakhs(54252100)} |
129
+ """
130
+ elif note_name == '3. Reserves and Surplus':
131
+ content = f"""
132
+ | March,31 2024 | March,31 2023
133
+ | {note_name.split('.', 1)[1].strip() if '.' in note_name else note_name} | {to_lakhs(result['total'])}
134
+ """
135
+ elif note_name == '4. Long Term Borrowings':
136
+ content = f"""
137
+ | March,31 2024 | March,31 2023
138
+ | {note_name.split('.', 1)[1].strip() if '.' in note_name else note_name} | {to_lakhs(result['total'])}
139
+ """
140
+ elif note_name == '5. Deferred Tax Liability':
141
+ content = f"""
142
+ | March,31 2024 | March,31 2023
143
+ | {note_name.split('.', 1)[1].strip() if '.' in note_name else note_name} | {to_lakhs(result['total'])}
144
+ """
145
+ elif note_name == '6. Trade Payables':
146
+ content = f"""
147
+ | March,31 2024 | March,31 2023
148
+ | {note_name.split('.', 1)[1].strip() if '.' in note_name else note_name} | {to_lakhs(result['total'])}
149
+ """
150
+ elif note_name == '7. Other Current Liabilities':
151
+ expenses_payable = calculate_note(tb_df, note_name, ['Expenses Payable', 'payable', 'accrued'])['total']
152
+ current_maturities = calculate_note(tb_df, note_name, ['Current Maturities', 'current portion'])['total']
153
+ statutory_dues = 7935166.72 # Static value
154
+ content = f"""
155
+ | March,31 2024 | March,31 2023
156
+
157
+ | Current Maturities of Long Term Borrowings | {to_lakhs(current_maturities)} | {to_lakhs(13920441)}
158
+ | Outstanding Liabilities for Expenses | {to_lakhs(expenses_payable)} | {to_lakhs(15688272)}
159
+ | Statutory dues | {to_lakhs(statutory_dues)} | {to_lakhs(4803131.66)}
160
+ | {to_lakhs(current_maturities + expenses_payable + statutory_dues)} | {to_lakhs(34411844.66)}
161
+ """
162
+ elif note_name == '8. Short Term Provisions':
163
+ content = f"""
164
+ | March,31 2024 | March,31 2023
165
+ | {note_name.split('.', 1)[1].strip() if '.' in note_name else note_name} | {to_lakhs(result['total'])}
166
+ """
167
+ elif note_name == '9. Fixed Assets':
168
+ equipments = calculate_note(tb_df, note_name, ['Equipment', 'equipment'])['total']
169
+ furniture = calculate_note(tb_df, note_name, ['Furniture', 'furniture', 'fixture'])['total']
170
+ building = calculate_note(tb_df, note_name, ['Building', 'building'])['total']
171
+ vehicle = calculate_note(tb_df, note_name, ['Vehicle', 'vehicle', 'car'])['total']
172
+ content = f"""
173
+ | Particulars | Gross Carrying Value | Accumulated Depreciation | Net Carrying Value |
174
+ |-------------|----------------------|--------------------------|--------------------|
175
+ | As at 1st April 2023 | Additions | Deletion | As at 31st March 2024 | As at 1st April 2023 | For the year | Deletion | As at 31st March 2024 | As at 31st March 2024 | As at 1st April 2023 |
176
+ | Tangible Assets | | | | | | | | | |
177
+ | Buildings | {to_lakhs(312655)} | {to_lakhs(building)} | 0 | {to_lakhs(312655 + building)} | {to_lakhs(312654)} | {to_lakhs(1478808)} | 0 | {to_lakhs(1791462)} | {to_lakhs(building)} | {to_lakhs(1)} |
178
+ | Equipments | {to_lakhs(equipments)} | {to_lakhs(0)} | 0 | {to_lakhs(equipments)} | {to_lakhs(0)} | {to_lakhs(0)} | 0 | {to_lakhs(0)} | {to_lakhs(equipments)} | {to_lakhs(equipments)} |
179
+ | Furniture & Fixtures | {to_lakhs(furniture)} | {to_lakhs(0)} | 0 | {to_lakhs(furniture)} | {to_lakhs(0)} | {to_lakhs(0)} | 0 | {to_lakhs(0)} | {to_lakhs(furniture)} | {to_lakhs(furniture)} |
180
+ | Motor Vehicle | {to_lakhs(vehicle)} | 0 | 0 | {to_lakhs(vehicle)} | {to_lakhs(0)} | {to_lakhs(752982.45)} | 0 | {to_lakhs(752982.45)} | {to_lakhs(vehicle - 752982.45)} | {to_lakhs(vehicle)} |
181
+ """
182
+ elif note_name == '11. Inventories':
183
+ content = f"""
184
+ | March,31 2024 | March,31 2023
185
+ | Consumables | {to_lakhs(result['total'])}
186
+ """
187
+ elif note_name == '12. Trade Receivables':
188
+ over_6m = result.get('over_6m', 0)
189
+ content = f"""
190
+ | Particulars | 2024-03-31 | 2023-03-31 |
191
+ |-------------|------------|------------|
192
+ | Unsecured, considered good | | |
193
+ | Outstanding for a period exceeding six months | {to_lakhs(over_6m)} | {to_lakhs(10465395)} |
194
+ | Total | {to_lakhs(result['total'])} | {to_lakhs(103758506)} |
195
+ """
196
+ elif note_name == '13. Cash and Bank Balances':
197
+ cash_in_hand = calculate_note(tb_df, note_name, ['Cash-in-hand'])['total']
198
+ bank_accounts = calculate_note(tb_df, note_name, ['Bank accounts'])['total']
199
+ fixed_deposit = calculate_note(tb_df, note_name, ['Deposits'])['total']
200
+ total = cash_in_hand + bank_accounts + fixed_deposit
201
+ content = f"""
202
+ | Particulars | March 31, 2024 | March 31, 2023 |
203
+ |------------------|----------------|----------------|
204
+ | Cash in hand | {to_lakhs(cash_in_hand)} | - |
205
+ | Bank accounts | {to_lakhs(bank_accounts)} | - |
206
+ | Fixed Deposit | {to_lakhs(fixed_deposit)} | - |
207
+ | **Total** | **{to_lakhs(total)}** | - |
208
+ """
209
+ result['total'] = total
210
+ elif note_name == '14. Short Term Loans and Advances':
211
+ otherAdvances = calculate_note(tb_df, note_name, ['Loans & Advances'])['total']
212
+ PrepaidExpenses = calculate_note(tb_df, note_name, ['Prepaid Expenses'])['total']
213
+ advancetaxes = calculate_note(tb_df, note_name, ['TDS Advance Tax Paid'])['total']
214
+ balances = calculate_note(tb_df, note_name, ['TDS Receivables'])['total']
215
+ total = otherAdvances + PrepaidExpenses + advancetaxes + balances
216
+ content = f"""
217
+ | Particulars | March 31, 2024 | March 31, 2023 |
218
+ |------------------|----------------|----------------|
219
+ | Prepaid Expenses | {to_lakhs(PrepaidExpenses)} | - |
220
+ | Other Advances | {to_lakhs(otherAdvances)} | - |
221
+ | Advance tax | {to_lakhs(advancetaxes)} | - |
222
+ | Balances with statutory/government authorities | {to_lakhs(balances)} | - |
223
+ | **Total** | **{to_lakhs(total)}** | - |
224
+ """
225
+ result['total'] = total
226
+ elif note_name == '16. Revenue from Operations':
227
+ ServicingBABEExport = calculate_note(tb_df, note_name, ['Servicing of BA/BE PROJECTS EXPORT'])['total']
228
+ WorkingStandardsExport = calculate_note(tb_df, note_name, ['Working Standards - Export'])['total']
229
+ exports = ServicingBABEExport + WorkingStandardsExport
230
+
231
+ ServicingBABEInterState = calculate_note(tb_df, note_name, ['Servicing of BA/BE PROJECTS-Inter State'])['total']
232
+ ServicingBABEIntraState = calculate_note(tb_df, note_name, ['Servicing of BA/BE PROJECTS-Intra State'])['total']
233
+ ServicingBAIntraState = calculate_note(tb_df, note_name, ['SERVICING OF BA PROJECTS-Intra State'])['total']
234
+ ServicingClinicalIntraState = calculate_note(tb_df, note_name, ['SERVICING OF ONLY CLINICAL INTRA STATE'])['total']
235
+ domestic = ServicingBABEInterState + ServicingBABEIntraState + ServicingBAIntraState + ServicingClinicalIntraState
236
+
237
+ total = exports + domestic
238
+
239
+ content = f"""
240
+ | | March 31, 2024 | March 31, 2023 |
241
+ |--------------------|------------------------|----------------|
242
+ | Sale of Services | | |
243
+ | Domestic | {to_lakhs(domestic)} | - |
244
+ | Exports | {to_lakhs(exports)} | - |
245
+ | **Total** | {to_lakhs(total)} | - |
246
+ """
247
+ result['total'] = total
248
+ elif note_name == '17. Other Income':
249
+ InterestnFD = calculate_note(tb_df, note_name, ['Interest on FD'])['total']
250
+ InterestonIncomeTaxRefund = calculate_note(tb_df, note_name, ['Interest on Income Tax Refund'])['total']
251
+ UnadjustedForexGainLoss = calculate_note(tb_df, note_name, ['Unadjusted Forex Gain/Loss'])['total']
252
+ ForexGainLoss = calculate_note(tb_df, note_name, ['Forex Gain/Loss'])['total']
253
+ Interestincome = InterestnFD + InterestonIncomeTaxRefund
254
+ ForeignexchangeainNet = UnadjustedForexGainLoss + ForexGainLoss
255
+ total = Interestincome + ForeignexchangeainNet
256
+
257
+ content = f"""
258
+ | Particulars | March 31, 2024 | March 31, 2023 |
259
+ |----------------------------|----------------|----------------|
260
+ | Interest income | {to_lakhs(Interestincome)} | - |
261
+ | Foreign exchange gain (Net)| {to_lakhs(ForeignexchangeainNet)} | - |
262
+ | **Total** | {to_lakhs(total)} | - |
263
+ """
264
+ result['total'] = total
265
+ elif note_name == '18. Cost of Materials Consumed':
266
+ openingstock = calculate_note(tb_df, note_name, ['opening stock'])['total']
267
+ BioLabConsumables = calculate_note(tb_df, note_name, ['Bio Lab Consumables'])['total']
268
+ NonGST = calculate_note(tb_df, note_name, ['Non GST'])['total']
269
+ PurchaseGST = calculate_note(tb_df, note_name, ['Purchase GST'])['total']
270
+ closingstock = calculate_note(tb_df, note_name, ['closing stock'])['total']
271
+
272
+ purchases = BioLabConsumables + NonGST + PurchaseGST
273
+ mid = openingstock + purchases
274
+ total = openingstock + purchases - closingstock
275
+
276
+ content = f"""
277
+ | Particulars | March 31, 2024 | March 31, 2023 |
278
+ |---------------------|----------------|----------------|
279
+ | Opening Stock | {to_lakhs(openingstock)} | - |
280
+ | Purchases | {to_lakhs(purchases)} | - |
281
+ | | {to_lakhs(mid)} | - |
282
+ | Closing Stock | {to_lakhs(closingstock)} | - |
283
+ | Cost of materials consumed | {to_lakhs(total)} | - |
284
+ """
285
+ result['total'] = total
286
+ elif note_name == '30. Financial Ratios':
287
+ current_assets = sum(calculate_note(tb_df, note_name, [kw])['total'] for kw in ['Stock', 'Cash', 'Bank', 'Receivables', 'Prepaid'])
288
+ current_liabilities = sum(calculate_note(tb_df, note_name, [kw])['total'] for kw in ['Creditors', 'Payable'])
289
+ current_ratio = current_assets / abs(current_liabilities) if current_liabilities != 0 else 0
290
+ content = f"""
291
+ | Particulars | 2024-03-31 | 2023-03-31 |
292
+ |-----------------|------------|------------|
293
+ | Current Ratio | {round(current_ratio, 2)} | 2.52 |
294
+ | Current Assets | {to_lakhs(current_assets)} | - |
295
+ | Current Liabilities | {to_lakhs(abs(current_liabilities))} | - |
296
+ """
297
+ else:
298
+ content = f"""
299
+ | March,31 2024 | March,31 2023
300
+ | {note_name.split('.', 1)[1].strip() if '.' in note_name else note_name} | {to_lakhs(result['total'])}
301
+ """
302
+ notes.append({'Note': note_name, 'Content': content, 'Total': result['total'], 'Matched_Accounts': len(result.get('matched_accounts', []))})
303
+ return notes
app/pnl.py ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import logging
4
+ from typing import Optional, Dict, Any
5
+ from openpyxl import Workbook
6
+ from openpyxl.styles import Font, Border, Side, Alignment
7
+ from openpyxl.utils import get_column_letter
8
+ from datetime import datetime
9
+ from pydantic import BaseModel, ValidationError
10
+ from pydantic_settings import BaseSettings
11
+
12
+ # Configure logging
13
+ logging.basicConfig(level=logging.INFO)
14
+ logger = logging.getLogger(__name__)
15
+
16
+ class Settings(BaseSettings):
17
+ """Application settings loaded from environment variables or .env file."""
18
+ notes_folder: str = "generated_notes"
19
+ output_folder: str = "pnl_excel"
20
+ output_file: str = "pnl_report.xlsx"
21
+
22
+ settings = Settings()
23
+
24
+ class NoteData(BaseModel):
25
+ note_number: Optional[str]
26
+ structure: Optional[Any]
27
+ notes_and_disclosures: Optional[list] = []
28
+
29
+ def load_note_data(note_number: str, folder: str = settings.notes_folder) -> Optional[NoteData]:
30
+ """
31
+ Load note data for a specific note_number from notes.json in the specified folder.
32
+ Compatible with {"notes": [ ... ]} structure.
33
+ """
34
+ file_path = os.path.join(folder, "notes.json")
35
+ try:
36
+ with open(file_path, "r", encoding="utf-8") as f:
37
+ data = json.load(f)
38
+ notes = data.get("notes", [])
39
+ for note in notes:
40
+ n_num = note.get("note_number") or note.get("metadata", {}).get("note_number")
41
+ if str(n_num) == str(note_number):
42
+ try:
43
+ return NoteData(**note)
44
+ except ValidationError as ve:
45
+ logger.warning(f"Validation error for note {note_number}: {ve}")
46
+ return None
47
+ logger.warning(f"Note {note_number} not found in {file_path}")
48
+ return None
49
+ except FileNotFoundError:
50
+ logger.warning(f"Note {note_number} file not found at {file_path}")
51
+ return None
52
+ except json.JSONDecodeError:
53
+ logger.warning(f"Invalid JSON in note {note_number}")
54
+ return None
55
+
56
+ def extract_total_from_note(note_data: Optional[NoteData], year: str = "2024") -> float:
57
+ """Extract total value from note data for the specified year."""
58
+ if not note_data or not note_data.structure:
59
+ return 0.0
60
+ for category in note_data.structure:
61
+ if year == "2024" and "total" in category:
62
+ try:
63
+ return float(category["total"])
64
+ except (ValueError, TypeError):
65
+ continue
66
+ elif year == "2023" and "previous_total" in category:
67
+ try:
68
+ return float(category["previous_total"])
69
+ except (ValueError, TypeError):
70
+ continue
71
+ # If no total found, sum up subcategory values
72
+ total = 0.0
73
+ for category in note_data.structure:
74
+ if "subcategories" in category:
75
+ for subcat in category["subcategories"]:
76
+ if year == "2024" and "value" in subcat:
77
+ try:
78
+ total += float(subcat["value"])
79
+ except (ValueError, TypeError):
80
+ continue
81
+ elif year == "2023" and "previous_value" in subcat:
82
+ try:
83
+ total += float(subcat["previous_value"])
84
+ except (ValueError, TypeError):
85
+ continue
86
+ return total
87
+
88
+ def extract_specific_value(note_data: Optional[NoteData], key: str, year: str = "2024") -> float:
89
+ """Extract specific value from note data based on key and year."""
90
+ if not note_data or not note_data.structure:
91
+ return 0.0
92
+ for category in note_data.structure:
93
+ if "subcategories" in category:
94
+ for subcat in category["subcategories"]:
95
+ label = subcat.get("label", "").lower().replace(" ", "").replace("-", "").replace("/", "").replace("&", "")
96
+ if key.lower() in label:
97
+ if year == "2024" and "value" in subcat:
98
+ try:
99
+ return float(subcat["value"])
100
+ except (ValueError, TypeError):
101
+ continue
102
+ elif year == "2023" and "previous_value" in subcat:
103
+ try:
104
+ return float(subcat["previous_value"])
105
+ except (ValueError, TypeError):
106
+ continue
107
+ return 0.0
108
+
109
+ def format_currency(value: float) -> str:
110
+ """Format currency value for display."""
111
+ if isinstance(value, (int, float)) and value != 0:
112
+ return f"{value:,.2f}"
113
+ return "0.00"
114
+
115
+ def generate_pnl_report() -> None:
116
+ """Generate P&L report in Excel format using data from generated_notes folder."""
117
+ wb = Workbook()
118
+ ws = wb.active
119
+ ws.title = "Profit and Loss Statement"
120
+
121
+ # Define styles
122
+ bold_font = Font(bold=True)
123
+ thin_border = Border(left=Side(style="thin"), right=Side(style="thin"),
124
+ top=Side(style="thin"), bottom=Side(style="thin"))
125
+ center_align = Alignment(horizontal="center")
126
+ left_align = Alignment(horizontal="left")
127
+
128
+ # Set column widths
129
+ ws.column_dimensions["A"].width = 50
130
+ ws.column_dimensions["B"].width = 10
131
+ ws.column_dimensions["C"].width = 20
132
+ ws.column_dimensions["D"].width = 20
133
+
134
+ # Header
135
+ ws["A1"] = "Profit and Loss Statement for the Years Ended March 31, 2024 and 2023"
136
+ ws["A1"].font = bold_font
137
+ ws.merge_cells("A1:D1")
138
+ ws["A1"].alignment = center_align
139
+
140
+ # Table headers
141
+ headers = ["Particulars", "Note No.", "Year Ended 31.03.2024", "Year Ended 31.03.2023"]
142
+ for col, header in enumerate(headers, 1):
143
+ cell = ws.cell(row=3, column=col)
144
+ cell.value = header
145
+ cell.font = bold_font
146
+ cell.border = thin_border
147
+ cell.alignment = center_align if col > 1 else left_align
148
+
149
+ # Load all required notes
150
+ notes_data: Dict[str, Optional[NoteData]] = {}
151
+ for note_num in range(16, 29): # Notes 16-28
152
+ notes_data[str(note_num)] = load_note_data(str(note_num))
153
+
154
+ # Calculate values
155
+ revenue_2024 = extract_total_from_note(notes_data.get("16"), "2024")
156
+ revenue_2023 = extract_total_from_note(notes_data.get("16"), "2023")
157
+ other_income_2024 = extract_total_from_note(notes_data.get("17"), "2024")
158
+ other_income_2023 = extract_total_from_note(notes_data.get("17"), "2023")
159
+ total_income_2024 = revenue_2024 + other_income_2024
160
+ total_income_2023 = revenue_2023 + other_income_2023
161
+
162
+ cost_materials_2024 = extract_total_from_note(notes_data.get("18"), "2024")
163
+ cost_materials_2023 = extract_total_from_note(notes_data.get("18"), "2023")
164
+ employee_benefit_2024 = extract_total_from_note(notes_data.get("19"), "2024")
165
+ employee_benefit_2023 = extract_total_from_note(notes_data.get("19"), "2023")
166
+ other_expenses_2024 = extract_total_from_note(notes_data.get("20"), "2024")
167
+ other_expenses_2023 = extract_total_from_note(notes_data.get("20"), "2023")
168
+ depreciation_2024 = extract_total_from_note(notes_data.get("21"), "2024")
169
+ depreciation_2023 = extract_total_from_note(notes_data.get("21"), "2023")
170
+ loss_assets_2024 = extract_total_from_note(notes_data.get("22"), "2024")
171
+ loss_assets_2023 = extract_total_from_note(notes_data.get("22"), "2023")
172
+ finance_costs_2024 = extract_total_from_note(notes_data.get("23"), "2024")
173
+ finance_costs_2023 = extract_total_from_note(notes_data.get("23"), "2023")
174
+ auditor_payment_2024 = extract_total_from_note(notes_data.get("24"), "2024")
175
+ auditor_payment_2023 = extract_total_from_note(notes_data.get("24"), "2023")
176
+
177
+ total_expenses_2024 = (cost_materials_2024 + employee_benefit_2024 + other_expenses_2024 +
178
+ depreciation_2024 + loss_assets_2024 + finance_costs_2024 + auditor_payment_2024)
179
+ total_expenses_2023 = (cost_materials_2023 + employee_benefit_2023 + other_expenses_2023 +
180
+ depreciation_2023 + loss_assets_2023 + finance_costs_2023 + auditor_payment_2023)
181
+
182
+ profit_before_exceptional_2024 = total_income_2024 - total_expenses_2024
183
+ profit_before_exceptional_2023 = total_income_2023 - total_expenses_2023
184
+
185
+ current_tax_2024 = extract_total_from_note(notes_data.get("25"), "2024")
186
+ current_tax_2023 = extract_total_from_note(notes_data.get("25"), "2023")
187
+ deferred_tax_2024 = extract_total_from_note(notes_data.get("26"), "2024")
188
+ deferred_tax_2023 = extract_total_from_note(notes_data.get("26"), "2023")
189
+
190
+ total_tax_2024 = current_tax_2024 + deferred_tax_2024
191
+ total_tax_2023 = current_tax_2023 + deferred_tax_2023
192
+
193
+ profit_after_tax_2024 = profit_before_exceptional_2024 - total_tax_2024
194
+ profit_after_tax_2023 = profit_before_exceptional_2023 - total_tax_2023
195
+
196
+ # P&L line items with calculated values
197
+ line_items = [
198
+ {"label": "I. Revenue from Operations", "note": "16", "value_2024": revenue_2024, "value_2023": revenue_2023},
199
+ {"label": "II. Other Income", "note": "17", "value_2024": other_income_2024, "value_2023": other_income_2023},
200
+ {"label": "III. Total Income (I + II)", "note": "", "value_2024": total_income_2024, "value_2023": total_income_2023},
201
+ {"label": "IV. Expenses", "note": "", "value_2024": "", "value_2023": ""},
202
+ {"label": " Cost of Materials Consumed", "note": "18", "value_2024": cost_materials_2024, "value_2023": cost_materials_2023},
203
+ {"label": " Employee Benefit Expense", "note": "19", "value_2024": employee_benefit_2024, "value_2023": employee_benefit_2023},
204
+ {"label": " Other Expenses", "note": "20", "value_2024": other_expenses_2024, "value_2023": other_expenses_2023},
205
+ {"label": " Depreciation and Amortisation Expense", "note": "21", "value_2024": depreciation_2024, "value_2023": depreciation_2023},
206
+ {"label": " Loss on Sale of Assets & Investments", "note": "22", "value_2024": loss_assets_2024, "value_2023": loss_assets_2023},
207
+ {"label": " Finance Costs", "note": "23", "value_2024": finance_costs_2024, "value_2023": finance_costs_2023},
208
+ {"label": " Payment to Auditor", "note": "24", "value_2024": auditor_payment_2024, "value_2023": auditor_payment_2023},
209
+ {"label": " Total Expenses", "note": "", "value_2024": total_expenses_2024, "value_2023": total_expenses_2023},
210
+ {"label": "V. Profit Before Exceptional and Extraordinary Items and Tax (III - IV)", "note": "", "value_2024": profit_before_exceptional_2024, "value_2023": profit_before_exceptional_2023},
211
+ {"label": "VI. Exceptional Items", "note": "27", "value_2024": extract_total_from_note(notes_data.get("27"), "2024"), "value_2023": extract_total_from_note(notes_data.get("27"), "2023")},
212
+ {"label": "VII. Profit Before Tax (V - VI)", "note": "", "value_2024": profit_before_exceptional_2024, "value_2023": profit_before_exceptional_2023},
213
+ {"label": "VIII. Tax Expense:", "note": "", "value_2024": "", "value_2023": ""},
214
+ {"label": " (1) Current Tax", "note": "25", "value_2024": current_tax_2024, "value_2023": current_tax_2023},
215
+ {"label": " (2) Deferred Tax", "note": "26", "value_2024": deferred_tax_2024, "value_2023": deferred_tax_2023},
216
+ {"label": "IX. Profit After Tax for the period (VII - VIII)", "note": "", "value_2024": profit_after_tax_2024, "value_2023": profit_after_tax_2023}
217
+ ]
218
+
219
+ # Write line items to Excel
220
+ row = 4
221
+ for item in line_items:
222
+ ws.cell(row=row, column=1).value = item["label"]
223
+ ws.cell(row=row, column=2).value = item["note"]
224
+ ws.cell(row=row, column=3).value = format_currency(item["value_2024"]) if item["value_2024"] != "" else ""
225
+ ws.cell(row=row, column=4).value = format_currency(item["value_2023"]) if item["value_2023"] != "" else ""
226
+ for col in range(1, 5):
227
+ ws.cell(row=row, column=col).border = thin_border
228
+ ws.cell(row=row, column=col).alignment = center_align if col > 1 else left_align
229
+ if item["label"].startswith(("I.", "II.", "III.", "IV.", "V.", "VI.", "VII.", "VIII.", "IX.")):
230
+ ws.cell(row=row, column=col).font = bold_font
231
+ row += 1
232
+
233
+ # Add notes and disclosures
234
+ row += 1
235
+ ws.cell(row=row, column=1).value = "Notes:"
236
+ ws.cell(row=row, column=1).font = bold_font
237
+ row += 1
238
+
239
+ # Add any specific disclosures from notes
240
+ note_20_data = notes_data.get("20")
241
+ if note_20_data and note_20_data.notes_and_disclosures:
242
+ ws.cell(row=row, column=1).value = note_20_data.notes_and_disclosures[0]
243
+ ws.cell(row=row, column=1).alignment = left_align
244
+ row += 1
245
+
246
+ # EPS information (if available in notes 27-28)
247
+ eps_data = notes_data.get("28") # Assuming EPS is in note 28
248
+ if eps_data:
249
+ ws.cell(row=row, column=1).value = "Earnings Per Share (EPS):"
250
+ ws.cell(row=row, column=1).font = bold_font
251
+ row += 1
252
+
253
+ basic_eps_2024 = extract_specific_value(eps_data, "basic", "2024")
254
+ basic_eps_2023 = extract_specific_value(eps_data, "basic", "2023")
255
+ diluted_eps_2024 = extract_specific_value(eps_data, "diluted", "2024")
256
+ diluted_eps_2023 = extract_specific_value(eps_data, "diluted", "2023")
257
+
258
+ ws.cell(row=row, column=1).value = " Basic EPS"
259
+ ws.cell(row=row, column=3).value = format_currency(basic_eps_2024)
260
+ ws.cell(row=row, column=4).value = format_currency(basic_eps_2023)
261
+ ws.cell(row=row, column=1).alignment = left_align
262
+ ws.cell(row=row, column=3).alignment = center_align
263
+ ws.cell(row=row, column=4).alignment = center_align
264
+ row += 1
265
+
266
+ ws.cell(row=row, column=1).value = " Diluted EPS"
267
+ ws.cell(row=row, column=3).value = format_currency(diluted_eps_2024)
268
+ ws.cell(row=row, column=4).value = format_currency(diluted_eps_2023)
269
+ ws.cell(row=row, column=1).alignment = left_align
270
+ ws.cell(row=row, column=3).alignment = center_align
271
+ ws.cell(row=row, column=4).alignment = center_align
272
+
273
+ # Save Excel file with error handling
274
+ output_folder = settings.output_folder
275
+ os.makedirs(output_folder, exist_ok=True)
276
+ output_file = os.path.join(output_folder, settings.output_file)
277
+ try:
278
+ wb.save(output_file)
279
+ logger.info(f"P&L report generated successfully and saved to {output_file}")
280
+ except PermissionError:
281
+ logger.error(f"PermissionError: Unable to save to {output_file}. Trying alternative location...")
282
+ fallback_file = os.path.join(os.path.expanduser("~"), "Desktop", "pnl_report_fallback.xlsx")
283
+ try:
284
+ wb.save(fallback_file)
285
+ logger.info(f"P&L report saved to alternative location: {fallback_file}")
286
+ except Exception as e:
287
+ logger.error(f"Failed to save P&L report: {str(e)}")
288
+ except Exception as e:
289
+ logger.error(f"Error saving P&L report: {str(e)}")
290
+
291
+ if __name__ == "__main__":
292
+ generate_pnl_report()
app/utils.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import Any, Union
3
+
4
+ # Configure logging
5
+ logging.basicConfig(level=logging.INFO)
6
+ logger = logging.getLogger(__name__)
7
+
8
+ def clean_value(value: Union[str, float, int, None]) -> float:
9
+ """
10
+ Clean and convert a value to float.
11
+ Removes commas from strings and strips whitespace.
12
+ Returns 0.0 if conversion fails.
13
+ """
14
+ try:
15
+ if isinstance(value, str):
16
+ value = value.replace(',', '').strip()
17
+ return float(value) if value else 0.0
18
+ except (ValueError, TypeError):
19
+ logger.debug(f"Could not clean value: {value}")
20
+ return 0.0
21
+
22
+ def to_lakhs(value: Union[float, int, str]) -> float:
23
+ """
24
+ Convert a numeric value to lakhs (divide by 100,000 and round to 2 decimals).
25
+ Accepts int, float, or numeric string.
26
+ """
27
+ try:
28
+ if isinstance(value, str):
29
+ value = float(value.replace(',', '').strip())
30
+ return round(float(value) / 100000, 2)
31
+ except (ValueError, TypeError):
32
+ logger.debug(f"Could not convert to lakhs: {value}")
33
+ return 0.0
34
+
35
+ def convert_note_json_to_lakhs(note_json: Any) -> Any:
36
+ """
37
+ Recursively convert all numeric values in a note JSON to lakhs.
38
+ Returns the converted object.
39
+ """
40
+ def convert(obj: Any) -> Any:
41
+ if isinstance(obj, dict):
42
+ for k, v in obj.items():
43
+ if isinstance(v, (int, float)):
44
+ obj[k] = to_lakhs(v)
45
+ elif isinstance(v, str):
46
+ try:
47
+ obj[k] = to_lakhs(float(v.replace(',', '')))
48
+ except Exception:
49
+ obj[k] = v
50
+ else:
51
+ obj[k] = convert(v)
52
+ elif isinstance(obj, list):
53
+ for i in range(len(obj)):
54
+ obj[i] = convert(obj[i])
55
+ return obj
56
+
57
+ return convert(note_json)
app/utils_normalize.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import Any, Dict, List, Optional
3
+ from pydantic import BaseModel, ValidationError
4
+
5
+ # Configure logging
6
+ logging.basicConfig(level=logging.INFO)
7
+ logger = logging.getLogger(__name__)
8
+
9
+ class NormalizedNote(BaseModel):
10
+ note_number: Optional[str]
11
+ note_title: Optional[str]
12
+ full_title: Optional[str]
13
+ table_data: List[Dict[str, Any]]
14
+ breakdown: Dict[str, Any] = {}
15
+ matched_accounts: List[Any] = []
16
+ total_amount: Optional[float] = None
17
+ total_amount_lakhs: Optional[float] = None
18
+ matched_accounts_count: Optional[int] = None
19
+ comparative_data: Dict[str, Any] = {}
20
+ notes_and_disclosures: List[str] = []
21
+ markdown_content: Optional[str] = ""
22
+
23
+ def is_date_label(label: str) -> bool:
24
+ """Check if a label is a date string."""
25
+ import re
26
+ return bool(re.match(r"^(March|April|May|June|July|August|September|October|November|December)\s+\d{1,2},\s+\d{4}$", label)) \
27
+ or bool(re.match(r"^\d{4}-\d{2}-\d{2}$", label))
28
+
29
+ def normalize_llm_note_json(llm_json: Dict[str, Any]) -> Dict[str, Any]:
30
+ """
31
+ Normalize a single LLM-generated note JSON to standard format.
32
+ Returns a dict compatible with NormalizedNote.
33
+ """
34
+ note_number = llm_json.get("note_number") or llm_json.get("metadata", {}).get("note_number", "")
35
+ note_title = llm_json.get("note_title") or llm_json.get("title", "")
36
+ full_title = llm_json.get("full_title") or (f"{note_number}. {note_title}" if note_number else note_title)
37
+
38
+ table_data: List[Dict[str, Any]] = []
39
+
40
+ if "structure" in llm_json and llm_json["structure"]:
41
+ for item in llm_json["structure"]:
42
+ if "subcategories" in item and item["subcategories"]:
43
+ for sub in item["subcategories"]:
44
+ label = sub.get("label", "")
45
+ if not is_date_label(label):
46
+ row = {
47
+ "particulars": label,
48
+ "current_year": sub.get("value", ""),
49
+ "previous_year": sub.get("previous_value", "-"),
50
+ }
51
+ table_data.append(row)
52
+ if "category" in item and ("total" in item or "previous_total" in item):
53
+ row = {
54
+ "particulars": f"Total {item.get('category', '')}",
55
+ "current_year": item.get("total", ""),
56
+ "previous_year": item.get("previous_total", "-"),
57
+ }
58
+ table_data.append(row)
59
+
60
+ # Optionally, add a header row
61
+ if table_data:
62
+ table_data.insert(0, {
63
+ "particulars": "Particulars",
64
+ "current_year": "March 31, 2024",
65
+ "previous_year": "March 31, 2023"
66
+ })
67
+
68
+ normalized = {
69
+ "note_number": note_number,
70
+ "note_title": note_title,
71
+ "full_title": full_title,
72
+ "table_data": table_data,
73
+ "breakdown": {},
74
+ "matched_accounts": [],
75
+ "total_amount": None,
76
+ "total_amount_lakhs": None,
77
+ "matched_accounts_count": None,
78
+ "comparative_data": {},
79
+ "notes_and_disclosures": [],
80
+ "markdown_content": llm_json.get("markdown_content", ""),
81
+ }
82
+ try:
83
+ # Validate with Pydantic model
84
+ NormalizedNote(**normalized)
85
+ except ValidationError as ve:
86
+ logger.warning(f"Validation error in normalized note: {ve}")
87
+ return normalized
88
+
89
+ def normalize_llm_notes_json(llm_json: Dict[str, Any]) -> Dict[str, Any]:
90
+ """
91
+ Accepts {"notes": [ ... ]} and returns {"notes": [ ...normalized... ]}
92
+ """
93
+ notes = llm_json.get("notes", [])
94
+ normalized_notes = [normalize_llm_note_json(note) for note in notes]
95
+ return {"notes": normalized_notes}
config/mapping1.json ADDED
@@ -0,0 +1,744 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "Capital Account": "Equity",
3
+ "Reserves & Surplus": "Equity",
4
+ "Share Capital": "Equity",
5
+ "Share Premium Account": "Equity",
6
+ "Loans (Liability)": "Non-Current Liability",
7
+ "DAIMLER FINANCIAL SERVICES INDIA PVT LTD": "Non-Current Liability",
8
+ "Loan From APSFC HYDERABAD": "Non-Current Liability",
9
+ "Loan From ICICI Bank 603090031420": "Non-Current Liability",
10
+ "LOAN FROM SBI SME HYDERABAD 40392204781": "Non-Current Liability",
11
+ "Current Liabilities": "Current Liability",
12
+ "Duties & Taxes": "Current Liability",
13
+ "Provisions": "Current Liability",
14
+ "Sundry Creditors": "Current Liability",
15
+ "Expenses Payable": "Current Liability",
16
+ "Commission Payable": "Current Liability",
17
+ "Interest Payable on APSFC LOAN": "Current Liability",
18
+ "Refundable Advance": "Current Liability",
19
+ "Fixed Assets": "Non-Current Asset",
20
+ "Equipments": "Non-Current Asset",
21
+ "Furniture & Fixtures": "Non-Current Asset",
22
+ "Accumulated Depreciation": "Accumulated Depreciation",
23
+ "Bar Code Scanner and Printer": "Non-Current Asset",
24
+ "MERCEDES-BENZ CAR": "Non-Current Asset",
25
+ "PURCHASE OF COMMERCIAL BULIDING": "Non-Current Asset",
26
+ "Current Assets": "Current Asset",
27
+ "Opening Stock": "Current Asset",
28
+ "Deposits (Asset)": "Current Asset",
29
+ "Loans & Advances (Asset)": "Current Asset",
30
+ "Sundry Debtors": "Current Asset",
31
+ "Cash-in-Hand": "Current Asset",
32
+ "Bank Accounts": "Current Asset",
33
+ "GST Input Tax Credit": "Current Asset",
34
+ "Advance to Perennail Code IT Consultants Pvt Ltd": "Current Asset",
35
+ "Prepaid Expenses": "Current Asset",
36
+ "TCS RECEIVABLES": "Current Asset",
37
+ "Tds & Advance Tax Fy -2021-2022": "Current Asset",
38
+ "TDS Advance Tax Paid SEC 100": "Current Asset",
39
+ "Tds Receivables": "Current Asset",
40
+ "Sales Accounts": "Revenue from Operations",
41
+ "Servicing of BA/BE PROJECTS EXPORT": "Revenue from Operations",
42
+ "Servicing of BA/BE PROJECTS-Inter State": "Revenue from Operations",
43
+ "Servicing of BA/BE PROJECTS-Intra State": "Revenue from Operations",
44
+ "SERVICING OF BA PROJECTS-Intra State": "Revenue from Operations",
45
+ "SERVICING OF ONLY CLINICAL INTRA STATE": "Revenue from Operations",
46
+ "Working Standards - Export": "Revenue from Operations",
47
+ "Working Standards-Inter State": "Revenue from Operations",
48
+ "Working Standards-Intra State": "Revenue from Operations",
49
+ "Purchase Accounts": "Cost of Materials Consumed",
50
+ "Bio Lab Consumables": "Cost of Materials Consumed",
51
+ "Non GST": "Cost of Materials Consumed",
52
+ "Purchase GST": "Cost of Materials Consumed",
53
+ "Direct Expenses": "Direct Expenses",
54
+ "Ambulance Service Charges": "Direct Expenses",
55
+ "Aprons-Cp Dept": "Direct Expenses",
56
+ "BA/BE NOC Charges": "Direct Expenses",
57
+ "BE Center-Consultancy Charges": "Direct Expenses",
58
+ "CLINICAL TRAIL INSURANCE": "Direct Expenses",
59
+ "Dietician Charges": "Direct Expenses",
60
+ "ECG PAYMENT": "Direct Expenses",
61
+ "Oncall Nurse/ Plebhos Charges": "Direct Expenses",
62
+ "Oncall Phisian Charges": "Direct Expenses",
63
+ "PK And Statastical Analysis Charges": "Direct Expenses",
64
+ "Study Expenses": "Direct Expenses",
65
+ "Volunteers Screening Payments": "Direct Expenses",
66
+ "Volunteers Study Payments": "Direct Expenses",
67
+ "Indirect Incomes": "Other Income",
68
+ "Interest on FD": "Other Income",
69
+ "Interest on Income Tax Refund": "Other Income",
70
+ "Indirect Expenses": "Other Expenses",
71
+ "DIVIDEND PAID TO SHARE HOLDERS": "Other Expenses",
72
+ "Employee Expenditure": "Employee Benefits Expense",
73
+ "Airtel DTH Charges": "Other Expenses",
74
+ "Annual Maintenance Charges": "Other Expenses",
75
+ "BA Expenses": "Other Expenses",
76
+ "Bank Charges": "Other Expenses",
77
+ "Bio Medical Waste Disposal Charges": "Other Expenses",
78
+ "Business Development Expenses": "Other Expenses",
79
+ "Calibration Charges": "Other Expenses",
80
+ "Car Insurance": "Other Expenses",
81
+ "CDM AND CDISC SERVICES": "Other Expenses",
82
+ "Civil Works": "Other Expenses",
83
+ "Cold Storage Charges-Freezer": "Other Expenses",
84
+ "Commission": "Other Expenses",
85
+ "COnsultancy Charges": "Other Expenses",
86
+ "Courier and Postage Charges": "Other Expenses",
87
+ "CSR Fund Allocation/expenses": "Other Expenses",
88
+ "Customs Duty Payment": "Other Expenses",
89
+ "Diesel Expences For Vehicle(Car)": "Other Expenses",
90
+ "ELECTRICAL EXPENSES": "Other Expenses",
91
+ "Electricity Charges": "Other Expenses",
92
+ "Emergency Medicines": "Other Expenses",
93
+ "Fabrication Work Expenses": "Other Expenses",
94
+ "Fire Extingushiers Refilling Charges": "Other Expenses",
95
+ "Food Expenses for Guests": "Other Expenses",
96
+ "Forex Gain / Loss": "Other Income",
97
+ "Generator -Diesel Expenses": "Other Expenses",
98
+ "Gratutity Paid to Staff": "Employee Benefits Expense",
99
+ "Group Health Insurence for Emp": "Employee Benefits Expense",
100
+ "GST Default Amount FY 2019-20": "Other Expenses",
101
+ "Hdfcergo General Insurance": "Other Expenses",
102
+ "HOUSE KEEPING CHARGES": "Other Expenses",
103
+ "House Keeping Materials": "Other Expenses",
104
+ "IEC Certificate Charges": "Other Expenses",
105
+ "Incentives to Staff": "Employee Benefits Expense",
106
+ "INSURANCE FOR ASSETS BUGLARY": "Other Expenses",
107
+ "INSURANCE FOR ASSETS FIRE POLICY": "Other Expenses",
108
+ "Interest of Car Loan": "Finance Cost",
109
+ "Interest on APSFC Loan": "Finance Cost",
110
+ "Interest on ICICI Loan-Commercial Building": "Finance Cost",
111
+ "Interest on Loan From SBI": "Finance Cost",
112
+ "Interest on TDS": "Finance Cost",
113
+ "Interest Paid": "Finance Cost",
114
+ "Interest Under 234 C Fy 2021-22": "Finance Cost",
115
+ "IT Expenses": "Other Expenses",
116
+ "Labour Licence": "Other Expenses",
117
+ "Lab Testing Charges": "Other Expenses",
118
+ "Loan Processing Charges": "Other Expenses",
119
+ "Maxwell PHARMA": "Other Expenses",
120
+ "Office Expenses": "Other Expenses",
121
+ "Pantry Expenses": "Other Expenses",
122
+ "Pest Control Charges": "Other Expenses",
123
+ "Plumbing Expenses": "Other Expenses",
124
+ "Plumbing Material": "Other Expenses",
125
+ "PRINTING AND STATIONERY": "Other Expenses",
126
+ "Processing Charges": "Other Expenses",
127
+ "Professional & Consultancy Fee": "Other Expenses",
128
+ "Professional Fee-Legal": "Other Expenses",
129
+ "Property Tax for Zenrise- Centre1": "Other Expenses",
130
+ "Protocol Review/IEC Charges": "Other Expenses",
131
+ "Rates and Taxes": "Other Expenses",
132
+ "Registration Fee": "Other Expenses",
133
+ "Remuniration to Directors": "Employee Benefits Expense",
134
+ "Rent of the Premises": "Other Expenses",
135
+ "Repairs and Maintenance": "Other Expenses",
136
+ "Retainership Fees": "Other Expenses",
137
+ "Round Off": "Other Expenses",
138
+ "Salary": "Employee Benefits Expense",
139
+ "SECURITY SERVICES": "Other Expenses",
140
+ "Service Charges": "Other Expenses",
141
+ "Sitting Fee of Directors": "Other Expenses",
142
+ "Software Equipment": "Non-Current Asset",
143
+ "SOFTWARE RENEWAL FEES": "Other Expenses",
144
+ "Staff Comp Offs and OTs": "Employee Benefits Expense",
145
+ "Staff Food Expenses": "Employee Benefits Expense",
146
+ "Stamp Duty": "Other Expenses",
147
+ "Stamp Papers": "Other Expenses",
148
+ "Storage Charges": "Other Expenses",
149
+ "Study Food Expences": "Other Expenses",
150
+ "Tax on Professional Charges": "Other Expenses",
151
+ "Telephone and Internet Charges": "Other Expenses",
152
+ "Trade License Expenses": "Other Expenses",
153
+ "Translation Charges": "Other Expenses",
154
+ "Transportation and Unloading Charges": "Other Expenses",
155
+ "Travelling Expences": "Other Expenses",
156
+ "Wages for Contract Employees": "Employee Benefits Expense",
157
+ "Water Cans Expenses": "Other Expenses",
158
+ "Water Charges (Hyderabad Metropolitan": "Other Expenses",
159
+ "Wooden Work Expenses": "Other Expenses",
160
+ "X-RAY CHARGES": "Other Expenses",
161
+ "Non-Current Liabilities": "Non-Current Liability",
162
+ "Deferred Tax Liability": "Deferred Tax Liability",
163
+ "Profit & Loss A/c": "Profit and Loss Account",
164
+ "Unadjusted Forex Gain/Loss": "Other Income",
165
+ "Advance Tax": "Current Asset",
166
+ "APC Schneider (UPS 3KVA/2.4KW)": "Fixed Asset",
167
+ "Apple Ipad": "Fixed Asset",
168
+ "Apple Iphone 12 Max Pro": "Fixed Asset",
169
+ "Apple Iphone 12 Max Pro - 2": "Fixed Asset",
170
+ "Apple Iphone XS": "Fixed Asset",
171
+ "Apple M2 Pro Laptop": "Fixed Asset",
172
+ "Apple M3 Pro Laptop": "Fixed Asset",
173
+ "Apple Macbook Pro - A1990": "Fixed Asset",
174
+ "Apple Watch": "Fixed Asset",
175
+ "Canon Printer cum Scanner - 1": "Fixed Asset",
176
+ "Canon Printer cum Scanner - 2": "Fixed Asset",
177
+ "CD Balance GHI & GPA - Aditya Birla Health Insurance Co Ltd": "Current Asset",
178
+ "CD Balance GTLI - Tata AIA Life Insurance Company Limited": "Current Asset",
179
+ "Citi Bank (528828019)": "Current Asset",
180
+ "Deferred Revenue Expenditure": "Other Asset",
181
+ "Deferred Tax Asset": "Other Asset",
182
+ "Dell LAPTOP_Batch-19": "Fixed Asset",
183
+ "Dell LAPTOP_Batch-20": "Fixed Asset",
184
+ "Dell LAPTOP_Batch-21": "Fixed Asset",
185
+ "EPSON Projector": "Fixed Asset",
186
+ "Fortigate Firewall (Server) - BLR": "Fixed Asset",
187
+ "Fortigate Firewall (Server) - HYD": "Fixed Asset",
188
+ "Godrej Safe": "Fixed Asset",
189
+ "Godrej Wardrobe": "Fixed Asset",
190
+ "HP Printer cum Scanner - 1": "Fixed Asset",
191
+ "Input CGST": "Input Tax Credit",
192
+ "Input IGST": "Input Tax Credit",
193
+ "Input SGST": "Input Tax Credit",
194
+ "IT Server": "Fixed Asset",
195
+ "Kotak Mahindra Bank (2012183177)": "Current Asset",
196
+ "Kotak Mahindra Bank (2013992013)": "Current Asset",
197
+ "Lenovo Desktop CPU": "Fixed Asset",
198
+ "Lenovo Laptop - E580": "Fixed Asset",
199
+ "Lenovo LAPTOP_Batch-1": "Fixed Asset",
200
+ "Lenovo LAPTOP_Batch-10": "Fixed Asset",
201
+ "Lenovo LAPTOP_Batch-11": "Fixed Asset",
202
+ "Lenovo LAPTOP_Batch-12": "Fixed Asset",
203
+ "Lenovo LAPTOP_Batch-13": "Fixed Asset",
204
+ "Lenovo LAPTOP_Batch-14": "Fixed Asset",
205
+ "Lenovo LAPTOP_Batch-15": "Fixed Asset",
206
+ "Lenovo LAPTOP_Batch-16": "Fixed Asset",
207
+ "Lenovo LAPTOP_Batch-17": "Fixed Asset",
208
+ "Lenovo LAPTOP_Batch-18": "Fixed Asset",
209
+ "Lenovo LAPTOP_Batch-2": "Fixed Asset",
210
+ "Lenovo LAPTOP_Batch-3": "Fixed Asset",
211
+ "Lenovo LAPTOP_Batch-4": "Fixed Asset",
212
+ "Lenovo LAPTOP_Batch-5": "Fixed Asset",
213
+ "Lenovo LAPTOP_Batch-6": "Fixed Asset",
214
+ "Lenovo LAPTOP_Batch-7": "Fixed Asset",
215
+ "Lenovo LAPTOP_Batch-8": "Fixed Asset",
216
+ "Lenovo LAPTOP_Batch-9": "Fixed Asset",
217
+ "LG Projector - Batch-1": "Fixed Asset",
218
+ "LG Projector - Batch-2": "Fixed Asset",
219
+ "Logitech Web Camera - 1": "Fixed Asset",
220
+ "Petty Cash": "Current Asset",
221
+ "Prepaid - Employees Group Life Insurance": "Current Asset",
222
+ "Prepaid Assets Insurance": "Current Asset",
223
+ "Prepaid Expenses - Laptop AMC": "Current Asset",
224
+ "Prepaid Expenses - Laptop Extended Warranty": "Current Asset",
225
+ "Prepaid Insurance - Employees Health & Personal Accident": "Current Asset",
226
+ "Samsung Mobile Galaxy S9 SM-G960F": "Fixed Asset",
227
+ "Samsung Refrigerator - 1": "Fixed Asset",
228
+ "Seagate Hard Disc 2TB": "Fixed Asset",
229
+ "Security Deposit - ESIC": "Other Asset",
230
+ "Security Deposit - Hive Space": "Other Asset",
231
+ "Security Deposit - Incuspaze Solutions Private Limited": "Other Asset",
232
+ "Security Deposits - Awfis Space Solutions Private Limited": "Other Asset",
233
+ "Security Deposits - Concept Classic Converge": "Other Asset",
234
+ "Accounts Payable": "Current Liability",
235
+ "Payroll liabilities": "Current Liability",
236
+ "Profession Tax Payable": "Current Liability",
237
+ "Provident Fund Payables": "Current Liability",
238
+ "Provision for Gratuity": "Long-Term Liability",
239
+ "Provision for Professional Fee (Transfer Pricing)": "Long-Term Liability",
240
+ "Provision for Statutory Audit Fee": "Long-Term Liability",
241
+ "Provision for Tax": "Long-Term Liability",
242
+ "TDS Payable": "Current Liability",
243
+ "Gulf FZE (Related Party)": "Current Liability",
244
+ "Retained Earnings": "Equity",
245
+ "Shares Issued to Fadhlurahman": "Equity",
246
+ "Shares Issued to Mohammed Anwar": "Equity",
247
+ "Shares Issued to Veripark Yazilim Anonim Sirketi": "Equity",
248
+ "Sales": "Revenue from Operations",
249
+ "Annual Maintenance Charges - Laptop": "Other Expenses",
250
+ "Annual Profession Tax": "Other Expenses",
251
+ "Asset Insurance": "Other Expenses",
252
+ "Bank Fees and Charges": "Other Expenses",
253
+ "Consultancy & Service Fee": "Other Expenses",
254
+ "Conveyance Allowances": "Other Expenses",
255
+ "Deferred Tax Expense": "Other Expenses",
256
+ "Depreciation And Amortisation": "Other Expenses",
257
+ "Dues & Subscriptions": "Other Expenses",
258
+ "Employees Expenses Reimbursement": "Employee Benefits Expense",
259
+ "Employees Group Life Insurance": "Employee Benefits Expense",
260
+ "Employees Health & Personal Accident Insurance": "Employee Benefits Expense",
261
+ "Employer Contribution to EPF": "Employee Benefits Expense",
262
+ "Gratuity Expense": "Employee Benefits Expense",
263
+ "Income Tax": "Other Expenses",
264
+ "Laptop Accessories and Maintenance": "Other Expenses",
265
+ "Laptop Extended Warranty": "Other Expenses",
266
+ "Loss/Gain on Exchange Rate": "Other Income",
267
+ "Loss/Gain on Foreign Exchange": "Other Income",
268
+ "Office Rent": "Other Expenses",
269
+ "Other Expenses": "Other Expenses",
270
+ "Payroll Expenses": "Employee Benefits Expense",
271
+ "Per Diem Expenses": "Employee Benefits Expense",
272
+ "PF Administration & EDLI Charges": "Other Expenses",
273
+ "Postage & Courier Charges": "Other Expenses",
274
+ "Printing and Stationery": "Other Expenses",
275
+ "Professional Fee": "Other Expenses",
276
+ "Professional Fee (Transfer Pricing)": "Other Expenses",
277
+ "Registrations & Renewals": "Other Expenses",
278
+ "Retainership Fee": "Other Expenses",
279
+ "ROC Filing Fee": "Other Expenses",
280
+ "Round off": "Other Expenses",
281
+ "Staff Welfare Expenses": "Employee Benefits Expense",
282
+ "Statutory Audit Fee": "Other Expenses",
283
+ "Telephone Expense": "Other Expenses",
284
+ "Visa Expenses": "Other Expenses",
285
+ "GST Written off": "Other Expenses",
286
+ "Net Profit": "Profit and Loss Account",
287
+ "0% GST Output Tax": "Liability",
288
+ "12% IGST INPUT TAX": "Input Tax Credit",
289
+ "14% CGST Input Tax Credit": "Input Tax Credit",
290
+ "14% SGST Input Tax Credit": "Input Tax Credit",
291
+ "18% IGST INPUT TAX CREDIT": "Input Tax Credit",
292
+ "18% IGST Out Put Tax": "Liability",
293
+ "2.5% CGST INPUT TAX CREDIT": "Input Tax Credit",
294
+ "2.5% SGST INPUT TAX CREDIT": "Input Tax Credit",
295
+ "5% IGST INPUT CREDIT": "Input Tax Credit",
296
+ "6% CGST Input Tax Credit": "Input Tax Credit",
297
+ "6% SGST Input Tax Credit": "Input Tax Credit",
298
+ "9% CGST Input Tax Credit": "Input Tax Credit",
299
+ "9% CGST OUTPUT TAX": "Liability",
300
+ "9% SGST Input Tax Credit": "Input Tax Credit",
301
+ "9% SGST OUTPUT TAX": "Liability",
302
+ "AB SCIEX API 3200": "Fixed Asset",
303
+ "ACCESS CONTROL EQUIPMENT - IT DEPT": "Fixed Asset",
304
+ "ACP PANEL WORK": "Fixed Asset",
305
+ "Acs Installation Charges": "Expense",
306
+ "Adavnce to Ebiztechnix.Com Pvt Ltd": "Current Asset",
307
+ "Adavnce to Sucopeia Reference Standards OPC Pvt Ltd": "Current Asset",
308
+ "Adrta Technologies Pvt Lttd": "Current Asset",
309
+ "Advance for Rent Dr Rohit Prakash Kolipaka": "Current Asset",
310
+ "Advance for Rent M Kaushik Reddy": "Current Asset",
311
+ "Advance for Rent M Mayur Reddy": "Current Asset",
312
+ "Advance for Rent P Murali Krishna Chaitanya": "Current Asset",
313
+ "Advance for Rent SSK Tulasi Rao": "Current Asset",
314
+ "Advance for Rent Vimala Sravanthi Vajjala": "Current Asset",
315
+ "Advance for Rent V Vijaya Krishna Prasad": "Current Asset",
316
+ "Advance Given to B Nagarjuna VMO": "Current Asset",
317
+ "Advance Given to B Pavan Kumar": "Current Asset",
318
+ "Advance Given to Dr K Krishna Moorthy": "Current Asset",
319
+ "Advance Given to Dr Ragula Naveen": "Current Asset",
320
+ "Advance to Avantec Laboroatories": "Current Asset",
321
+ "ADVANCE TO I CLEAN TECHNOLOGIES": "Current Asset",
322
+ "Advance to K Hari Prasad": "Current Asset",
323
+ "Advance to K Sampath": "Current Asset",
324
+ "Advance to M Bhaskar": "Current Asset",
325
+ "Advance to M Prabhukumar": "Current Asset",
326
+ "Advance to NEPPALLI NARENDRA": "Current Asset",
327
+ "Advance to Perennail Code IT Conslultants Pvt Ltd": "Current Asset",
328
+ "Advance to Sudha Analytics": "Current Asset",
329
+ "Advance to Vemula Ravi Kumar": "Current Asset",
330
+ "Agilent Technologies": "Fixed Asset",
331
+ "A G Ravindranath Reddy": "Current Asset",
332
+ "AGR Corporate Consultants LLP": "Expense",
333
+ "AIR CONDITIONERS - Admin Dept": "Fixed Asset",
334
+ "AKIRA ANALYTICAL SOLUTIONS P LTD": "Current Asset",
335
+ "Akshaya Labtech": "Current Asset",
336
+ "Akshaya Scientific Pvt Ltd": "Current Asset",
337
+ "Akula Indira Prasanna": "Current Asset",
338
+ "Amazon": "Expense",
339
+ "AMBICA & COMPANY": "Current Asset",
340
+ "Amigo Techno Park Private Limited": "Current Asset",
341
+ "AMIS ENGINEERS": "Current Asset",
342
+ "Ample Enterprises": "Current Asset",
343
+ "AnalChem SPE": "Current Asset",
344
+ "Annual Fee for Demat RTA Services": "Expense",
345
+ "APL Research Centre": "Current Asset",
346
+ "Appco Pharma LLC, USA": "Current Asset",
347
+ "APSTEC SYSTEMS": "Current Asset",
348
+ "AR Interiors & Contrator": "Fixed Asset",
349
+ "ARJUN WATCH COMPANY": "Current Asset",
350
+ "Arnav Health Care": "Current Asset",
351
+ "Art Lab India Private Ltd.": "Current Asset",
352
+ "ASIAN SURGICAL COMPANY": "Current Asset",
353
+ "ATHENA TECHNOLOGIES": "Current Asset",
354
+ "ATRIA CONVERGENCE TECHNOLOGIES PVT LTD": "Expense",
355
+ "Audit Fee": "Expense",
356
+ "Audit Fee Payable": "Liability",
357
+ "AURO PHARMA INC": "Current Asset",
358
+ "AVS NARAYANA": "Current Asset",
359
+ "Axis Bank KUKATPALLY A/C": "Current Asset",
360
+ "AXIS BANK ,MIYAPUR A/C": "Current Asset",
361
+ "Axis Clinicals Limited": "Current Asset",
362
+ "AYSIT Solutions Private Limited": "Current Asset",
363
+ "Ayyappa Amubulance Service": "Expense",
364
+ "AYYAPPA MANAGEMENT SERVICES": "Expense",
365
+ "Azurity Pharmaceuticals,Inc": "Current Asset",
366
+ "Azurity Pharmaceuticals India LLP": "Current Asset",
367
+ "BADAL KUMAR MAHOPATRA": "Current Asset",
368
+ "Bajaj Allianz General Insurance": "Expense",
369
+ "B Balachari Carpenter": "Expense",
370
+ "Beetal Phone": "Fixed Asset",
371
+ "BEQSSE SERVICES -BRAZIL": "Current Asset",
372
+ "Beximco Pharmaceuticals Ltd": "Current Asset",
373
+ "BHARATHI AIRTEL LTD": "Expense",
374
+ "Bharat Science Apparatus Workshops": "Current Asset",
375
+ "BHAVANI ENTERPRISES": "Current Asset",
376
+ "Bhumeswar": "Current Asset",
377
+ "Bioagile Therapeutics Pvt Ltd": "Current Asset",
378
+ "Bio Organics & Applied Materials Pvt Ltd": "Current Asset",
379
+ "Bison Panels 75MM Thickness": "Fixed Asset",
380
+ "BLOOMEDHA INFO SOLUTIONS LTD": "Current Asset",
381
+ "BPL Multipara Monitors": "Fixed Asset",
382
+ "Brundavan Medical Needs": "Current Asset",
383
+ "BSreeLatha Rent Depost": "Current Asset",
384
+ "Business Combine Corporation": "Current Asset",
385
+ "BVVSN PRASAD": "Current Asset",
386
+ "C. Abhay kumar & Co.": "Expense",
387
+ "Calling Buttons - CP Dept": "Fixed Asset",
388
+ "Caltronix Healthcare Solutions": "Current Asset",
389
+ "CAPITAL INDIA FINANCE LTD": "Liability",
390
+ "Capital WIP - Centre 1": "Fixed Asset",
391
+ "Carpet Home Decor": "Fixed Asset",
392
+ "Cash": "Current Asset",
393
+ "Cash in Foreign Currency USD": "Current Asset",
394
+ "CCTV CAMERAS -IT DEPT": "Fixed Asset",
395
+ "Chemtopes": "Current Asset",
396
+ "CHOICE MARKETING": "Current Asset",
397
+ "Choudhary Enterprises": "Current Asset",
398
+ "Chromatography Solutions": "Current Asset",
399
+ "Chromatography World": "Current Asset",
400
+ "CHROME SOURCE": "Current Asset",
401
+ "Chrome source Pvt Ltd.": "Current Asset",
402
+ "City Glass Centre": "Current Asset",
403
+ "Civil Works-Siri Building": "Fixed Asset",
404
+ "CLEARSYNTH LABS LTD": "Current Asset",
405
+ "CLEARSYNTH LABS LTD (Hyd)": "Current Asset",
406
+ "Clinical Equipments - Cp Dept": "Fixed Asset",
407
+ "Clinitext Translation Services": "Expense",
408
+ "Clinvend Clinical Research Solutions": "Current Asset",
409
+ "Communities Heritage Pvt LTd.": "Current Asset",
410
+ "Consultancy Charges": "Expense",
411
+ "CONTRACT PHARMACAL CORPORATION USA": "Current Asset",
412
+ "Cooling Chambers - BA Dept": "Fixed Asset",
413
+ "Cooling Chambers - CP Dept": "Fixed Asset",
414
+ "Core Pharma LLC": "Current Asset",
415
+ "CROMA": "Current Asset",
416
+ "CRO Splendid Lab Pvt Ltd": "Current Asset",
417
+ "Customs Duty Payable Account": "Liability",
418
+ "Davy Chem Labs": "Current Asset",
419
+ "Deposit to G J MULTICLAVE INDIA PVT LTD": "Current Asset",
420
+ "Deposit to SLV SPECIALITY GASES PVT LTD": "Current Asset",
421
+ "DESKTOPS AND LAPTOPS": "Fixed Asset",
422
+ "DHL EXPRESS P LTD": "Expense",
423
+ "DHR Holding India Pvt Ltd": "Current Asset",
424
+ "Dr A T Bapuji": "Current Asset",
425
+ "Dr Bodapati Neeraja": "Current Asset",
426
+ "Dr K Krishna Moorthy": "Current Asset",
427
+ "Dr Kosuri Naga Murali": "Current Asset",
428
+ "Dr M Navya": "Current Asset",
429
+ "Dr Reddy's Laboartories Limited": "Current Asset",
430
+ "Dr. Venkat Sai Raj Kumar": "Current Asset",
431
+ "Durga Fabricators": "Current Asset",
432
+ "DVR AND HDD - IT DEPT": "Fixed Asset",
433
+ "ECG MACHINES": "Fixed Asset",
434
+ "Electrical Cables-Admin Dept": "Fixed Asset",
435
+ "Electrical Deposit": "Current Asset",
436
+ "Electrical Gadgets- CP Dept": "Fixed Asset",
437
+ "Electrical Transformer and Pannels": "Fixed Asset",
438
+ "E Mudhra Limited": "Current Asset",
439
+ "ENDPOINT DATA ANALYTICS PVT LTD": "Current Asset",
440
+ "EPF Contribution A/c": "Liability",
441
+ "Epic Pharma LLC": "Current Asset",
442
+ "Ertiga Car Insurance": "Expense",
443
+ "Esic Contribution": "Liability",
444
+ "Ethixinn Consulting & Research Solutions PVT LTD": "Current Asset",
445
+ "Excel Infotech": "Current Asset",
446
+ "Fabric Blinds": "Fixed Asset",
447
+ "Fedex Express Transportation and Supply Chain Servi": "Expense",
448
+ "Fixed Deposit 05880510013861": "Current Asset",
449
+ "Fixed Deposit 058810014070": "Current Asset",
450
+ "Fixed Deposit 058810014350": "Current Asset",
451
+ "Fixed Deposit 058810014816": "Current Asset",
452
+ "Fixed Deposit 058810014817": "Current Asset",
453
+ "Fixed Deposit 058810014895": "Current Asset",
454
+ "Fixed Deposit 058810015195": "Current Asset",
455
+ "Fixed Deposit 058810015702": "Current Asset",
456
+ "Fixed Deposit 058810015988": "Current Asset",
457
+ "Fixed Deposit 058813017503": "Current Asset",
458
+ "Fixed Deposit 058813022834": "Current Asset",
459
+ "Focus Exhibition and Leisure Tours Pvt Ltd": "Expense",
460
+ "Forex Card 5554491100927730": "Current Asset",
461
+ "Fortune Labels 'n' Systems": "Current Asset",
462
+ "FUJIFILM INDIA PVT LTD": "Current Asset",
463
+ "Furniture - Admin Dept": "Fixed Asset",
464
+ "Furniture - CP Dept": "Fixed Asset",
465
+ "Future General India Insurance Co. Ltd": "Expense",
466
+ "Garnier Office Systems": "Current Asset",
467
+ "GB AIR SOLUTIONS": "Current Asset",
468
+ "GC Health care": "Current Asset",
469
+ "GENERATOR": "Fixed Asset",
470
+ "G J Multiclave (India) Pvt Ltd": "Current Asset",
471
+ "GK ANALYTICS": "Current Asset",
472
+ "Global Technologies": "Current Asset",
473
+ "GLOBE FURNITURES & LIGHTINGS": "Fixed Asset",
474
+ "GPS CLOCKS": "Fixed Asset",
475
+ "G Pulla Reddy (Sweets)": "Expense",
476
+ "GRANULES INDIA LIMITED": "Current Asset",
477
+ "GRAVITI PHARMACEUTICALS": "Current Asset",
478
+ "GRID CHANNEL FALCEILING": "Fixed Asset",
479
+ "GST Payable": "Liability",
480
+ "GST Payable on RCM": "Liability",
481
+ "GST RCM INPUT": "Input Tax Credit",
482
+ "Guardian Drug Company": "Current Asset",
483
+ "Gulf Pharmaceutical Industries": "Current Asset",
484
+ "HAEMO SERVICE LABORATORIES": "Current Asset",
485
+ "Harish Kumar Chinni": "Current Asset",
486
+ "Health Care Needs": "Expense",
487
+ "Help Hospitals Pvt Ltd": "Current Asset",
488
+ "HIMALA WATER SUPPLY": "Expense",
489
+ "HINDUSTAN FIRE SAFETY PROTECTION": "Expense",
490
+ "HRMS Software": "Fixed Asset",
491
+ "Humidity Chamber - CP Dept": "Fixed Asset",
492
+ "Hyderabad Metro Water Service Board": "Expense",
493
+ "I3 Pharamaceuticals LLC USA": "Current Asset",
494
+ "ICICI BANK CHANDANAGAR 058805003515": "Current Asset",
495
+ "Icici Bank-Chandanagar-058805004234": "Current Asset",
496
+ "ICICI BANK -Dividend Account 058805007279": "Current Asset",
497
+ "ICICI LOMBARD GIC LTD": "Expense",
498
+ "Icon Irani Chai": "Expense",
499
+ "ICPMS Machine 7850 along with Spares": "Fixed Asset",
500
+ "ICU Equipments - CP Dept": "Fixed Asset",
501
+ "Implement Technologies": "Current Asset",
502
+ "Income Tax FY 23-24": "Liability",
503
+ "Income Tax Refund": "Current Asset",
504
+ "Inductive Quotient Analytics India Pvt Ltd": "Current Asset",
505
+ "Ineligible GST": "Expense",
506
+ "INEXUS BIOTECH PVT LTD": "Current Asset",
507
+ "Innosyn Life Sciences Pvt. Ltd.": "Current Asset",
508
+ "INSADEC SERVICES PRIVATE LIMITED": "Current Asset",
509
+ "INSTANT PRINT SOLUTIONS INDIA P LTD": "Expense",
510
+ "Insurance of Properties": "Expense",
511
+ "Interest on FD Receivable": "Current Asset",
512
+ "InterGlobe Aviation Ltd": "Expense",
513
+ "IP PBX": "Fixed Asset",
514
+ "Jairam Bio Sciences": "Current Asset",
515
+ "JAYA SIRI ENTERPRISES": "Current Asset",
516
+ "Jet Speed Engineers": "Current Asset",
517
+ "J.K. Enterprises": "Current Asset",
518
+ "K.G.N Medi Care": "Current Asset",
519
+ "Kireeti Indenting & Exim Services Pvt Ltd": "Current Asset",
520
+ "KOTHARI FIRE SAFETY EQUIPMENTS": "Current Asset",
521
+ "K Prabhakar": "Current Asset",
522
+ "K Rama Satyanarayana": "Current Asset",
523
+ "Krishnaveni Printers": "Expense",
524
+ "LAB EQUIPMENT-BA Dept": "Fixed Asset",
525
+ "Lab Equipment-Cp Dept": "Fixed Asset",
526
+ "Lab Expo": "Expense",
527
+ "Lab Modulure Furniture BA Dept": "Fixed Asset",
528
+ "LabNeeds": "Current Asset",
529
+ "Lab Needs Private Limited": "Current Asset",
530
+ "Labosys Instruments India Pvt Ltd": "Current Asset",
531
+ "Labtop Instruments Pvt Ltd": "Current Asset",
532
+ "LAMBROS Analytics Pvt Ltd": "Current Asset",
533
+ "Lan Cable Network - IT Dept": "Fixed Asset",
534
+ "LCGC Chrom Consumables LLP": "Current Asset",
535
+ "LCMS SYSTEM": "Fixed Asset",
536
+ "LEGAL ENTITY IDENTIFIER INDIA LTD": "Expense",
537
+ "Likhitha Diagnostics and Speciality Lab": "Current Asset",
538
+ "LKG Industrial Solutions": "Current Asset",
539
+ "LOGANATHAN SEKHAR": "Current Asset",
540
+ "LYRUS LIFE SCIENCES PVT LTD": "Current Asset",
541
+ "Maddi Bhanu Kiran Reddy": "Current Asset",
542
+ "MAHADEV ENTERPRISES": "Current Asset",
543
+ "Mankind Pharma Limited": "Current Asset",
544
+ "MARG INDEPENDENT ETHICS COMMITTEE": "Expense",
545
+ "Maruthi Air Conditioners": "Fixed Asset",
546
+ "Mataji Plywood Glass & Hardware": "Current Asset",
547
+ "MCA Filing Fees": "Expense",
548
+ "MDS BIO ANALYTICS PVT LTD": "Current Asset",
549
+ "Mediclin Clinical Services Private Limited": "Current Asset",
550
+ "MEDILINK ENTERPRISES P LTD": "Current Asset",
551
+ "MeReDoC Pharma Cosultancy": "Expense",
552
+ "METRO CASH AND CARRY": "Expense",
553
+ "Mint Pharmaceutical INC": "Current Asset",
554
+ "Modular Lab Furniture (Art Lab)": "Fixed Asset",
555
+ "Mokshy Surgicals": "Current Asset",
556
+ "Molecules Analytical Lab Solutions Pvt Ltd": "Current Asset",
557
+ "MR SCIENTIFICS": "Current Asset",
558
+ "Msn Laboratories Pvt Ltd": "Current Asset",
559
+ "MSP LAB INSTRUMENTS": "Current Asset",
560
+ "Mylan Laboratories Limited": "Current Asset",
561
+ "NATCO PHARMA LIMITED": "Current Asset",
562
+ "NEO TECHNIQUES": "Current Asset",
563
+ "NETPROPHETS CYBERWORKS PVT. LTD": "Current Asset",
564
+ "Nihon Scientifics": "Current Asset",
565
+ "Ninth Dimension IT Solutions Private Limited": "Current Asset",
566
+ "Novel Laboratories Inc D/B/A Lupin": "Current Asset",
567
+ "NSDL FEE DMAT ACCOUNT": "Expense",
568
+ "Office Equipments": "Fixed Asset",
569
+ "OPERATING SOFWARE KEYS": "Fixed Asset",
570
+ "Opulent Furniture": "Fixed Asset",
571
+ "ORBIT EXHIBITIONS PVT LTD": "Expense",
572
+ "ORIGIN Medicare Systems": "Current Asset",
573
+ "Others Payble Account": "Liability",
574
+ "Pavani Consultancy": "Expense",
575
+ "Pavan Traders": "Current Asset",
576
+ "Payable to Srinivas": "Liability",
577
+ "PCI Analyticals Pvt Ltd": "Current Asset",
578
+ "Peak Scientific Instruments (India) Pvt Ltd": "Current Asset",
579
+ "Pest Control Technics": "Expense",
580
+ "Phenomenex India Pvt Ltd": "Current Asset",
581
+ "Phones - IT Dept": "Fixed Asset",
582
+ "Pinnacle Life Science Pvt Ltd": "Current Asset",
583
+ "POWER LINKS Infra Tech Pvt Ltd": "Current Asset",
584
+ "Prasoft IT Services Private Limited": "Current Asset",
585
+ "Pravesha Industries Private Limited, Unit - I": "Current Asset",
586
+ "Pravesha Industries Private Limited, Unit-IV": "Current Asset",
587
+ "Pre Closure of Loan": "Expense",
588
+ "Premier Systems": "Current Asset",
589
+ "Pre-Operative Expenses Capitalised As FF": "Fixed Asset",
590
+ "Pre-Operative Expenses Capitalised As P&M": "Fixed Asset",
591
+ "PRINTERS - IT Dept": "Fixed Asset",
592
+ "Priority Advertising": "Expense",
593
+ "PRODIGY COMPUTERS & LAPTOPS P LTD": "Current Asset",
594
+ "Prof Tax": "Expense",
595
+ "Projector and Screen - IT Dept": "Fixed Asset",
596
+ "Provision for Income Tax": "Liability",
597
+ "Prroxy Technology": "Current Asset",
598
+ "PSR IT SERVICES PVT LTD": "Current Asset",
599
+ "Quality Calibration Laboratory": "Current Asset",
600
+ "Radiant Bio System": "Current Asset",
601
+ "RAFF AND HALL PHARMACY": "Current Asset",
602
+ "Rainbow Engineering & Manufacturing": "Current Asset",
603
+ "Rajesh Technical Services": "Current Asset",
604
+ "Ramanvita Solutions": "Current Asset",
605
+ "RAMSON GENSYSTEM SERVICES": "Current Asset",
606
+ "RAPSTECH SYSTEMS INDIA PRIVATE LIMITED": "Current Asset",
607
+ "Raptor Safety": "Current Asset",
608
+ "RATHAN RERIGERATION": "Current Asset",
609
+ "Ray Analyticals Instruments": "Current Asset",
610
+ "Rental Advance - Siri Raidurgam": "Current Asset",
611
+ "RICON PHARMA LLC": "Current Asset",
612
+ "Riki Global Trading Pvt Ltd": "Current Asset",
613
+ "Rising Pharmaceuticals": "Current Asset",
614
+ "R Sumana": "Current Asset",
615
+ "RYB Technologies": "Current Asset",
616
+ "Sai Baba Business Solutions Pvt Ltd": "Current Asset",
617
+ "Salaries Payable": "Liability",
618
+ "Sales GST": "Liability",
619
+ "SAMPLE BARCODE MAGEMENT SYSTEM": "Fixed Asset",
620
+ "SAMSON MS": "Current Asset",
621
+ "Sana Fabrication": "Current Asset",
622
+ "SAPTAGIRI TRADERS": "Current Asset",
623
+ "Satya Devaya Clinic Lab": "Current Asset",
624
+ "Savant Instruments Pvt Ltd": "Current Asset",
625
+ "Sciex India Private Ltd": "Current Asset",
626
+ "Scindler India Pvt Ltd": "Current Asset",
627
+ "Security Deposit to DFS Financials": "Current Asset",
628
+ "SG SANITATION": "Expense",
629
+ "SHANTHI ENGINEERING": "Current Asset",
630
+ "Share Capital Account of KOTA PARVATHINI": "Equity",
631
+ "Share Capital Indira Prasanna": "Equity",
632
+ "Share Capital of Apteka RX Inc": "Equity",
633
+ "Share Capital of Dr A T Bapuji Director": "Equity",
634
+ "Share Capital of K Anil Venkata Reddy": "Equity",
635
+ "Share Capital Of R Srinivas -Director": "Equity",
636
+ "Share Capital of S Rajesh Kumar Director": "Equity",
637
+ "Shares of Mrs Vishnu Bhotla Sunitha": "Equity",
638
+ "SHED CONSTRUCTION": "Fixed Asset",
639
+ "Shiva Project Binding": "Expense",
640
+ "Shree Mathaji Electricals": "Current Asset",
641
+ "SHRI BALAZEE CATERING SERVICES": "Expense",
642
+ "SIDDHARTHA MEDICAL COLLEGE": "Current Asset",
643
+ "SIGMA PHARMA USA": "Current Asset",
644
+ "Simco Calibration & Testing Pvt Ltd": "Current Asset",
645
+ "SIMSON PHARMA LIMITED": "Current Asset",
646
+ "S Kalyan": "Current Asset",
647
+ "SKANRAY TECHNOLOGIES": "Current Asset",
648
+ "S.K.C Agencies": "Current Asset",
649
+ "SKG Pharma Inc": "Current Asset",
650
+ "SLAYBACK PHARMA INDIA LLP": "Current Asset",
651
+ "SLV Speciality Gases Private Limited": "Current Asset",
652
+ "Smart Labtech Pvt Ltd": "Current Asset",
653
+ "Smoke Detectors and Fire Alaram Expenses": "Expense",
654
+ "SMR Sales And Services": "Current Asset",
655
+ "Snowman Logistics Limited": "Expense",
656
+ "Sohan Healthcare Pvt Ltd": "Current Asset",
657
+ "SPEAKERS - IT DEPT": "Fixed Asset",
658
+ "SPINCOTECH PVT LTD": "Current Asset",
659
+ "SREE V LINK TELECOM INTEGRATOR": "Current Asset",
660
+ "SRESHTH PRINT HUB": "Expense",
661
+ "Sri Karyasiddhi Veeranjaneya Services": "Expense",
662
+ "Sri Lakshmi Moon and Bright Laundry Services": "Expense",
663
+ "Sri Laxmi Dry Ice & Gases": "Expense",
664
+ "Sri Nandini Air Conditioning Works": "Current Asset",
665
+ "Sri Sai Home Needs": "Expense",
666
+ "Sri Sai Power Solutions": "Current Asset",
667
+ "Sri Sai Ram Enterprises": "Current Asset",
668
+ "Sri Vasavi Enginerring": "Current Asset",
669
+ "Sri Venkateshwara Prefab": "Current Asset",
670
+ "S S ANALYTICALS": "Current Asset",
671
+ "S Siva Parvathi": "Current Asset",
672
+ "S S RACKS": "Fixed Asset",
673
+ "SSR Facility Management Services": "Expense",
674
+ "STP TANK": "Fixed Asset",
675
+ "Subtle Pharmaceuticals Private Limited": "Current Asset",
676
+ "SV Aircon": "Current Asset",
677
+ "SVVSS COMPUTERS": "Current Asset",
678
+ "System Software Server": "Fixed Asset",
679
+ "Tables & Chairs - Admin Dept": "Fixed Asset",
680
+ "T CHANDRA SEKHARA SARMA": "Current Asset",
681
+ "TCS Payble 206C 6CE": "Liability",
682
+ "TDS ON INTERESTOTHER THAN SECURITIES SEC 194A": "Liability",
683
+ "Tds on Professional Services Sec194 , 94J": "Liability",
684
+ "TDS ON RENT SEC 194, 94I": "Liability",
685
+ "TDS ON SALARIES SEC 192 92B": "Liability",
686
+ "TDS PAYMENT CONTRACT 194 C": "Liability",
687
+ "TDS SEC 195": "Liability",
688
+ "TecPharma": "Current Asset",
689
+ "Tempocon Express Pvt Ltd": "Expense",
690
+ "T GARGEYA": "Current Asset",
691
+ "The Professional Couriers LTD": "Expense",
692
+ "Thermo Fisher Certrifuge Apparatus - BA Dept": "Fixed Asset",
693
+ "THERMO FISHER SCIENTIFIC INDIA PVT LTD": "Current Asset",
694
+ "T.Leela Rent": "Expense",
695
+ "T Leela Rent Deposit": "Current Asset",
696
+ "T Nageswara Rao Carpentor": "Expense",
697
+ "TORRENT PHARMACEUTICALS LTD": "Current Asset",
698
+ "TOUGHENED GLASS DOORS": "Fixed Asset",
699
+ "TOUGHENED GLASS DOORS - Admin Dept": "Fixed Asset",
700
+ "Trolleys": "Fixed Asset",
701
+ "T Sridhar (Poojari)": "Expense",
702
+ "TSSPDCL": "Expense",
703
+ "TVS AND FRIDGES": "Fixed Asset",
704
+ "UNI CAL LABS PVT LTD": "Current Asset",
705
+ "Universal Instruments": "Current Asset",
706
+ "UPS SYSTEM": "Fixed Asset",
707
+ "U Srinivasulu (Dietician)": "Expense",
708
+ "Vasavi Hospital": "Current Asset",
709
+ "Vasudha Enviro Labs Pvt Ltd": "Current Asset",
710
+ "V Clean Technology": "Current Asset",
711
+ "Veldanda Traders": "Current Asset",
712
+ "VelZen Pharma Pvt Ltd": "Current Asset",
713
+ "V-ENSURE PHARMA TECHNOLOGIES PVT LTD": "Current Asset",
714
+ "Venture Capital and Corporate Investments PVT LTD": "Current Asset",
715
+ "Vidith Powers": "Current Asset",
716
+ "Vignesh Scientifics Technologies": "Current Asset",
717
+ "Vijaya Textiles": "Current Asset",
718
+ "Vinayaka Foods": "Expense",
719
+ "Vista Labs": "Current Asset",
720
+ "Vivan Life Sciences Pvt Ltd": "Current Asset",
721
+ "VM Sciences": "Current Asset",
722
+ "Volunteer BEDS - CP Dept": "Fixed Asset",
723
+ "VRK BUSINESS SOLUTIONS": "Current Asset",
724
+ "V Satyanarayana Rao": "Current Asset",
725
+ "VSL Electronics": "Current Asset",
726
+ "V Swapna Chai": "Expense",
727
+ "WALL PANEL SYSTEM-Admin Dept": "Fixed Asset",
728
+ "Wash O Wash": "Expense",
729
+ "Water Charges (Hyderabad Metropolitan)": "Expense",
730
+ "Weighing Machines-BA Dept": "Fixed Asset",
731
+ "Weighing Machines - CP Dept": "Fixed Asset",
732
+ "Weighing & Measuring Instrument Corporation": "Current Asset",
733
+ "Wishmen Life Sciences Pvt Ltd": "Current Asset",
734
+ "Wooden Partitions and Cupboards": "Fixed Asset",
735
+ "Writers Business Services Pvt Ltd": "Current Asset",
736
+ "Written Off": "Expense",
737
+ "WWF CSR Fund": "Expense",
738
+ "YOOSHITHA AUTO ENTERPRISES": "Current Asset",
739
+ "Yoshitha Traders": "Current Asset",
740
+ "Zef Scientific India Pvt Ltd": "Current Asset",
741
+ "ZENARA PHARMA PVT LTD": "Current Asset",
742
+ "ZODIAC LIFE SCIENCES PVT LTD": "Current Asset",
743
+ "Difference in opening balances": "Equity"
744
+ }
config/rules1.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "Equity": ["capital", "share", "reserve", "surplus", "premium"],
3
+ "Non-Current Liability": ["loan", "liability", "debt", "borrowing"],
4
+ "Current Liability": ["payable", "creditor", "tax", "duty", "refundable", "advance.*received"],
5
+ "Non-Current Asset": ["asset", "equipment", "furniture", "vehicle", "building", "depreciation"],
6
+ "Current Asset": ["advance.*to", "advance.*given", "deposit", "prepaid","cash", "bank", "debtor", "receivable", "stock", "deposit", "prepaid", "input", "credit"],
7
+ "Revenue from Operations": ["sales", "service", "revenue", "turnover", "income"],
8
+ "Cost of Materials Consumed": ["purchase", "consumable", "material", "stock"],
9
+ "Direct Expenses": ["charge", "payment", "expense", "study", "volunteer", "insurance", "consultancy", "nurse", "physician", "analysis", "screening"],
10
+ "Other Income": ["interest", "gain", "refund"],
11
+ "Other Expenses": ["maintenance", "repair", "rent", "electricity", "water", "housekeeping", "travel", "transport", "printing", "stationery", "software", "fee", "license", "tax", "duty"],
12
+ "Employee Benefits Expense": ["salary", "wage", "staff", "employee", "gratuity", "incentive", "remuneration", "director", "compensation"],
13
+ "Finance Cost": ["interest", "finance", "bank charge"]
14
+
15
+ }
docker-compose.yml ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.9'
2
+
3
+ services:
4
+ fastapi-api:
5
+ build: .
6
+ container_name: financial-notes-api
7
+ ports:
8
+ - "8000:8000"
9
+ volumes:
10
+ - ./input:/app/input
11
+ - ./output1:/app/output1
12
+ - ./generated_notes:/app/generated_notes
13
+ - ./.env:/app/.env
14
+ environment:
15
+ - PYTHONUNBUFFERED=1
16
+ - PORT=8000
17
+ env_file:
18
+ - .env
19
+ restart: unless-stopped
pnlbs/bl_llm.py ADDED
@@ -0,0 +1,835 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import re
4
+ import logging
5
+ from datetime import datetime
6
+ from typing import Any, Dict, List, Optional
7
+ from openpyxl import Workbook
8
+ from openpyxl.styles import Font, Border, Side, Alignment
9
+ import requests
10
+ from dotenv import load_dotenv
11
+ from pydantic import BaseModel, Field, ValidationError
12
+ from pydantic_settings import BaseSettings
13
+
14
+ load_dotenv()
15
+
16
+ # Configure logging
17
+ logging.basicConfig(level=logging.INFO)
18
+ logger = logging.getLogger(__name__)
19
+
20
+ class Settings(BaseSettings):
21
+ """Application settings loaded from environment variables or .env file."""
22
+ api_key: str = Field(default_factory=lambda: os.getenv("OPENROUTER_API_KEY", ""), env="OPENROUTER_API_KEY")
23
+ input_file: str = Field(default="clean_financial_data_bs.json", env="INPUT_FILE")
24
+ output_dir: str = Field(default="output", env="BL_OUTPUT_DIR")
25
+
26
+ settings = Settings()
27
+
28
+ class BalanceSheetItem(BaseModel):
29
+ category: str
30
+ subcategory: Optional[str] = ""
31
+ name: str
32
+ note: Optional[str] = ""
33
+ value_2024: float
34
+ value_2023: float
35
+
36
+ class BalanceSheetTotals(BaseModel):
37
+ shareholders_funds_2024: float = 0.0
38
+ shareholders_funds_2023: float = 0.0
39
+ non_current_liabilities_2024: float = 0.0
40
+ non_current_liabilities_2023: float = 0.0
41
+ current_liabilities_2024: float = 0.0
42
+ current_liabilities_2023: float = 0.0
43
+ non_current_assets_2024: float = 0.0
44
+ non_current_assets_2023: float = 0.0
45
+ current_assets_2024: float = 0.0
46
+ current_assets_2023: float = 0.0
47
+ total_equity_liabilities_2024: float = 0.0
48
+ total_equity_liabilities_2023: float = 0.0
49
+ total_assets_2024: float = 0.0
50
+ total_assets_2023: float = 0.0
51
+ balance_difference_2024: float = 0.0
52
+ balance_difference_2023: float = 0.0
53
+
54
+ class EnhancedBalanceSheetGenerator:
55
+ def __init__(self, api_key: str):
56
+ self.api_key = api_key
57
+ self.base_url = "https://openrouter.ai/api/v1/chat/completions"
58
+
59
+ # Enhanced mapping with multiple patterns
60
+ self.field_mappings = {
61
+ # Share Capital patterns
62
+ 'share_capital': [
63
+ 'share capital', 'equity share', 'paid up', 'issued shares',
64
+ 'authorised shares', 'subscribed', 'fully paid'
65
+ ],
66
+ # Reserves patterns
67
+ 'reserves_surplus': [
68
+ 'reserves and surplus', 'reserves', 'surplus', 'retained earnings',
69
+ 'profit and loss', 'general reserves', 'closing balance'
70
+ ],
71
+ # Long term borrowings
72
+ 'long_term_borrowings': [
73
+ 'long term borrowings', 'long-term borrowings', 'borrowings',
74
+ 'debt', 'loans', 'financial corporation', 'bank loan'
75
+ ],
76
+ # Deferred tax
77
+ 'deferred_tax': [
78
+ 'deferred tax', 'tax liability', 'deferred tax liability'
79
+ ],
80
+ # Trade payables
81
+ 'trade_payables': [
82
+ 'trade payables', 'payables', 'creditors', 'sundry creditors',
83
+ 'capital expenditure', 'other expenses'
84
+ ],
85
+ # Other current liabilities
86
+ 'other_current_liabilities': [
87
+ 'other current liabilities', 'current maturities', 'outstanding liabilities',
88
+ 'statutory dues', 'accrued expenses'
89
+ ],
90
+ # Short term provisions
91
+ 'short_term_provisions': [
92
+ 'short term provisions', 'provisions', 'provision for taxation',
93
+ 'tax provision'
94
+ ],
95
+ # Fixed assets - Tangible
96
+ 'tangible_assets': [
97
+ 'tangible assets', 'property plant', 'fixed assets', 'buildings',
98
+ 'plant', 'equipment', 'net carrying value'
99
+ ],
100
+ # Fixed assets - Intangible
101
+ 'intangible_assets': [
102
+ 'intangible assets', 'software', 'goodwill', 'intangible'
103
+ ],
104
+ # Long term loans and advances
105
+ 'long_term_loans_advances': [
106
+ 'long term loans', 'security deposits', 'long term advances'
107
+ ],
108
+ # Inventories
109
+ 'inventories': [
110
+ 'inventories', 'stock', 'consumables', 'raw materials'
111
+ ],
112
+ # Trade receivables
113
+ 'trade_receivables': [
114
+ 'trade receivables', 'receivables', 'debtors', 'outstanding',
115
+ 'other receivables'
116
+ ],
117
+ # Cash and bank
118
+ 'cash_bank': [
119
+ 'cash and bank', 'cash', 'bank balances', 'current accounts',
120
+ 'cash on hand', 'fixed deposits'
121
+ ],
122
+ # Short term loans and advances
123
+ 'short_term_loans_advances': [
124
+ 'short term loans', 'prepaid expenses', 'other advances',
125
+ 'advance tax', 'statutory authorities'
126
+ ],
127
+ # Other current assets
128
+ 'other_current_assets': [
129
+ 'other current assets', 'accrued income', 'interest accrued'
130
+ ]
131
+ }
132
+
133
+ def safe_float(self, value: Any) -> float:
134
+ """Convert various value formats to float."""
135
+ if not value or str(value).strip() in ['-', '--', 'None', '', 'null']:
136
+ return 0.0
137
+
138
+ # Handle strings
139
+ if isinstance(value, str):
140
+ # Remove currency symbols and brackets
141
+ cleaned = re.sub(r'[₹,Rs\.\s\(\)]', '', value)
142
+ # Handle negative values in brackets
143
+ if '(' in str(value) and ')' in str(value):
144
+ cleaned = '-' + cleaned.replace('(', '').replace(')', '')
145
+ try:
146
+ return float(cleaned)
147
+ except Exception:
148
+ return 0.0
149
+
150
+ # Handle numeric values
151
+ try:
152
+ return float(value)
153
+ except Exception:
154
+ return 0.0
155
+
156
+ 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]:
157
+ """Flexibly extract values from either list or dictionary format."""
158
+ if isinstance(data, dict):
159
+ # Dictionary format - extract by date keys
160
+ val_2024 = self.safe_float(data.get(date_key_2024, 0))
161
+ val_2023 = self.safe_float(data.get(date_key_2023, 0))
162
+ return val_2024, val_2023
163
+
164
+ elif isinstance(data, list):
165
+ # List format - assume first element is 2024, second is 2023
166
+ val_2024 = self.safe_float(data[0]) if len(data) > 0 else 0.0
167
+ val_2023 = self.safe_float(data[1]) if len(data) > 1 else 0.0
168
+ return val_2024, val_2023
169
+
170
+ else:
171
+ # Single value or other format
172
+ val = self.safe_float(data)
173
+ return val, 0.0 # Assume it's 2024 value, 2023 is 0
174
+
175
+ def call_ai_for_analysis(self, data_summary: str) -> Dict[str, Any]:
176
+ """Use AI to analyze and extract balance sheet data"""
177
+
178
+ prompt = f"""
179
+ You are a financial analyst. Extract balance sheet data from the following JSON data and create a properly structured balance sheet.
180
+
181
+ CRITICAL REQUIREMENTS:
182
+ 1. Extract ALL line items with their 2024 and 2023 values
183
+ 2. Calculate missing totals where needed
184
+ 3. Ensure the balance sheet balances (Assets = Equity + Liabilities)
185
+ 4. Return ONLY valid JSON in the exact format specified below
186
+
187
+ Expected Balance Sheet Structure:
188
+ - EQUITY AND LIABILITIES
189
+ - Shareholders' funds (Share capital, Reserves and surplus)
190
+ - Non-Current liabilities (Long term borrowings, Deferred tax liability)
191
+ - Current liabilities (Trade payables, Other current liabilities, Short term provisions)
192
+ - ASSETS
193
+ - Non-current assets (Fixed assets - Tangible/Intangible, Long term loans and advances)
194
+ - Current assets (Inventories, Trade receivables, Cash and bank balances, Short-term loans and advances, Other current assets)
195
+
196
+ Data to analyze:
197
+ {data_summary}
198
+
199
+ Return ONLY this JSON format:
200
+ {{
201
+ "balance_sheet_items": [
202
+ {{
203
+ "category": "Shareholders' funds",
204
+ "subcategory": "",
205
+ "name": "Share capital",
206
+ "note": "2",
207
+ "value_2024": 542.52,
208
+ "value_2023": 542.52
209
+ }},
210
+ {{
211
+ "category": "Shareholders' funds",
212
+ "subcategory": "",
213
+ "name": "Reserves and surplus",
214
+ "note": "3",
215
+ "value_2024": 3152.39,
216
+ "value_2023": 2642.87
217
+ }},
218
+ {{
219
+ "category": "Non-Current liabilities",
220
+ "subcategory": "",
221
+ "name": "Long term borrowings",
222
+ "note": "4",
223
+ "value_2024": 914.46,
224
+ "value_2023": 321.36
225
+ }}
226
+ ],
227
+ "totals": {{
228
+ "shareholders_funds_2024": 3694.91,
229
+ "shareholders_funds_2023": 3185.39,
230
+ "total_equity_liabilities_2024": 5246.10,
231
+ "total_equity_liabilities_2023": 4725.23,
232
+ "total_assets_2024": 5246.10,
233
+ "total_assets_2023": 4725.23
234
+ }}
235
+ }}
236
+ """
237
+
238
+ headers = {
239
+ "Authorization": f"Bearer {self.api_key}",
240
+ "Content-Type": "application/json"
241
+ }
242
+
243
+ payload = {
244
+ "model": "anthropic/claude-3.5-sonnet",
245
+ "messages": [{"role": "user", "content": prompt}],
246
+ "temperature": 0.1,
247
+ "max_tokens": 4000
248
+ }
249
+
250
+ try:
251
+ response = requests.post(self.base_url, headers=headers, json=payload, timeout=60)
252
+ content = response.json()['choices'][0]['message']['content']
253
+
254
+ # Clean the response
255
+ content = re.sub(r'```(?:json)?\s*', '', content).strip('`').strip()
256
+
257
+ return json.loads(content)
258
+ except Exception as e:
259
+ logger.error(f"AI analysis failed: {e}")
260
+ return {"balance_sheet_items": [], "totals": {}}
261
+
262
+ def extract_from_json_structure(self, json_data: Dict[str, Any]) -> List[Dict[str, Any]]:
263
+ """Direct extraction from the structured JSON data with flexible list/dict support"""
264
+ items = []
265
+
266
+ company_data = json_data.get("company_financial_data", {})
267
+
268
+ # Extract Share Capital
269
+ share_capital = company_data.get("share_capital", {})
270
+ total_share_capital = share_capital.get("Total issued, subscribed and fully paid-up share capital", {})
271
+ if total_share_capital:
272
+ val_2024, val_2023 = self.get_value_flexible(total_share_capital)
273
+ if val_2024 or val_2023:
274
+ items.append({
275
+ "category": "Shareholders' funds",
276
+ "name": "Share capital",
277
+ "note": "2",
278
+ "value_2024": val_2024,
279
+ "value_2023": val_2023
280
+ })
281
+
282
+ # Extract Reserves and Surplus
283
+ reserves = company_data.get("reserves_and_surplus", {})
284
+ closing_balance = reserves.get("Balance, at the end of the year", {})
285
+ if closing_balance:
286
+ val_2024, val_2023 = self.get_value_flexible(closing_balance)
287
+ if val_2024 or val_2023:
288
+ items.append({
289
+ "category": "Shareholders' funds",
290
+ "name": "Reserves and surplus",
291
+ "note": "3",
292
+ "value_2024": val_2024,
293
+ "value_2023": val_2023
294
+ })
295
+
296
+ # Extract Long-term Borrowings
297
+ borrowings = company_data.get("borrowings", {}).get("4. Long-Term Borrowings", {})
298
+ total_borrowings_2024 = 0
299
+ total_borrowings_2023 = 0
300
+
301
+ for key, value in borrowings.items():
302
+ if key != "_metadata" and value is not None:
303
+ val_2024, val_2023 = self.get_value_flexible(value)
304
+ total_borrowings_2024 += val_2024
305
+ total_borrowings_2023 += val_2023
306
+
307
+ if total_borrowings_2024 or total_borrowings_2023:
308
+ items.append({
309
+ "category": "Non-Current liabilities",
310
+ "name": "Long term borrowings",
311
+ "note": "4",
312
+ "value_2024": total_borrowings_2024,
313
+ "value_2023": total_borrowings_2023
314
+ })
315
+
316
+ # Extract Deferred Tax
317
+ deferred_tax = company_data.get("other_data", {}).get("5. Deferred Tax Liability / (Asset)", {})
318
+ if deferred_tax:
319
+ dtl = deferred_tax.get("Deferred tax liability", {})
320
+ val_2024, val_2023 = self.get_value_flexible(dtl)
321
+ if val_2024 or val_2023:
322
+ items.append({
323
+ "category": "Non-Current liabilities",
324
+ "name": "Deferred Tax Liability (Net)",
325
+ "note": "5",
326
+ "value_2024": val_2024,
327
+ "value_2023": val_2023
328
+ })
329
+
330
+ # Extract Current Liabilities
331
+ current_liabilities = company_data.get("current_liabilities", {})
332
+
333
+ # Trade Payables
334
+ trade_payables = current_liabilities.get("6. Trade Payables", {})
335
+ tp_2024 = tp_2023 = 0
336
+ for key, value in trade_payables.items():
337
+ if key not in ["_metadata", "Particulars", "Disputed dues"] and value is not None:
338
+ val_2024, val_2023 = self.get_value_flexible(value)
339
+ tp_2024 += val_2024
340
+ tp_2023 += val_2023
341
+
342
+ if tp_2024 or tp_2023:
343
+ items.append({
344
+ "category": "Current liabilities",
345
+ "name": "Trade payables",
346
+ "note": "6",
347
+ "value_2024": tp_2024,
348
+ "value_2023": tp_2023
349
+ })
350
+
351
+ # Other Current Liabilities
352
+ other_cl = current_liabilities.get("7. Other Current Liabilities", {})
353
+ ocl_2024 = ocl_2023 = 0
354
+ for key, value in other_cl.items():
355
+ if key != "_metadata" and value is not None:
356
+ val_2024, val_2023 = self.get_value_flexible(value)
357
+ ocl_2024 += val_2024
358
+ ocl_2023 += val_2023
359
+
360
+ if ocl_2024 or ocl_2023:
361
+ items.append({
362
+ "category": "Current liabilities",
363
+ "name": "Other current liabilities",
364
+ "note": "7",
365
+ "value_2024": ocl_2024,
366
+ "value_2023": ocl_2023
367
+ })
368
+
369
+ # Short Term Provisions
370
+ provisions = current_liabilities.get("8. Short Term Provisions", {})
371
+ prov_2024 = prov_2023 = 0
372
+ for key, value in provisions.items():
373
+ if key != "_metadata" and value is not None:
374
+ val_2024, val_2023 = self.get_value_flexible(value)
375
+ prov_2024 += val_2024
376
+ prov_2023 += val_2023
377
+
378
+ if prov_2024 or prov_2023:
379
+ items.append({
380
+ "category": "Current liabilities",
381
+ "name": "Short term provisions",
382
+ "note": "8",
383
+ "value_2024": prov_2024,
384
+ "value_2023": prov_2023
385
+ })
386
+
387
+ # Extract Fixed Assets
388
+ fixed_assets = company_data.get("fixed_assets", {})
389
+
390
+ # Tangible Assets
391
+ tangible = fixed_assets.get("tangible_assets", {}).get("", {})
392
+ if tangible:
393
+ net_carrying = tangible.get("net_carrying_value", {})
394
+ if net_carrying:
395
+ # Handle both dict and list formats for net carrying value
396
+ if isinstance(net_carrying, dict):
397
+ val_2024 = self.safe_float(net_carrying.get("closing", 0))
398
+ val_2023 = self.safe_float(net_carrying.get("opening", 0))
399
+ else:
400
+ val_2024, val_2023 = self.get_value_flexible(net_carrying)
401
+
402
+ if val_2024 or val_2023:
403
+ items.append({
404
+ "category": "Non-current assets",
405
+ "subcategory": "Fixed assets",
406
+ "name": "Tangible assets",
407
+ "note": "9",
408
+ "value_2024": val_2024,
409
+ "value_2023": val_2023
410
+ })
411
+
412
+ # Intangible Assets
413
+ intangible = fixed_assets.get("intangible_assets", {}).get("", {})
414
+ if intangible:
415
+ net_carrying = intangible.get("net_carrying_value", {})
416
+ if net_carrying:
417
+ # Handle both dict and list formats for net carrying value
418
+ if isinstance(net_carrying, dict):
419
+ val_2024 = self.safe_float(net_carrying.get("closing", 0))
420
+ val_2023 = self.safe_float(net_carrying.get("opening", 0))
421
+ else:
422
+ val_2024, val_2023 = self.get_value_flexible(net_carrying)
423
+
424
+ if val_2024 or val_2023:
425
+ items.append({
426
+ "category": "Non-current assets",
427
+ "subcategory": "Fixed assets",
428
+ "name": "Intangible assets",
429
+ "note": "9",
430
+ "value_2024": val_2024,
431
+ "value_2023": val_2023
432
+ })
433
+
434
+ # Long Term Loans and Advances
435
+ lt_loans = company_data.get("loans_and_advances", {}).get("10. Long Term Loans and advances", {})
436
+ lt_2024 = lt_2023 = 0
437
+ for key, value in lt_loans.items():
438
+ if key != "_metadata" and value is not None:
439
+ val_2024, val_2023 = self.get_value_flexible(value)
440
+ lt_2024 += val_2024
441
+ lt_2023 += val_2023
442
+
443
+ if lt_2024 or lt_2023:
444
+ items.append({
445
+ "category": "Non-current assets",
446
+ "name": "Long Term Loans and Advances",
447
+ "note": "10",
448
+ "value_2024": lt_2024,
449
+ "value_2023": lt_2023
450
+ })
451
+
452
+ # Extract Current Assets
453
+ current_assets = company_data.get("current_assets", {})
454
+
455
+ # Inventories
456
+ inventories = current_assets.get("11. Inventories", {})
457
+ inv_2024 = inv_2023 = 0
458
+ for key, value in inventories.items():
459
+ if key != "_metadata" and value is not None:
460
+ val_2024, val_2023 = self.get_value_flexible(value)
461
+ inv_2024 += val_2024
462
+ inv_2023 += val_2023
463
+
464
+ if inv_2024 or inv_2023:
465
+ items.append({
466
+ "category": "Current assets",
467
+ "name": "Inventories",
468
+ "note": "11",
469
+ "value_2024": inv_2024,
470
+ "value_2023": inv_2023
471
+ })
472
+
473
+ # Trade Receivables
474
+ trade_recv = current_assets.get("12. Trade receivables", {})
475
+ tr_2024 = tr_2023 = 0
476
+ for key, value in trade_recv.items():
477
+ if key not in ["_metadata", "Particulars", "trade_receivables_aging"] and value is not None:
478
+ val_2024, val_2023 = self.get_value_flexible(value)
479
+ tr_2024 += val_2024
480
+ tr_2023 += val_2023
481
+
482
+ if tr_2024 or tr_2023:
483
+ items.append({
484
+ "category": "Current assets",
485
+ "name": "Trade receivables",
486
+ "note": "12",
487
+ "value_2024": tr_2024,
488
+ "value_2023": tr_2023
489
+ })
490
+
491
+ # Cash and Bank Balances
492
+ cash_bank = current_assets.get("13. Cash and bank balances", {})
493
+ cb_2024 = cb_2023 = 0
494
+ for key, value in cash_bank.items():
495
+ if key != "_metadata" and value is not None:
496
+ val_2024, val_2023 = self.get_value_flexible(value)
497
+ cb_2024 += val_2024
498
+ cb_2023 += val_2023
499
+
500
+ if cb_2024 or cb_2023:
501
+ items.append({
502
+ "category": "Current assets",
503
+ "name": "Cash and bank balances",
504
+ "note": "13",
505
+ "value_2024": cb_2024,
506
+ "value_2023": cb_2023
507
+ })
508
+
509
+ # Short-term Loans and Advances
510
+ st_loans = company_data.get("loans_and_advances", {}).get("14. Short Term Loans and Advances", {})
511
+ st_2024 = st_2023 = 0
512
+ for key, value in st_loans.items():
513
+ if key != "_metadata" and value is not None:
514
+ val_2024, val_2023 = self.get_value_flexible(value)
515
+ st_2024 += val_2024
516
+ st_2023 += val_2023
517
+
518
+ if st_2024 or st_2023:
519
+ items.append({
520
+ "category": "Current assets",
521
+ "name": "Short-term loans and advances",
522
+ "note": "14",
523
+ "value_2024": st_2024,
524
+ "value_2023": st_2023
525
+ })
526
+
527
+ # Other Current Assets
528
+ other_ca = company_data.get("other_data", {}).get("15. Other Current Assets", {})
529
+ oca_2024 = oca_2023 = 0
530
+ for key, value in other_ca.items():
531
+ if key != "_metadata" and value is not None:
532
+ val_2024, val_2023 = self.get_value_flexible(value)
533
+ oca_2024 += val_2024
534
+ oca_2023 += val_2023
535
+
536
+ if oca_2024 or oca_2023:
537
+ items.append({
538
+ "category": "Current assets",
539
+ "name": "Other current assets",
540
+ "note": "15",
541
+ "value_2024": oca_2024,
542
+ "value_2023": oca_2023
543
+ })
544
+
545
+ return items
546
+
547
+ def calculate_totals(self, items: List[Dict[str, Any]]) -> BalanceSheetTotals:
548
+ """Calculate section totals and verify balance"""
549
+ totals = {}
550
+
551
+ # Group by categories
552
+ categories = {}
553
+ for item in items:
554
+ cat = item["category"]
555
+ if cat not in categories:
556
+ categories[cat] = {"2024": 0, "2023": 0}
557
+ categories[cat]["2024"] += item["value_2024"]
558
+ categories[cat]["2023"] += item["value_2023"]
559
+
560
+ # Calculate major totals
561
+ shareholders_funds_2024 = categories.get("Shareholders' funds", {}).get("2024", 0)
562
+ shareholders_funds_2023 = categories.get("Shareholders' funds", {}).get("2023", 0)
563
+
564
+ non_current_liab_2024 = categories.get("Non-Current liabilities", {}).get("2024", 0)
565
+ non_current_liab_2023 = categories.get("Non-Current liabilities", {}).get("2023", 0)
566
+
567
+ current_liab_2024 = categories.get("Current liabilities", {}).get("2024", 0)
568
+ current_liab_2023 = categories.get("Current liabilities", {}).get("2023", 0)
569
+
570
+ non_current_assets_2024 = categories.get("Non-current assets", {}).get("2024", 0)
571
+ non_current_assets_2023 = categories.get("Non-current assets", {}).get("2023", 0)
572
+
573
+ current_assets_2024 = categories.get("Current assets", {}).get("2024", 0)
574
+ current_assets_2023 = categories.get("Current assets", {}).get("2023", 0)
575
+
576
+ total_equity_liab_2024 = shareholders_funds_2024 + non_current_liab_2024 + current_liab_2024
577
+ total_equity_liab_2023 = shareholders_funds_2023 + non_current_liab_2023 + current_liab_2023
578
+
579
+ total_assets_2024 = non_current_assets_2024 + current_assets_2024
580
+ total_assets_2023 = non_current_assets_2023 + current_assets_2023
581
+
582
+ return BalanceSheetTotals(
583
+ shareholders_funds_2024=shareholders_funds_2024,
584
+ shareholders_funds_2023=shareholders_funds_2023,
585
+ non_current_liabilities_2024=non_current_liab_2024,
586
+ non_current_liabilities_2023=non_current_liab_2023,
587
+ current_liabilities_2024=current_liab_2024,
588
+ current_liabilities_2023=current_liab_2023,
589
+ non_current_assets_2024=non_current_assets_2024,
590
+ non_current_assets_2023=non_current_assets_2023,
591
+ current_assets_2024=current_assets_2024,
592
+ current_assets_2023=current_assets_2023,
593
+ total_equity_liabilities_2024=total_equity_liab_2024,
594
+ total_equity_liabilities_2023=total_equity_liab_2023,
595
+ total_assets_2024=total_assets_2024,
596
+ total_assets_2023=total_assets_2023,
597
+ balance_difference_2024=abs(total_assets_2024 - total_equity_liab_2024),
598
+ balance_difference_2023=abs(total_assets_2023 - total_equity_liab_2023)
599
+ )
600
+
601
+ def generate_balance_sheet_excel(self, items: List[Dict[str, Any]], totals: BalanceSheetTotals, output_dir: str = "output") -> str:
602
+ """Generate formatted Excel balance sheet"""
603
+ os.makedirs(output_dir, exist_ok=True)
604
+
605
+ wb = Workbook()
606
+ ws = wb.active
607
+ ws.title = "Balance Sheet"
608
+
609
+ # Set column widths
610
+ ws.column_dimensions["A"].width = 40
611
+ ws.column_dimensions["B"].width = 8
612
+ ws.column_dimensions["C"].width = 15
613
+ ws.column_dimensions["D"].width = 15
614
+
615
+ # Styles
616
+ bold_font = Font(bold=True)
617
+ thin_border = Border(
618
+ left=Side(style='thin'), right=Side(style='thin'),
619
+ top=Side(style='thin'), bottom=Side(style='thin')
620
+ )
621
+
622
+ row = 1
623
+
624
+ def add_row(desc, note, val_2024, val_2023, bold=False, indent=0, border=False):
625
+ nonlocal row
626
+
627
+ # Description
628
+ cell_a = ws.cell(row=row, column=1, value=" " * indent + desc)
629
+ if bold:
630
+ cell_a.font = bold_font
631
+ if border:
632
+ cell_a.border = thin_border
633
+
634
+ # Note
635
+ cell_b = ws.cell(row=row, column=2, value=note)
636
+ if bold:
637
+ cell_b.font = bold_font
638
+ if border:
639
+ cell_b.border = thin_border
640
+
641
+ # Values
642
+ for col, val in [(3, val_2024), (4, val_2023)]:
643
+ cell = ws.cell(row=row, column=col)
644
+ if val != 0:
645
+ cell.value = val
646
+ cell.number_format = '#,##0.00'
647
+ if bold:
648
+ cell.font = bold_font
649
+ if border:
650
+ cell.border = thin_border
651
+ cell.alignment = Alignment(horizontal='right')
652
+
653
+ row += 1
654
+
655
+ # Header
656
+ add_row("Balance Sheet as at March 31, 2024", "", 0, 0, True)
657
+ add_row("", "", 0, 0)
658
+ add_row("(In Lakhs)", "", 0, 0)
659
+ add_row("", "Notes", "March 31, 2024", "March 31, 2023", True)
660
+ add_row("", "", 0, 0)
661
+
662
+ # EQUITY AND LIABILITIES
663
+ add_row("EQUITY AND LIABILITIES", "", 0, 0, True)
664
+
665
+ # Shareholders' funds
666
+ add_row("Shareholders' funds", "", 0, 0, True)
667
+ shareholders_items = [item for item in items if item["category"] == "Shareholders' funds"]
668
+ for item in shareholders_items:
669
+ add_row(item["name"], item["note"], item["value_2024"], item["value_2023"])
670
+
671
+ add_row("", "", totals.shareholders_funds_2024, totals.shareholders_funds_2023, True)
672
+ add_row("", "", 0, 0)
673
+
674
+ # Non-Current liabilities
675
+ add_row("Non-Current liabilities", "", 0, 0, True)
676
+ non_current_liab_items = [item for item in items if item["category"] == "Non-Current liabilities"]
677
+ for item in non_current_liab_items:
678
+ add_row(item["name"], item["note"], item["value_2024"], item["value_2023"])
679
+
680
+ add_row("", "", totals.non_current_liabilities_2024, totals.non_current_liabilities_2023, True)
681
+ add_row("", "", 0, 0)
682
+
683
+ # Current liabilities
684
+ add_row("Current liabilities", "", 0, 0, True)
685
+ current_liab_items = [item for item in items if item["category"] == "Current liabilities"]
686
+ for item in current_liab_items:
687
+ add_row(item["name"], item["note"], item["value_2024"], item["value_2023"])
688
+
689
+ add_row("", "", totals.current_liabilities_2024, totals.current_liabilities_2023, True)
690
+ add_row("", "", 0, 0)
691
+
692
+ # TOTAL EQUITY & LIABILITIES
693
+ add_row("TOTAL", "", totals.total_equity_liabilities_2024, totals.total_equity_liabilities_2023, True, 0, True)
694
+ add_row("", "", 0, 0)
695
+
696
+ # ASSETS
697
+ add_row("ASSETS", "", 0, 0, True)
698
+
699
+ # Non-current assets
700
+ add_row("Non-current assets", "", 0, 0, True)
701
+
702
+ # Fixed assets
703
+ fixed_asset_items = [item for item in items if item.get("subcategory") == "Fixed assets"]
704
+ if fixed_asset_items:
705
+ add_row("Fixed assets", "", 0, 0, True, 1)
706
+ fixed_total_2024 = fixed_total_2023 = 0
707
+ for item in fixed_asset_items:
708
+ add_row(item["name"], item["note"], item["value_2024"], item["value_2023"], False, 2)
709
+ fixed_total_2024 += item["value_2024"]
710
+ fixed_total_2023 += item["value_2023"]
711
+ add_row("", "", fixed_total_2024, fixed_total_2023, True, 2)
712
+
713
+ # Other non-current assets
714
+ other_non_current = [item for item in items if item["category"] == "Non-current assets" and item.get("subcategory") != "Fixed assets"]
715
+ for item in other_non_current:
716
+ add_row(item["name"], item["note"], item["value_2024"], item["value_2023"], False, 1)
717
+
718
+ add_row("", "", totals.non_current_assets_2024, totals.non_current_assets_2023, True)
719
+ add_row("", "", 0, 0)
720
+
721
+ # Current assets
722
+ add_row("Current assets", "", 0, 0, True)
723
+ current_asset_items = [item for item in items if item["category"] == "Current assets"]
724
+ for item in current_asset_items:
725
+ add_row(item["name"], item["note"], item["value_2024"], item["value_2023"], False, 1)
726
+
727
+ add_row("", "", totals.current_assets_2024, totals.current_assets_2023, True)
728
+ add_row("", "", 0, 0)
729
+
730
+ # TOTAL ASSETS
731
+ add_row("TOTAL", "", totals.total_assets_2024, totals.total_assets_2023, True, 0, True)
732
+
733
+ # Add balance verification
734
+ add_row("", "", 0, 0)
735
+ balance_2024 = totals.balance_difference_2024
736
+ balance_2023 = totals.balance_difference_2023
737
+
738
+ if balance_2024 < 0.01 and balance_2023 < 0.01:
739
+ add_row(" Balance Sheet is BALANCED", "", 0, 0, True)
740
+ else:
741
+ add_row(f" Balance Difference: {balance_2024:.2f} | {balance_2023:.2f}", "", 0, 0, True)
742
+
743
+ # Save file
744
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
745
+ output_file = os.path.join(output_dir, f"balance_sheet_{timestamp}.xlsx")
746
+ wb.save(output_file)
747
+ logger.info(f"Output file: {output_file}")
748
+ return output_file
749
+
750
+ def process(self, input_file: str, output_dir: str = "output") -> Optional[str]:
751
+ """Main processing function"""
752
+ try:
753
+ logger.info(f"Processing: {input_file}")
754
+
755
+ # Load JSON data
756
+ with open(input_file, 'r', encoding='utf-8') as f:
757
+ json_data = json.load(f)
758
+
759
+ logger.info("Extracting data from JSON structure...")
760
+
761
+ # Method 1: Direct extraction from structured JSON
762
+ items = self.extract_from_json_structure(json_data)
763
+ logger.info(f"Extracted {len(items)} items from JSON structure")
764
+
765
+ # Method 2: AI-assisted extraction if needed
766
+ if len(items) < 10: # If we don't have enough items
767
+ logger.info("Using AI for additional extraction...")
768
+
769
+ # Create summary for AI
770
+ summary = json.dumps(json_data, indent=2)[:8000] # Limit size
771
+ ai_result = self.call_ai_for_analysis(summary)
772
+
773
+ ai_items = ai_result.get("balance_sheet_items", [])
774
+ logger.info(f"AI extracted {len(ai_items)} additional items")
775
+
776
+ # Merge items (avoid duplicates)
777
+ existing_names = {item["name"].lower() for item in items}
778
+ for ai_item in ai_items:
779
+ if ai_item["name"].lower() not in existing_names:
780
+ items.append(ai_item)
781
+
782
+ if not items:
783
+ logger.error("No balance sheet items extracted")
784
+ return None
785
+
786
+ # Calculate totals
787
+ totals = self.calculate_totals(items)
788
+
789
+ # Display summary
790
+ logger.info(f"\n BALANCE SHEET SUMMARY:")
791
+ logger.info(f"Total Items Extracted: {len(items)}")
792
+ logger.info(f"Assets 2024: Rs. {totals.total_assets_2024:,.2f} Lakhs")
793
+ logger.info(f"Equity & Liabilities 2024: Rs. {totals.total_equity_liabilities_2024:,.2f} Lakhs")
794
+ logger.info(f"Balance Difference 2024: Rs. {totals.balance_difference_2024:,.2f} Lakhs")
795
+ logger.info(f"Assets 2023: Rs. {totals.total_assets_2023:,.2f} Lakhs")
796
+ logger.info(f"Equity & Liabilities 2023: Rs. {totals.total_equity_liabilities_2023:,.2f} Lakhs")
797
+ logger.info(f"Balance Difference 2023: Rs. {totals.balance_difference_2023:,.2f} Lakhs")
798
+
799
+ # Check if balanced
800
+ is_balanced_2024 = totals.balance_difference_2024 < 0.01
801
+ is_balanced_2023 = totals.balance_difference_2023 < 0.01
802
+
803
+ if is_balanced_2024 and is_balanced_2023:
804
+ logger.info("Balance Sheet is PERFECTLY BALANCED!")
805
+ else:
806
+ logger.warning("Balance Sheet has differences - may need adjustment")
807
+
808
+ # Generate Excel
809
+ output_file = self.generate_balance_sheet_excel(items, totals, output_dir)
810
+
811
+ logger.info(f"SUCCESS: Generated {output_file}")
812
+ return output_file
813
+
814
+ except Exception as e:
815
+ logger.error(f"Error processing file: {e}", exc_info=True)
816
+ return None
817
+
818
+ def main() -> None:
819
+ """Main function for running the balance sheet generator."""
820
+ logger.info("ENHANCED BALANCE SHEET GENERATOR v2.0")
821
+ api_key = settings.api_key
822
+ if not api_key:
823
+ logger.error("Missing OPENROUTER_API_KEY environment variable. Please set your OpenRouter API key in the .env file.")
824
+ return
825
+ input_file = settings.input_file
826
+ if not os.path.exists(input_file):
827
+ logger.error(f"Input file not found: {input_file}. Please ensure your JSON data file exists.")
828
+ return
829
+ generator = EnhancedBalanceSheetGenerator(api_key)
830
+ result = generator.process(input_file, settings.output_dir)
831
+ if result:
832
+ logger.info(f"COMPLETED SUCCESSFULLY! Output file: {result}")
833
+ logger.info("Open the Excel file to view your balance sheet")
834
+ else:
835
+ logger.error("PROCESSING FAILED. Please check the error messages above and try again.")
pnlbs/csv_json_bs.py ADDED
@@ -0,0 +1,360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import json
3
+ import os
4
+ import re
5
+ import logging
6
+ from datetime import datetime
7
+ from typing import Dict, List, Any, Optional, Union
8
+ from pydantic import BaseModel, Field, ValidationError
9
+ from pydantic_settings import BaseSettings
10
+
11
+ # Configure logging
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ class Settings(BaseSettings):
16
+ """Settings for CSV to JSON conversion, loaded from environment variables or .env file."""
17
+ csv_folder_path: str = Field(default="csv_notes_bs", env="CSV_FOLDER_PATH")
18
+ output_json: str = Field(default="clean_financial_data_bs.json", env="OUTPUT_JSON")
19
+
20
+ settings = Settings()
21
+
22
+ class NoteSection(BaseModel):
23
+ title: str
24
+ data: Dict[str, Any]
25
+
26
+ class FixedAssetData(BaseModel):
27
+ gross_carrying_value: Dict[str, Optional[Union[float, int]]]
28
+ accumulated_depreciation: Dict[str, Optional[Union[float, int]]]
29
+ net_carrying_value: Dict[str, Optional[Union[float, int]]]
30
+
31
+ class FinancialData(BaseModel):
32
+ processing_summary: Dict[str, Any]
33
+ share_capital: Dict[str, Any] = Field(default_factory=dict)
34
+ reserves_and_surplus: Dict[str, Any] = Field(default_factory=dict)
35
+ borrowings: Dict[str, Any] = Field(default_factory=dict)
36
+ current_liabilities: Dict[str, Any] = Field(default_factory=dict)
37
+ fixed_assets: Dict[str, Any] = Field(default_factory=dict)
38
+ current_assets: Dict[str, Any] = Field(default_factory=dict)
39
+ loans_and_advances: Dict[str, Any] = Field(default_factory=dict)
40
+ other_data: Dict[str, Any] = Field(default_factory=dict)
41
+
42
+ class FinancialCSVMapper:
43
+ def __init__(self, csv_folder_path: str = settings.csv_folder_path):
44
+ self.csv_folder_path = csv_folder_path
45
+
46
+ def clean_value(self, value: Any) -> Optional[Union[float, int, str]]:
47
+ """
48
+ Clean and convert values appropriately.
49
+ Returns None for empty or NaN values.
50
+ """
51
+ if pd.isna(value) or value == '':
52
+ return None
53
+ value_str = str(value).strip()
54
+ cleaned_num = re.sub(r'[,\s₹]', '', value_str)
55
+ try:
56
+ if '.' in cleaned_num:
57
+ return float(cleaned_num)
58
+ else:
59
+ return int(cleaned_num)
60
+ except (ValueError, TypeError):
61
+ return value_str
62
+
63
+ def identify_note_sections(self, df: pd.DataFrame) -> Dict[str, Dict]:
64
+ """
65
+ Identify and extract note sections (e.g., 2. Share capital, 3. Reserves).
66
+ Returns a dictionary of section title to parsed data.
67
+ """
68
+ sections = {}
69
+ current_section = None
70
+ current_data = []
71
+
72
+ for idx, row in df.iterrows():
73
+ first_col = str(row.iloc[0]) if not pd.isna(row.iloc[0]) else ""
74
+ # Check if this is a new section header (starts with number and dot)
75
+ if re.match(r'^\d+\.?\s+[A-Za-z]', first_col):
76
+ # Save previous section
77
+ if current_section and current_data:
78
+ sections[current_section] = self.parse_section_data(current_data)
79
+
80
+ # Start new section
81
+ current_section = first_col.strip()
82
+ current_data = []
83
+ else:
84
+ # Add row to current section
85
+ if current_section:
86
+ row_data = [self.clean_value(cell) for cell in row]
87
+ if any(cell is not None for cell in row_data): # Skip empty rows
88
+ current_data.append(row_data)
89
+
90
+ # Handle last section
91
+ if current_section and current_data:
92
+ sections[current_section] = self.parse_section_data(current_data)
93
+
94
+ return sections
95
+
96
+ def parse_section_data(self, rows: List[List[Any]]) -> Dict:
97
+ """
98
+ Parse section data into a meaningful structure.
99
+ Returns a dictionary mapping keys to values or date-mapped values.
100
+ """
101
+ if not rows:
102
+ return {}
103
+
104
+ section_data = {}
105
+
106
+ # Find date headers (usually in first or second row)
107
+ date_row = None
108
+ for i, row in enumerate(rows[:3]):
109
+ for cell in row:
110
+ if cell and isinstance(cell, str) and re.search(r'\d{4}-\d{2}-\d{2}', str(cell)):
111
+ date_row = i
112
+ break
113
+ if date_row is not None:
114
+ break
115
+
116
+ # Extract dates if found
117
+ dates = []
118
+ if date_row is not None:
119
+ dates = [cell for cell in rows[date_row] if cell and re.search(r'\d{4}-\d{2}-\d{2}', str(cell))]
120
+
121
+ # Process data rows
122
+ for row in rows:
123
+ if not row or not row[0]:
124
+ continue
125
+
126
+ key = str(row[0]).strip()
127
+
128
+ # Skip header/date rows
129
+ if date_row is not None and row == rows[date_row]:
130
+ continue
131
+ if any(date in str(cell) for cell in row for date in dates if date):
132
+ continue
133
+
134
+ # Extract values (non-None values after the key)
135
+ values = [cell for cell in row[1:] if cell is not None]
136
+
137
+ if values:
138
+ if len(values) == 1:
139
+ section_data[key] = values[0]
140
+ else:
141
+ # If we have dates, map values to dates
142
+ if dates and len(values) <= len(dates):
143
+ section_data[key] = {dates[i]: values[i] for i in range(len(values))}
144
+ else:
145
+ section_data[key] = values
146
+
147
+ # Add dates to metadata if found
148
+ if dates:
149
+ section_data["_metadata"] = {"reporting_dates": dates}
150
+
151
+ return section_data
152
+
153
+ def parse_fixed_assets(self, df: pd.DataFrame) -> Dict[str, Any]:
154
+ """
155
+ Parse fixed assets table (Note 9) with proper structure.
156
+ Returns a dictionary with tangible, intangible, and totals.
157
+ """
158
+ fixed_assets = {
159
+ "tangible_assets": {},
160
+ "intangible_assets": {},
161
+ "totals": {}
162
+ }
163
+
164
+ current_category = None
165
+
166
+ for idx, row in df.iterrows():
167
+ first_col = self.clean_value(row.iloc[0])
168
+
169
+ # Skip header rows
170
+ if not first_col or "Particulars" in str(first_col) or "Gross Carrying" in str(first_col):
171
+ continue
172
+
173
+ # Identify categories
174
+ if "Tangible Assets" in str(first_col):
175
+ current_category = "tangible"
176
+ continue
177
+ elif "Intangible Assets" in str(first_col):
178
+ current_category = "intangible"
179
+ continue
180
+ elif "Total" in str(first_col) or "Grand Total" in str(first_col):
181
+ current_category = "totals"
182
+
183
+ # Extract asset data
184
+ if current_category and len(row) > 1:
185
+ asset_name = str(first_col).strip()
186
+
187
+ # Remove numbering (1, 2, 3, etc.)
188
+ asset_name = re.sub(r'^\d+\s*', '', asset_name)
189
+
190
+ asset_data = FixedAssetData(
191
+ gross_carrying_value={
192
+ "opening": self.clean_value(row.iloc[2]) if len(row) > 2 else None,
193
+ "additions": self.clean_value(row.iloc[3]) if len(row) > 3 else None,
194
+ "deletions": self.clean_value(row.iloc[4]) if len(row) > 4 else None,
195
+ "closing": self.clean_value(row.iloc[5]) if len(row) > 5 else None
196
+ },
197
+ accumulated_depreciation={
198
+ "opening": self.clean_value(row.iloc[6]) if len(row) > 6 else None,
199
+ "for_the_year": self.clean_value(row.iloc[7]) if len(row) > 7 else None,
200
+ "deletions": self.clean_value(row.iloc[8]) if len(row) > 8 else None,
201
+ "closing": self.clean_value(row.iloc[9]) if len(row) > 9 else None
202
+ },
203
+ net_carrying_value={
204
+ "closing": self.clean_value(row.iloc[10]) if len(row) > 10 else None,
205
+ "opening": self.clean_value(row.iloc[11]) if len(row) > 11 else None
206
+ }
207
+ )
208
+
209
+ if current_category == "tangible":
210
+ fixed_assets["tangible_assets"][asset_name] = asset_data.dict()
211
+ elif current_category == "intangible":
212
+ fixed_assets["intangible_assets"][asset_name] = asset_data.dict()
213
+ elif current_category == "totals":
214
+ fixed_assets["totals"][asset_name] = asset_data.dict()
215
+
216
+ return fixed_assets
217
+
218
+ def parse_trade_receivables_aging(self, df: pd.DataFrame) -> Dict[str, Any]:
219
+ """
220
+ Parse trade receivables aging analysis.
221
+ Returns a dictionary of year to aging buckets.
222
+ """
223
+ aging_data = {}
224
+ current_year = None
225
+
226
+ for idx, row in df.iterrows():
227
+ first_col = str(row.iloc[0]) if not pd.isna(row.iloc[0]) else ""
228
+
229
+ # Identify year sections
230
+ if "2024" in first_col:
231
+ current_year = "2024"
232
+ continue
233
+ elif "2023" in first_col:
234
+ current_year = "2023"
235
+ continue
236
+
237
+ # Parse aging buckets
238
+ if current_year and "Considered good" in first_col:
239
+ aging_data[current_year] = {
240
+ "0_6_months": self.clean_value(row.iloc[1]) if len(row) > 1 else None,
241
+ "6_12_months": self.clean_value(row.iloc[2]) if len(row) > 2 else None,
242
+ "1_2_years": self.clean_value(row.iloc[3]) if len(row) > 3 else None,
243
+ "2_3_years": self.clean_value(row.iloc[4]) if len(row) > 4 else None,
244
+ "more_than_3_years": self.clean_value(row.iloc[5]) if len(row) > 5 else None,
245
+ "total": self.clean_value(row.iloc[6]) if len(row) > 6 else None
246
+ }
247
+
248
+ return aging_data
249
+
250
+ def process_single_csv(self, file_path: str) -> Dict[str, Any]:
251
+ """
252
+ Process a single CSV file with intelligent parsing.
253
+ Returns a dictionary of processed data.
254
+ """
255
+ try:
256
+ df = pd.read_csv(file_path, encoding='utf-8')
257
+ filename = os.path.basename(file_path)
258
+ result = {
259
+ "file_name": filename,
260
+ "processing_date": datetime.now().isoformat()
261
+ }
262
+ # Special handling for different note types
263
+ if "Note_9" in filename:
264
+ # Fixed assets
265
+ result["fixed_assets"] = self.parse_fixed_assets(df)
266
+ elif "Note_2_to_8" in filename or "Note_10_to_15" in filename:
267
+ # Share capital, reserves, borrowings, etc.
268
+ result["notes"] = self.identify_note_sections(df)
269
+
270
+ # Special handling for trade receivables aging
271
+ if any("Age wise analysis" in str(cell) for row in df.values for cell in row):
272
+ result["trade_receivables_aging"] = self.parse_trade_receivables_aging(df)
273
+ else:
274
+ # Generic note parsing
275
+ result["notes"] = self.identify_note_sections(df)
276
+
277
+ return result
278
+ except Exception as e:
279
+ logger.error(f"Error processing {file_path}: {e}")
280
+ return {
281
+ "file_name": os.path.basename(file_path),
282
+ "error": str(e),
283
+ "processing_date": datetime.now().isoformat()
284
+ }
285
+
286
+ def process_all_csvs(self) -> Dict[str, Any]:
287
+ """
288
+ Process all CSV files and create meaningful financial JSON.
289
+ Returns the structured financial data.
290
+ """
291
+ if not os.path.exists(self.csv_folder_path):
292
+ logger.error(f"Folder {self.csv_folder_path} not found")
293
+ return {"error": f"Folder {self.csv_folder_path} not found"}
294
+
295
+ csv_files = [f for f in os.listdir(self.csv_folder_path) if f.endswith('.csv')]
296
+
297
+ if not csv_files:
298
+ logger.error(f"No CSV files found in {self.csv_folder_path}")
299
+ return {"error": f"No CSV files found in {self.csv_folder_path}"}
300
+
301
+ financial_data = FinancialData(
302
+ processing_summary={
303
+ "total_files": len(csv_files),
304
+ "processing_date": datetime.now().isoformat(),
305
+ "processed_files": []
306
+ }
307
+ )
308
+
309
+ # Process each file
310
+ for csv_file in csv_files:
311
+ file_path = os.path.join(self.csv_folder_path, csv_file)
312
+ file_data = self.process_single_csv(file_path)
313
+
314
+ if "error" not in file_data:
315
+ financial_data.processing_summary["processed_files"].append(csv_file)
316
+
317
+ # Organize data by financial statement categories
318
+ if "notes" in file_data:
319
+ for note_title, note_data in file_data["notes"].items():
320
+ if "Share capital" in note_title:
321
+ financial_data.share_capital = note_data
322
+ elif "Reserves and surplus" in note_title:
323
+ financial_data.reserves_and_surplus = note_data
324
+ elif "borrowings" in note_title.lower():
325
+ financial_data.borrowings[note_title] = note_data
326
+ elif any(x in note_title.lower() for x in ["payables", "liabilities", "provisions"]):
327
+ financial_data.current_liabilities[note_title] = note_data
328
+ elif any(x in note_title.lower() for x in ["receivables", "cash", "inventories"]):
329
+ financial_data.current_assets[note_title] = note_data
330
+ elif any(x in note_title.lower() for x in ["loans", "advances"]):
331
+ financial_data.loans_and_advances[note_title] = note_data
332
+ else:
333
+ financial_data.other_data[note_title] = note_data
334
+
335
+ if "fixed_assets" in file_data:
336
+ financial_data.fixed_assets = file_data["fixed_assets"]
337
+
338
+ if "trade_receivables_aging" in file_data:
339
+ financial_data.current_assets["trade_receivables_aging"] = file_data["trade_receivables_aging"]
340
+
341
+ return {"company_financial_data": financial_data.dict()}
342
+
343
+ def save_to_json(self, output_path: str = settings.output_json) -> str:
344
+ """
345
+ Process all CSVs and save meaningful financial JSON.
346
+ Returns the output file path.
347
+ """
348
+ financial_data = self.process_all_csvs()
349
+
350
+ with open(output_path, 'w', encoding='utf-8') as f:
351
+ json.dump(financial_data, f, indent=2, ensure_ascii=False, default=str)
352
+
353
+ logger.info(f"Clean financial JSON created: {output_path}")
354
+ return output_path
355
+
356
+ # Usage
357
+ if __name__ == "__main__":
358
+ mapper = FinancialCSVMapper(settings.csv_folder_path)
359
+ output_file = mapper.save_to_json(settings.output_json)
360
+ logger.info(f"Clean financial JSON created: {output_file}")
pnlbs/csv_json_pnl.py ADDED
@@ -0,0 +1,360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import json
3
+ import os
4
+ import re
5
+ import logging
6
+ from datetime import datetime
7
+ from typing import Dict, List, Any, Optional, Union
8
+ from pydantic import BaseModel, Field, ValidationError
9
+ from pydantic_settings import BaseSettings
10
+
11
+ # Configure logging
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ class Settings(BaseSettings):
16
+ """Settings for CSV to JSON conversion, loaded from environment variables or .env file."""
17
+ csv_folder_path: str = Field(default="csv_notes_pnl", env="CSV_FOLDER_PATH")
18
+ output_json: str = Field(default="clean_financial_data_pnl.json", env="OUTPUT_JSON")
19
+
20
+ settings = Settings()
21
+
22
+ class NoteSection(BaseModel):
23
+ title: str
24
+ data: Dict[str, Any]
25
+
26
+ class FixedAssetData(BaseModel):
27
+ gross_carrying_value: Dict[str, Optional[Union[float, int]]]
28
+ accumulated_depreciation: Dict[str, Optional[Union[float, int]]]
29
+ net_carrying_value: Dict[str, Optional[Union[float, int]]]
30
+
31
+ class FinancialData(BaseModel):
32
+ processing_summary: Dict[str, Any]
33
+ share_capital: Dict[str, Any] = Field(default_factory=dict)
34
+ reserves_and_surplus: Dict[str, Any] = Field(default_factory=dict)
35
+ borrowings: Dict[str, Any] = Field(default_factory=dict)
36
+ current_liabilities: Dict[str, Any] = Field(default_factory=dict)
37
+ fixed_assets: Dict[str, Any] = Field(default_factory=dict)
38
+ current_assets: Dict[str, Any] = Field(default_factory=dict)
39
+ loans_and_advances: Dict[str, Any] = Field(default_factory=dict)
40
+ other_data: Dict[str, Any] = Field(default_factory=dict)
41
+
42
+ class FinancialCSVMapper:
43
+ def __init__(self, csv_folder_path: str = settings.csv_folder_path):
44
+ self.csv_folder_path = csv_folder_path
45
+
46
+ def clean_value(self, value: Any) -> Optional[Union[float, int, str]]:
47
+ """
48
+ Clean and convert values appropriately.
49
+ Returns None for empty or NaN values.
50
+ """
51
+ if pd.isna(value) or value == '':
52
+ return None
53
+ value_str = str(value).strip()
54
+ cleaned_num = re.sub(r'[,\s₹]', '', value_str)
55
+ try:
56
+ if '.' in cleaned_num:
57
+ return float(cleaned_num)
58
+ else:
59
+ return int(cleaned_num)
60
+ except (ValueError, TypeError):
61
+ return value_str
62
+
63
+ def identify_note_sections(self, df: pd.DataFrame) -> Dict[str, Dict]:
64
+ """
65
+ Identify and extract note sections (e.g., 2. Share capital, 3. Reserves).
66
+ Returns a dictionary of section title to parsed data.
67
+ """
68
+ sections = {}
69
+ current_section = None
70
+ current_data = []
71
+
72
+ for idx, row in df.iterrows():
73
+ first_col = str(row.iloc[0]) if not pd.isna(row.iloc[0]) else ""
74
+ # Check if this is a new section header (starts with number and dot)
75
+ if re.match(r'^\d+\.?\s+[A-Za-z]', first_col):
76
+ # Save previous section
77
+ if current_section and current_data:
78
+ sections[current_section] = self.parse_section_data(current_data)
79
+
80
+ # Start new section
81
+ current_section = first_col.strip()
82
+ current_data = []
83
+ else:
84
+ # Add row to current section
85
+ if current_section:
86
+ row_data = [self.clean_value(cell) for cell in row]
87
+ if any(cell is not None for cell in row_data): # Skip empty rows
88
+ current_data.append(row_data)
89
+
90
+ # Handle last section
91
+ if current_section and current_data:
92
+ sections[current_section] = self.parse_section_data(current_data)
93
+
94
+ return sections
95
+
96
+ def parse_section_data(self, rows: List[List[Any]]) -> Dict:
97
+ """
98
+ Parse section data into a meaningful structure.
99
+ Returns a dictionary mapping keys to values or date-mapped values.
100
+ """
101
+ if not rows:
102
+ return {}
103
+
104
+ section_data = {}
105
+
106
+ # Find date headers (usually in first or second row)
107
+ date_row = None
108
+ for i, row in enumerate(rows[:3]):
109
+ for cell in row:
110
+ if cell and isinstance(cell, str) and re.search(r'\d{4}-\d{2}-\d{2}', str(cell)):
111
+ date_row = i
112
+ break
113
+ if date_row is not None:
114
+ break
115
+
116
+ # Extract dates if found
117
+ dates = []
118
+ if date_row is not None:
119
+ dates = [cell for cell in rows[date_row] if cell and re.search(r'\d{4}-\d{2}-\d{2}', str(cell))]
120
+
121
+ # Process data rows
122
+ for row in rows:
123
+ if not row or not row[0]:
124
+ continue
125
+
126
+ key = str(row[0]).strip()
127
+
128
+ # Skip header/date rows
129
+ if date_row is not None and row == rows[date_row]:
130
+ continue
131
+ if any(date in str(cell) for cell in row for date in dates if date):
132
+ continue
133
+
134
+ # Extract values (non-None values after the key)
135
+ values = [cell for cell in row[1:] if cell is not None]
136
+
137
+ if values:
138
+ if len(values) == 1:
139
+ section_data[key] = values[0]
140
+ else:
141
+ # If we have dates, map values to dates
142
+ if dates and len(values) <= len(dates):
143
+ section_data[key] = {dates[i]: values[i] for i in range(len(values))}
144
+ else:
145
+ section_data[key] = values
146
+
147
+ # Add dates to metadata if found
148
+ if dates:
149
+ section_data["_metadata"] = {"reporting_dates": dates}
150
+
151
+ return section_data
152
+
153
+ def parse_fixed_assets(self, df: pd.DataFrame) -> Dict[str, Any]:
154
+ """
155
+ Parse fixed assets table (Note 9) with proper structure.
156
+ Returns a dictionary with tangible, intangible, and totals.
157
+ """
158
+ fixed_assets = {
159
+ "tangible_assets": {},
160
+ "intangible_assets": {},
161
+ "totals": {}
162
+ }
163
+
164
+ current_category = None
165
+
166
+ for idx, row in df.iterrows():
167
+ first_col = self.clean_value(row.iloc[0])
168
+
169
+ # Skip header rows
170
+ if not first_col or "Particulars" in str(first_col) or "Gross Carrying" in str(first_col):
171
+ continue
172
+
173
+ # Identify categories
174
+ if "Tangible Assets" in str(first_col):
175
+ current_category = "tangible"
176
+ continue
177
+ elif "Intangible Assets" in str(first_col):
178
+ current_category = "intangible"
179
+ continue
180
+ elif "Total" in str(first_col) or "Grand Total" in str(first_col):
181
+ current_category = "totals"
182
+
183
+ # Extract asset data
184
+ if current_category and len(row) > 1:
185
+ asset_name = str(first_col).strip()
186
+
187
+ # Remove numbering (1, 2, 3, etc.)
188
+ asset_name = re.sub(r'^\d+\s*', '', asset_name)
189
+
190
+ asset_data = FixedAssetData(
191
+ gross_carrying_value={
192
+ "opening": self.clean_value(row.iloc[2]) if len(row) > 2 else None,
193
+ "additions": self.clean_value(row.iloc[3]) if len(row) > 3 else None,
194
+ "deletions": self.clean_value(row.iloc[4]) if len(row) > 4 else None,
195
+ "closing": self.clean_value(row.iloc[5]) if len(row) > 5 else None
196
+ },
197
+ accumulated_depreciation={
198
+ "opening": self.clean_value(row.iloc[6]) if len(row) > 6 else None,
199
+ "for_the_year": self.clean_value(row.iloc[7]) if len(row) > 7 else None,
200
+ "deletions": self.clean_value(row.iloc[8]) if len(row) > 8 else None,
201
+ "closing": self.clean_value(row.iloc[9]) if len(row) > 9 else None
202
+ },
203
+ net_carrying_value={
204
+ "closing": self.clean_value(row.iloc[10]) if len(row) > 10 else None,
205
+ "opening": self.clean_value(row.iloc[11]) if len(row) > 11 else None
206
+ }
207
+ )
208
+
209
+ if current_category == "tangible":
210
+ fixed_assets["tangible_assets"][asset_name] = asset_data.dict()
211
+ elif current_category == "intangible":
212
+ fixed_assets["intangible_assets"][asset_name] = asset_data.dict()
213
+ elif current_category == "totals":
214
+ fixed_assets["totals"][asset_name] = asset_data.dict()
215
+
216
+ return fixed_assets
217
+
218
+ def parse_trade_receivables_aging(self, df: pd.DataFrame) -> Dict[str, Any]:
219
+ """
220
+ Parse trade receivables aging analysis.
221
+ Returns a dictionary of year to aging buckets.
222
+ """
223
+ aging_data = {}
224
+ current_year = None
225
+
226
+ for idx, row in df.iterrows():
227
+ first_col = str(row.iloc[0]) if not pd.isna(row.iloc[0]) else ""
228
+
229
+ # Identify year sections
230
+ if "2024" in first_col:
231
+ current_year = "2024"
232
+ continue
233
+ elif "2023" in first_col:
234
+ current_year = "2023"
235
+ continue
236
+
237
+ # Parse aging buckets
238
+ if current_year and "Considered good" in first_col:
239
+ aging_data[current_year] = {
240
+ "0_6_months": self.clean_value(row.iloc[1]) if len(row) > 1 else None,
241
+ "6_12_months": self.clean_value(row.iloc[2]) if len(row) > 2 else None,
242
+ "1_2_years": self.clean_value(row.iloc[3]) if len(row) > 3 else None,
243
+ "2_3_years": self.clean_value(row.iloc[4]) if len(row) > 4 else None,
244
+ "more_than_3_years": self.clean_value(row.iloc[5]) if len(row) > 5 else None,
245
+ "total": self.clean_value(row.iloc[6]) if len(row) > 6 else None
246
+ }
247
+
248
+ return aging_data
249
+
250
+ def process_single_csv(self, file_path: str) -> Dict[str, Any]:
251
+ """
252
+ Process a single CSV file with intelligent parsing.
253
+ Returns a dictionary of processed data.
254
+ """
255
+ try:
256
+ df = pd.read_csv(file_path, encoding='utf-8')
257
+ filename = os.path.basename(file_path)
258
+ result = {
259
+ "file_name": filename,
260
+ "processing_date": datetime.now().isoformat()
261
+ }
262
+ # Special handling for different note types
263
+ if "Note_9" in filename:
264
+ # Fixed assets
265
+ result["fixed_assets"] = self.parse_fixed_assets(df)
266
+ elif "Note_2_to_8" in filename or "Note_10_to_15" in filename:
267
+ # Share capital, reserves, borrowings, etc.
268
+ result["notes"] = self.identify_note_sections(df)
269
+
270
+ # Special handling for trade receivables aging
271
+ if any("Age wise analysis" in str(cell) for row in df.values for cell in row):
272
+ result["trade_receivables_aging"] = self.parse_trade_receivables_aging(df)
273
+ else:
274
+ # Generic note parsing
275
+ result["notes"] = self.identify_note_sections(df)
276
+
277
+ return result
278
+ except Exception as e:
279
+ logger.error(f"Error processing {file_path}: {e}")
280
+ return {
281
+ "file_name": os.path.basename(file_path),
282
+ "error": str(e),
283
+ "processing_date": datetime.now().isoformat()
284
+ }
285
+
286
+ def process_all_csvs(self) -> Dict[str, Any]:
287
+ """
288
+ Process all CSV files and create meaningful financial JSON.
289
+ Returns the structured financial data.
290
+ """
291
+ if not os.path.exists(self.csv_folder_path):
292
+ logger.error(f"Folder {self.csv_folder_path} not found")
293
+ return {"error": f"Folder {self.csv_folder_path} not found"}
294
+
295
+ csv_files = [f for f in os.listdir(self.csv_folder_path) if f.endswith('.csv')]
296
+
297
+ if not csv_files:
298
+ logger.error(f"No CSV files found in {self.csv_folder_path}")
299
+ return {"error": f"No CSV files found in {self.csv_folder_path}"}
300
+
301
+ financial_data = FinancialData(
302
+ processing_summary={
303
+ "total_files": len(csv_files),
304
+ "processing_date": datetime.now().isoformat(),
305
+ "processed_files": []
306
+ }
307
+ )
308
+
309
+ # Process each file
310
+ for csv_file in csv_files:
311
+ file_path = os.path.join(self.csv_folder_path, csv_file)
312
+ file_data = self.process_single_csv(file_path)
313
+
314
+ if "error" not in file_data:
315
+ financial_data.processing_summary["processed_files"].append(csv_file)
316
+
317
+ # Organize data by financial statement categories
318
+ if "notes" in file_data:
319
+ for note_title, note_data in file_data["notes"].items():
320
+ if "Share capital" in note_title:
321
+ financial_data.share_capital = note_data
322
+ elif "Reserves and surplus" in note_title:
323
+ financial_data.reserves_and_surplus = note_data
324
+ elif "borrowings" in note_title.lower():
325
+ financial_data.borrowings[note_title] = note_data
326
+ elif any(x in note_title.lower() for x in ["payables", "liabilities", "provisions"]):
327
+ financial_data.current_liabilities[note_title] = note_data
328
+ elif any(x in note_title.lower() for x in ["receivables", "cash", "inventories"]):
329
+ financial_data.current_assets[note_title] = note_data
330
+ elif any(x in note_title.lower() for x in ["loans", "advances"]):
331
+ financial_data.loans_and_advances[note_title] = note_data
332
+ else:
333
+ financial_data.other_data[note_title] = note_data
334
+
335
+ if "fixed_assets" in file_data:
336
+ financial_data.fixed_assets = file_data["fixed_assets"]
337
+
338
+ if "trade_receivables_aging" in file_data:
339
+ financial_data.current_assets["trade_receivables_aging"] = file_data["trade_receivables_aging"]
340
+
341
+ return {"company_financial_data": financial_data.dict()}
342
+
343
+ def save_to_json(self, output_path: str = settings.output_json) -> str:
344
+ """
345
+ Process all CSVs and save meaningful financial JSON.
346
+ Returns the output file path.
347
+ """
348
+ financial_data = self.process_all_csvs()
349
+
350
+ with open(output_path, 'w', encoding='utf-8') as f:
351
+ json.dump(financial_data, f, indent=2, ensure_ascii=False, default=str)
352
+
353
+ logger.info(f"Clean financial JSON created: {output_path}")
354
+ return output_path
355
+
356
+ # Usage
357
+ if __name__ == "__main__":
358
+ mapper = FinancialCSVMapper(settings.csv_folder_path)
359
+ output_file = mapper.save_to_json(settings.output_json)
360
+ logger.info(f"Clean financial JSON created: {output_file}")
pnlbs/pnl_note.py ADDED
@@ -0,0 +1,353 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import logging
4
+ from openpyxl import Workbook
5
+ from openpyxl.styles import Font, Border, Side, Alignment
6
+ from typing import Dict, List, Tuple, Any, Optional
7
+ from pydantic import BaseModel, Field, ValidationError
8
+ from pydantic_settings import BaseSettings
9
+
10
+ # Configure logging
11
+ logging.basicConfig(level=logging.INFO)
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class Settings(BaseSettings):
15
+ """Settings for P&L generation, loaded from environment variables or .env file."""
16
+ json_files: List[str] = Field(default_factory=lambda: [
17
+ "clean_financial_data_pnl.json",
18
+ "pnl_notes.json"
19
+ ], env="PNL_JSON_FILES")
20
+ output_file: str = Field(default="pnl_statement.xlsx", env="PNL_OUTPUT_FILE")
21
+
22
+ settings = Settings()
23
+
24
+ class FinancialItem(BaseModel):
25
+ name: str
26
+ values: List[float] = Field(default_factory=list)
27
+
28
+ class FinancialDataModel(BaseModel):
29
+ other_data: Dict[str, Any] = Field(default_factory=dict)
30
+
31
+ class PnLGenerator:
32
+ def __init__(self, json_file_path: str = settings.json_files[0]):
33
+ """Initialize the P&L generator with JSON file path."""
34
+ self.json_file_path = json_file_path
35
+ self.financial_data: Dict[str, Any] = {}
36
+
37
+ def load_financial_data(self) -> bool:
38
+ """Load financial data from JSON file."""
39
+ try:
40
+ logger.info(f"Loading financial data from: {self.json_file_path}")
41
+ with open(self.json_file_path, 'r', encoding='utf-8') as f:
42
+ data = json.load(f)
43
+ # Handle different JSON structures flexibly
44
+ if "company_financial_data" in data:
45
+ self.financial_data = data["company_financial_data"].get("other_data", {})
46
+ elif "other_data" in data:
47
+ self.financial_data = data["other_data"]
48
+ else:
49
+ self.financial_data = data
50
+ logger.info(f"Loaded data for {len(self.financial_data)} financial items")
51
+ return True
52
+ except FileNotFoundError:
53
+ logger.error(f"File not found: {self.json_file_path}")
54
+ return False
55
+ except json.JSONDecodeError as e:
56
+ logger.error(f"Invalid JSON format: {str(e)}")
57
+ return False
58
+ except Exception as e:
59
+ logger.error(f"Error loading data: {str(e)}")
60
+ return False
61
+
62
+ def extract_values(self, item_key: str) -> Tuple[float, float]:
63
+ """Extract 2024 and 2023 values from financial data."""
64
+ if item_key not in self.financial_data:
65
+ logger.warning(f"{item_key} not found in data")
66
+ return 0.0, 0.0
67
+ item_data = self.financial_data[item_key]
68
+ total_2024 = 0.0
69
+ total_2023 = 0.0
70
+ if isinstance(item_data, dict):
71
+ for category, values in item_data.items():
72
+ if isinstance(values, list) and len(values) >= 2:
73
+ total_2024 += float(values[0] or 0)
74
+ total_2023 += float(values[1] or 0)
75
+ elif isinstance(values, (int, float)):
76
+ total_2024 += float(values)
77
+ elif isinstance(item_data, list) and len(item_data) >= 2:
78
+ total_2024 = float(item_data[0] or 0)
79
+ total_2023 = float(item_data[1] or 0)
80
+ return total_2024, total_2023
81
+
82
+ def get_revenue_data(self) -> Tuple[float, float]:
83
+ """Extract revenue from operations data."""
84
+ return self.extract_values("16. Revenue from Operations")
85
+
86
+ def get_other_income_data(self) -> Tuple[float, float]:
87
+ """Extract other income data."""
88
+ return self.extract_values("17. Other income")
89
+
90
+ def get_cost_materials_data(self) -> Tuple[float, float]:
91
+ """Extract cost of materials consumed data."""
92
+ item_key = "18. Cost of materials consumed"
93
+ if item_key not in self.financial_data:
94
+ logger.warning(f"{item_key} not found in data")
95
+ return 0.0, 0.0
96
+ item_data = self.financial_data[item_key]
97
+ if "Cost of materials consumed" in item_data:
98
+ values = item_data["Cost of materials consumed"]
99
+ if isinstance(values, list) and len(values) >= 2:
100
+ return float(values[0] or 0), float(values[1] or 0)
101
+ # Fallback: calculate from opening stock + purchases - closing stock
102
+ opening_2024 = opening_2023 = 0.0
103
+ purchases_2024 = purchases_2023 = 0.0
104
+ closing_2024 = closing_2023 = 0.0
105
+ if "Opening stock" in item_data:
106
+ values = item_data["Opening stock"]
107
+ if isinstance(values, list) and len(values) >= 2:
108
+ opening_2024, opening_2023 = float(values[0] or 0), float(values[1] or 0)
109
+ if "Add: Purchases" in item_data:
110
+ values = item_data["Add: Purchases"]
111
+ if isinstance(values, list) and len(values) >= 2:
112
+ purchases_2024, purchases_2023 = float(values[0] or 0), float(values[1] or 0)
113
+ if "Less: Closing stock" in item_data:
114
+ values = item_data["Less: Closing stock"]
115
+ if isinstance(values, list) and len(values) >= 2:
116
+ closing_2024, closing_2023 = float(values[0] or 0), float(values[1] or 0)
117
+ cost_2024 = opening_2024 + purchases_2024 - closing_2024
118
+ cost_2023 = opening_2023 + purchases_2023 - closing_2023
119
+ return cost_2024, cost_2023
120
+
121
+ def get_employee_expense_data(self) -> Tuple[float, float]:
122
+ """Extract employee benefit expense data."""
123
+ return self.extract_values("19. Employee benefit expense")
124
+
125
+ def get_other_expenses_data(self) -> Tuple[float, float]:
126
+ """Extract other expenses data."""
127
+ return self.extract_values("20. Other expenses")
128
+
129
+ def get_depreciation_data(self) -> Tuple[float, float]:
130
+ """Extract depreciation and amortisation data."""
131
+ return self.extract_values("21. Depreciation and amortisation expense")
132
+
133
+ def get_loss_on_sale_data(self) -> Tuple[float, float]:
134
+ """Extract loss on sale of assets data."""
135
+ return self.extract_values("22. Loss on sale of assets")
136
+
137
+ def get_finance_costs_data(self) -> Tuple[float, float]:
138
+ """Extract finance costs data."""
139
+ return self.extract_values("23. Finance costs")
140
+
141
+ def format_currency(self, value: float) -> str:
142
+ """Format currency with commas."""
143
+ if value == 0:
144
+ return ""
145
+ return f"{value:,.2f}"
146
+
147
+ def generate_pnl_statement(self, output_file: str = settings.output_file) -> bool:
148
+ """Generate comprehensive P&L statement Excel file."""
149
+ if not self.financial_data:
150
+ logger.error("No financial data loaded. Please load data first.")
151
+ return False
152
+ wb = Workbook()
153
+ ws = wb.active
154
+ ws.title = "Profit and Loss Statement"
155
+ title_font = Font(bold=True, size=12)
156
+ header_font = Font(bold=True, size=10)
157
+ normal_font = Font(size=10)
158
+ bold_font = Font(bold=True, size=10)
159
+ thin_border = Border(
160
+ left=Side(style="thin"), right=Side(style="thin"),
161
+ top=Side(style="thin"), bottom=Side(style="thin")
162
+ )
163
+ top_bottom_border = Border(
164
+ top=Side(style="thin"), bottom=Side(style="thin")
165
+ )
166
+ center_align = Alignment(horizontal="center", vertical="center")
167
+ left_align = Alignment(horizontal="left", vertical="center")
168
+ right_align = Alignment(horizontal="right", vertical="center")
169
+ ws.column_dimensions["A"].width = 45
170
+ ws.column_dimensions["B"].width = 8
171
+ ws.column_dimensions["C"].width = 15
172
+ ws.column_dimensions["D"].width = 15
173
+ row = 1
174
+ ws.merge_cells("A1:D1")
175
+ ws["A1"] = "Statement of Profit and Loss for the year ended March 31, 2024"
176
+ ws["A1"].font = title_font
177
+ ws["A1"].alignment = center_align
178
+ ws["A1"].border = top_bottom_border
179
+ row += 2
180
+ ws["C3"] = "In Lakhs"
181
+ ws["C3"].font = normal_font
182
+ ws["C3"].alignment = right_align
183
+ row += 1
184
+ headers = ["", "Notes", "Year ended March 31, 2024", "Year ended March 31, 2023"]
185
+ for col, header in enumerate(headers, 1):
186
+ cell = ws.cell(row=row, column=col)
187
+ cell.value = header
188
+ cell.font = header_font
189
+ cell.border = top_bottom_border
190
+ cell.alignment = center_align if col > 2 else left_align
191
+ row += 1
192
+
193
+ def add_data_row(description: str, note_ref: str, val_2024: float, val_2023: float,
194
+ is_bold: bool = False, is_section_header: bool = False) -> None:
195
+ """Add a data row with proper formatting."""
196
+ nonlocal row
197
+ cell_a = ws.cell(row=row, column=1)
198
+ cell_a.value = description
199
+ cell_a.font = bold_font if (is_bold or is_section_header) else normal_font
200
+ cell_a.alignment = left_align
201
+ if not is_section_header:
202
+ cell_a.border = thin_border
203
+ cell_b = ws.cell(row=row, column=2)
204
+ cell_b.value = note_ref if note_ref else ""
205
+ cell_b.font = normal_font
206
+ cell_b.alignment = center_align
207
+ if not is_section_header:
208
+ cell_b.border = thin_border
209
+ cell_c = ws.cell(row=row, column=3)
210
+ cell_c.value = self.format_currency(val_2024)
211
+ cell_c.font = bold_font if is_bold else normal_font
212
+ cell_c.alignment = right_align
213
+ if not is_section_header:
214
+ cell_c.border = thin_border
215
+ cell_d = ws.cell(row=row, column=4)
216
+ cell_d.value = self.format_currency(val_2023)
217
+ cell_d.font = bold_font if is_bold else normal_font
218
+ cell_d.alignment = right_align
219
+ if not is_section_header:
220
+ cell_d.border = thin_border
221
+ row += 1
222
+
223
+ logger.info("Extracting financial data...")
224
+ revenue_2024, revenue_2023 = self.get_revenue_data()
225
+ other_income_2024, other_income_2023 = self.get_other_income_data()
226
+ materials_2024, materials_2023 = self.get_cost_materials_data()
227
+ employee_2024, employee_2023 = self.get_employee_expense_data()
228
+ other_exp_2024, other_exp_2023 = self.get_other_expenses_data()
229
+ depreciation_2024, depreciation_2023 = self.get_depreciation_data()
230
+ loss_sale_2024, loss_sale_2023 = self.get_loss_on_sale_data()
231
+ finance_2024, finance_2023 = self.get_finance_costs_data()
232
+
233
+ # INCOME SECTION
234
+ add_data_row("Income", "", 0, 0, is_section_header=True)
235
+ add_data_row("Revenue from operations (net)", "16", revenue_2024, revenue_2023)
236
+ add_data_row("Other income", "17", other_income_2024, other_income_2023)
237
+ total_revenue_2024 = revenue_2024 + other_income_2024
238
+ total_revenue_2023 = revenue_2023 + other_income_2023
239
+ add_data_row("Total revenue (I)", "", total_revenue_2024, total_revenue_2023, is_bold=True)
240
+
241
+ # EXPENSES SECTION
242
+ add_data_row("Expenses", "", 0, 0, is_section_header=True)
243
+ add_data_row("Cost of materials consumed", "18", materials_2024, materials_2023)
244
+ add_data_row("Employee benefit expense", "19", employee_2024, employee_2023)
245
+ add_data_row("Other expenses", "20", other_exp_2024, other_exp_2023)
246
+ add_data_row("Depreciation and amortisation expense", "21", depreciation_2024, depreciation_2023)
247
+ add_data_row("Loss on sale of assets & investments", "22", loss_sale_2024, loss_sale_2023)
248
+ add_data_row("Finance costs", "23", finance_2024, finance_2023)
249
+ total_expenses_2024 = materials_2024 + employee_2024 + other_exp_2024 + depreciation_2024 + loss_sale_2024 + finance_2024
250
+ total_expenses_2023 = materials_2023 + employee_2023 + other_exp_2023 + depreciation_2023 + loss_sale_2023 + finance_2023
251
+ add_data_row("Total Expenses (II)", "", total_expenses_2024, total_expenses_2023, is_bold=True)
252
+
253
+ # Profit before tax
254
+ profit_before_tax_2024 = total_revenue_2024 - total_expenses_2024
255
+ profit_before_tax_2023 = total_revenue_2023 - total_expenses_2023
256
+ add_data_row("Profit before Tax (I) - (II)", "", profit_before_tax_2024, profit_before_tax_2023, is_bold=True)
257
+
258
+ # Tax Expense section (placeholders)
259
+ add_data_row("IV. TAX EXPENSE", "", 0, 0, is_section_header=True)
260
+ add_data_row("Current Tax", "", 0.0, 0.0)
261
+ add_data_row("Deferred Tax Liability/(Asset)", "", 0.0, 0.0)
262
+ add_data_row("Income Tax relating to Prior Year", "", 0.0, 0.0)
263
+ add_data_row("MAT Credit (Entitlement)/Utilisation", "", 0.0, 0.0)
264
+ add_data_row("Total Tax Expense (IV)", "", 0.0, 0.0, is_bold=True)
265
+
266
+ # Profit after Tax (assuming no tax for now)
267
+ profit_after_tax_2024 = profit_before_tax_2024
268
+ profit_after_tax_2023 = profit_before_tax_2023
269
+ add_data_row("Profit After Tax (III - IV)", "", profit_after_tax_2024, profit_after_tax_2023, is_bold=True)
270
+
271
+ # Earnings per share section (placeholders)
272
+ add_data_row("Earnings per share", "", 0, 0, is_section_header=True)
273
+ add_data_row("Basic and diluted", "30", 0.0, 0.0)
274
+ add_data_row("Nominal value", "", 10.0, 10.0)
275
+ add_data_row("Weighted average number of equity shares", "30", 0.0, 0.0)
276
+
277
+ # Footer
278
+ row += 2
279
+ ws.merge_cells(f"A{row}:D{row}")
280
+ ws[f"A{row}"] = "The accompanying notes are an integral part of the financial statements"
281
+ ws[f"A{row}"].font = normal_font
282
+ ws[f"A{row}"].alignment = left_align
283
+
284
+ # Save the file
285
+ try:
286
+ wb.save(output_file)
287
+ logger.info(f"P&L Statement generated successfully: {output_file}")
288
+ self.print_financial_summary(
289
+ total_revenue_2024, total_revenue_2023,
290
+ total_expenses_2024, total_expenses_2023,
291
+ profit_before_tax_2024, profit_before_tax_2023,
292
+ profit_after_tax_2024, profit_after_tax_2023
293
+ )
294
+ return True
295
+ except PermissionError:
296
+ logger.error(f"Permission Error: Cannot save to {output_file}")
297
+ fallback_file = os.path.join(os.path.expanduser("~"), "Desktop", "pnl_statement_fallback.xlsx")
298
+ try:
299
+ wb.save(fallback_file)
300
+ logger.info(f"P&L Statement saved to: {fallback_file}")
301
+ return True
302
+ except Exception as e:
303
+ logger.error(f"Failed to save: {str(e)}")
304
+ return False
305
+ except Exception as e:
306
+ logger.error(f"Error saving file: {str(e)}")
307
+ return False
308
+
309
+ def print_financial_summary(self, total_revenue_2024: float, total_revenue_2023: float,
310
+ total_expenses_2024: float, total_expenses_2023: float,
311
+ profit_before_tax_2024: float, profit_before_tax_2023: float,
312
+ profit_after_tax_2024: float, profit_after_tax_2023: float) -> None:
313
+ """Log financial summary."""
314
+ logger.info("=" * 60)
315
+ logger.info("FINANCIAL SUMMARY")
316
+ logger.info("=" * 60)
317
+ logger.info(f"Total Revenue 2024: Rs.{total_revenue_2024:>12,.2f} Lakhs")
318
+ logger.info(f"Total Revenue 2023: Rs.{total_revenue_2023:>12,.2f} Lakhs")
319
+ logger.info(f"Total Expenses 2024: Rs.{total_expenses_2024:>12,.2f} Lakhs")
320
+ logger.info(f"Total Expenses 2023: Rs.{total_expenses_2023:>12,.2f} Lakhs")
321
+ logger.info(f"Profit Before Tax 2024: Rs.{profit_before_tax_2024:>12,.2f} Lakhs")
322
+ logger.info(f"Profit Before Tax 2023: Rs.{profit_before_tax_2023:>12,.2f} Lakhs")
323
+ logger.info(f"Profit After Tax 2024: Rs.{profit_after_tax_2024:>12,.2f} Lakhs")
324
+ logger.info(f"Profit After Tax 2023: Rs.{profit_after_tax_2023:>12,.2f} Lakhs")
325
+ if total_revenue_2023 > 0:
326
+ growth_rate = ((total_revenue_2024 - total_revenue_2023) / total_revenue_2023) * 100
327
+ logger.info(f"Revenue Growth Rate: {growth_rate:>12.2f}%")
328
+
329
+ def main() -> None:
330
+ """Main function to run the P&L generator."""
331
+ logger.info("P&L STATEMENT GENERATOR FROM JSON")
332
+ logger.info("=" * 50)
333
+ json_file: Optional[str] = None
334
+ for file in settings.json_files:
335
+ if os.path.exists(file):
336
+ json_file = file
337
+ break
338
+ if not json_file:
339
+ json_file = input("Enter the path to your JSON file: ").strip()
340
+ generator = PnLGenerator(json_file)
341
+ if generator.load_financial_data():
342
+ output_path = settings.output_file
343
+ logger.info(f"Output file: {output_path}")
344
+ if generator.generate_pnl_statement(output_path):
345
+ logger.info("P&L STATEMENT GENERATION COMPLETED SUCCESSFULLY!")
346
+ logger.info(f"Output file: {output_path}")
347
+ else:
348
+ logger.error("Failed to generate P&L statement")
349
+ else:
350
+ logger.error("Failed to load financial data")
351
+
352
+ if __name__ == "__main__":
353
+ main()
pnlbs/sircodebs.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pandas as pd
3
+ import sys
4
+ import logging
5
+ from typing import Optional
6
+ from pydantic import BaseModel, Field
7
+ from pydantic_settings import BaseSettings
8
+
9
+ # Ensure stdout encoding for Unicode
10
+ sys.stdout.reconfigure(encoding='utf-8')
11
+
12
+ # Configure logging
13
+ logging.basicConfig(level=logging.INFO)
14
+ logger = logging.getLogger(__name__)
15
+
16
+ class Settings(BaseSettings):
17
+ """Settings for Balance Sheet CSV extraction, loaded from environment variables or .env file."""
18
+ excel_file_path: str = Field(default="In Lakhs BS_FY 23-24 V5 - Final.xlsx", env="BS_EXCEL_FILE_PATH")
19
+ output_folder: str = Field(default="csv_notes_bs", env="BS_OUTPUT_FOLDER")
20
+ note_2_8_sheet: str = Field(default="Note 2 - 8", env="BS_NOTE_2_8_SHEET")
21
+ note_9_sheet: str = Field(default="Note 9", env="BS_NOTE_9_SHEET")
22
+ note_10_15_sheet: str = Field(default="Note 10-15", env="BS_NOTE_10_15_SHEET")
23
+ skiprows: int = Field(default=3, env="BS_SKIPROWS")
24
+
25
+ settings = Settings()
26
+
27
+ class NoteCSVInfo(BaseModel):
28
+ name: str
29
+ rows: int
30
+
31
+ def clean_note(sheet_name: str, skiprows: int = settings.skiprows) -> pd.DataFrame:
32
+ """
33
+ Parse and clean a sheet from the Excel file.
34
+ Drops empty rows and columns, resets index.
35
+ """
36
+ df = xls.parse(sheet_name, skiprows=skiprows)
37
+ df = df.dropna(how='all').dropna(axis=1, how='all').reset_index(drop=True)
38
+ return df
39
+
40
+ def export_note_to_csv(df: pd.DataFrame, filename: str, output_folder: str) -> NoteCSVInfo:
41
+ """
42
+ Export DataFrame to CSV and return info.
43
+ """
44
+ output_path = os.path.join(output_folder, filename)
45
+ df.to_csv(output_path, index=False)
46
+ return NoteCSVInfo(name=filename, rows=df.shape[0])
47
+
48
+ def main() -> None:
49
+ """
50
+ Main function to extract notes from Excel and export as CSVs.
51
+ """
52
+ logger.info("Loading Excel file: %s", settings.excel_file_path)
53
+ global xls
54
+ xls = pd.ExcelFile(settings.excel_file_path)
55
+
56
+ # Clean each sheet
57
+ note_2_8_df = clean_note(settings.note_2_8_sheet, settings.skiprows)
58
+ note_9_df = clean_note(settings.note_9_sheet, settings.skiprows)
59
+ note_10_15_df = clean_note(settings.note_10_15_sheet, settings.skiprows)
60
+
61
+ # Ensure output folder exists
62
+ os.makedirs(settings.output_folder, exist_ok=True)
63
+
64
+ # Export each as CSV in the folder
65
+ info_2_8 = export_note_to_csv(note_2_8_df, "Note_2_to_8_Full.csv", settings.output_folder)
66
+ info_9 = export_note_to_csv(note_9_df, "Note_9_Full.csv", settings.output_folder)
67
+ info_10_15 = export_note_to_csv(note_10_15_df, "Note_10_to_15_Full.csv", settings.output_folder)
68
+
69
+ # Log confirmation and row counts
70
+ logger.info(f"Extracted rows: Note 2–8 = {info_2_8.rows} rows")
71
+ logger.info(f"Extracted rows: Note 9 = {info_9.rows} rows")
72
+ logger.info(f"Extracted rows: Note 10–15 = {info_10_15.rows} rows")
73
+
74
+ if __name__ == "__main__":
75
+ main()
pnlbs/sircodepnl.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pandas as pd
3
+ import logging
4
+ from typing import Optional
5
+ from pydantic import BaseModel, Field
6
+ from pydantic_settings import BaseSettings
7
+
8
+ # Configure logging
9
+ logging.basicConfig(level=logging.INFO)
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class Settings(BaseSettings):
13
+ """Settings for P&L CSV extraction, loaded from environment variables or .env file."""
14
+ excel_file_path: str = Field(default="In Lakhs BS_FY 23-24 V5 - Final.xlsx", env="PNL_EXCEL_FILE_PATH")
15
+ output_folder: str = Field(default="csv_notes_pnl", env="PNL_OUTPUT_FOLDER")
16
+ note_16_23_sheet: str = Field(default="Note 16-23", env="PNL_NOTE_16_23_SHEET")
17
+ skiprows: int = Field(default=3, env="PNL_SKIPROWS")
18
+
19
+ settings = Settings()
20
+
21
+ class NoteCSVInfo(BaseModel):
22
+ name: str
23
+ rows: int
24
+
25
+ def clean_note(sheet_name: str, skiprows: int = settings.skiprows) -> pd.DataFrame:
26
+ """
27
+ Parse and clean a sheet from the Excel file.
28
+ Drops empty rows and columns, resets index.
29
+ """
30
+ xls = pd.ExcelFile(settings.excel_file_path)
31
+ df = xls.parse(sheet_name, skiprows=skiprows)
32
+ df = df.dropna(how='all').dropna(axis=1, how='all').reset_index(drop=True)
33
+ return df
34
+
35
+ def export_note_to_csv(df: pd.DataFrame, filename: str, output_folder: str) -> NoteCSVInfo:
36
+ """
37
+ Export DataFrame to CSV and return info.
38
+ """
39
+ # Ensure output folder exists
40
+ os.makedirs(output_folder, exist_ok=True)
41
+ output_path = os.path.join(output_folder, filename)
42
+ df.to_csv(output_path, index=False)
43
+ return NoteCSVInfo(name=filename, rows=df.shape[0])
44
+
45
+ def main() -> None:
46
+ """
47
+ Main function to extract P&L notes from Excel and export as CSV.
48
+ """
49
+ logger.info("Loading Excel file: %s", settings.excel_file_path)
50
+ note_16_23_df = clean_note(settings.note_16_23_sheet, settings.skiprows)
51
+
52
+ os.makedirs(settings.output_folder, exist_ok=True)
53
+ info_16_23 = export_note_to_csv(note_16_23_df, "Note_16_to_23_Full.csv", settings.output_folder)
54
+
55
+ logger.info(f"Extracted rows: Note 16-23 = {info_16_23.rows} rows")
pnlbs/temp_bl.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List, Dict, Any, Optional
3
+ from pydantic import BaseModel, Field
4
+
5
+ # Configure logging
6
+ logging.basicConfig(level=logging.INFO)
7
+ logger = logging.getLogger(__name__)
8
+
9
+ class BalanceSheetItem(BaseModel):
10
+ section: str
11
+ category: str
12
+ subcategory: Optional[str] = ""
13
+ name: str
14
+ note: str
15
+ indent_level: int = 1
16
+ is_total_row: bool = False
17
+ is_section_header: bool = False
18
+ is_category_header: bool = False
19
+
20
+ class FormattingRules(BaseModel):
21
+ header: Dict[str, Any]
22
+ sections: Dict[str, Dict[str, Any]]
23
+ categories: Dict[str, Dict[str, Any]]
24
+ subcategories: Dict[str, Dict[str, Any]]
25
+ totals: Dict[str, Dict[str, Any]]
26
+
27
+ class BalanceSheetTemplate:
28
+ """
29
+ Provides the structure, formatting, and field mappings for a standard Balance Sheet.
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
+ ]
51
+
52
+ # Formatting rules for display
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},
61
+ "ASSETS": {"display_name": "ASSETS", "order": 2}
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},
75
+ "TOTAL_ASSETS": {"display_name": "TOTAL", "position": "after_assets", "is_grand_total": True}
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
+ }
97
+
98
+ def get_template_structure(self) -> List[Dict[str, Any]]:
99
+ """Return the complete template structure."""
100
+ return self.template_structure.copy()
101
+
102
+ def get_formatting_rules(self) -> FormattingRules:
103
+ """Return the formatting rules."""
104
+ return self.formatting_rules.copy()
105
+
106
+ def get_field_mappings(self) -> Dict[str, List[str]]:
107
+ """Return the field mapping patterns."""
108
+ return self.field_mappings.copy()
109
+
110
+ def get_categories(self) -> List[str]:
111
+ """Get unique categories from template."""
112
+ categories = []
113
+ seen = set()
114
+ for item in self.template_structure:
115
+ cat = item["category"]
116
+ if cat not in seen:
117
+ categories.append(cat)
118
+ seen.add(cat)
119
+ return categories
120
+
121
+ def get_items_by_category(self, category: str) -> List[Dict[str, Any]]:
122
+ """Get all items for a specific category."""
123
+ return [item for item in self.template_structure if item["category"] == category]
124
+
125
+ def get_items_by_section(self, section: str) -> List[Dict[str, Any]]:
126
+ """Get all items for a specific section."""
127
+ return [item for item in self.template_structure if item["section"] == section]
128
+
129
+ def get_subcategories(self, category: str) -> List[str]:
130
+ """Get subcategories for a specific category."""
131
+ subcats = set()
132
+ for item in self.template_structure:
133
+ if item["category"] == category and item["subcategory"]:
134
+ subcats.add(item["subcategory"])
135
+ return list(subcats)
136
+
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",
148
+ "Current assets"
149
+ ]
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
+ }
168
+
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
+ ]
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ pandas
4
+ openpyxl
5
+ python-dotenv
6
+ pydantic-settings
7
+ pydantic
8
+ requests
9
+ python-multipart