Spaces:
Sleeping
Sleeping
Sahil Garg
commited on
Commit
·
c79824c
1
Parent(s):
c3dda2f
need to test all routes once again
Browse files- .gitignore +26 -0
- Dockerfile +46 -0
- app/api.py +391 -0
- app/bs.py +314 -0
- app/cashflow.py +362 -0
- app/extract.py +251 -0
- app/json_xlsx.py +321 -0
- app/loader.py +57 -0
- app/main.py +23 -0
- app/main16_23.py +906 -0
- app/new.py +1799 -0
- app/new_main.py +517 -0
- app/notes.py +303 -0
- app/pnl.py +292 -0
- app/utils.py +57 -0
- app/utils_normalize.py +95 -0
- config/mapping1.json +744 -0
- config/rules1.json +15 -0
- docker-compose.yml +19 -0
- pnlbs/bl_llm.py +835 -0
- pnlbs/csv_json_bs.py +360 -0
- pnlbs/csv_json_pnl.py +360 -0
- pnlbs/pnl_note.py +353 -0
- pnlbs/sircodebs.py +75 -0
- pnlbs/sircodepnl.py +55 -0
- pnlbs/temp_bl.py +185 -0
- requirements.txt +9 -0
.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
|