LedgerSync-AI / app.py
Haseeb-001's picture
Create app.py
39e62fd verified
import os
import streamlit as st
import pandas as pd
import numpy as np
from io import BytesIO
import tempfile
# Optional: PDF extraction if needed
try:
import pdfplumber
except ImportError:
pdfplumber = None
# FAISS for potential vector similarity (for future enhancement)
import faiss
# Groq API for LLM integration
from groq import Groq
# -------------------------------
# Initialize Groq Client
# -------------------------------
client = Groq(api_key=os.environ.get("GROQ_API_KEY"))
# -------------------------------
# Utility Functions
# -------------------------------
def load_ledger(file):
"""
Load ledger from CSV, JSON, or PDF.
"""
file_ext = os.path.splitext(file.name)[1].lower()
if file_ext == ".csv":
df = pd.read_csv(file)
elif file_ext == ".json":
df = pd.read_json(file)
elif file_ext == ".pdf":
if pdfplumber is None:
st.error("Please install pdfplumber to process PDF files.")
return None
with pdfplumber.open(file) as pdf:
page = pdf.pages[0] # Assumes table on first page
table = page.extract_table()
df = pd.DataFrame(table[1:], columns=table[0])
else:
st.error("Unsupported file type!")
return None
return df
def preprocess_ledger(df):
"""
Standardize date format and convert credit/debit to float.
"""
df['date'] = pd.to_datetime(df['date'], errors='coerce')
df['credit'] = pd.to_numeric(df['credit'], errors='coerce').fillna(0.0)
df['debit'] = pd.to_numeric(df['debit'], errors='coerce').fillna(0.0)
return df
def generate_suggestion(row):
"""
Generate a reconciliation suggestion using Groq API.
"""
prompt = (
f"Ledger entry mismatch detected.\n"
f"- Date: {row['date'].date() if pd.notnull(row['date']) else 'Unknown'}\n"
f"- Credit: {row['credit']}\n"
f"- Debit: {row['debit']}\n\n"
"Please provide reconciliation suggestions in simple bullet points."
)
try:
response = client.chat.completions.create(
messages=[{"role": "user", "content": prompt}],
model="llama-3.3-70b-versatile",
stream=False,
)
suggestion = response.choices[0].message.content
except Exception as e:
suggestion = f"Error generating suggestion: {e}"
return suggestion
def compare_ledgers(df_a, df_b):
"""
Compare two ledger DataFrames row-by-row based on date, credit, and debit.
"""
results = []
df_b_copy = df_b.copy()
# Compare each entry in Ledger A with Ledger B
for idx, row in df_a.iterrows():
# Match based on same date and nearly identical credit & debit amounts.
match = df_b_copy[
(df_b_copy['date'] == row['date']) &
(np.isclose(df_b_copy['credit'], row['credit'])) &
(np.isclose(df_b_copy['debit'], row['debit']))
]
if not match.empty:
status = "βœ… Matched"
suggestion = ""
# Remove matched entry to prevent duplicate matching.
df_b_copy = df_b_copy.drop(match.index[0])
else:
status = "❌ Mismatch"
suggestion = generate_suggestion(row)
results.append({
"date": row['date'],
"credit": row['credit'],
"debit": row['debit'],
"description": row.get("description", ""),
"status": status,
"suggestion": suggestion
})
# Any remaining entries in Ledger B are extra entries.
for idx, row in df_b_copy.iterrows():
results.append({
"date": row['date'],
"credit": row['credit'],
"debit": row['debit'],
"description": row.get("description", ""),
"status": "❌ Mismatch (Extra in Ledger B)",
"suggestion": "Review extra entry in Ledger B."
})
result_df = pd.DataFrame(results)
return result_df
def calculate_totals(df_a, df_b):
"""
Calculate totals and differences for credits and debits.
"""
totals = {
"ledger_a_credit": df_a['credit'].sum(),
"ledger_a_debit": df_a['debit'].sum(),
"ledger_b_credit": df_b['credit'].sum(),
"ledger_b_debit": df_b['debit'].sum(),
"credit_difference": df_a['credit'].sum() - df_b['credit'].sum(),
"debit_difference": df_a['debit'].sum() - df_b['debit'].sum(),
}
return totals
def generate_excel_report(df):
"""
Generate an Excel report from the reconciliation DataFrame.
"""
output = BytesIO()
with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
df.to_excel(writer, index=False, sheet_name="Reconciliation")
processed_data = output.getvalue()
return processed_data
# -------------------------------
# Streamlit User Interface
# -------------------------------
def main():
st.title("πŸ“Š Finance Ledger Reconciliation App")
st.markdown("Upload the ledger files to compare two opposite party records and get reconciliation suggestions.")
col1, col2 = st.columns(2)
with col1:
ledger_a_file = st.file_uploader("Upload Ledger A (CSV/JSON/PDF)", type=["csv", "json", "pdf"], key="ledger_a")
with col2:
ledger_b_file = st.file_uploader("Upload Ledger B (CSV/JSON/PDF)", type=["csv", "json", "pdf"], key="ledger_b")
if ledger_a_file and ledger_b_file:
df_a = load_ledger(ledger_a_file)
df_b = load_ledger(ledger_b_file)
if df_a is not None and df_b is not None:
st.subheader("Original Ledgers Preview")
st.markdown("**Ledger A:**")
st.write(df_a.head())
st.markdown("**Ledger B:**")
st.write(df_b.head())
# Preprocess the data
df_a = preprocess_ledger(df_a)
df_b = preprocess_ledger(df_b)
st.subheader("Processed Ledgers Preview")
st.markdown("**Ledger A:**")
st.write(df_a.head())
st.markdown("**Ledger B:**")
st.write(df_b.head())
# Compare ledgers and calculate differences
with st.spinner("Comparing ledgers..."):
result_df = compare_ledgers(df_a, df_b)
totals = calculate_totals(df_a, df_b)
st.subheader("Reconciliation Results")
st.write(result_df)
st.markdown("### Totals & Differences")
st.write(totals)
# Download report button (Excel file)
excel_data = generate_excel_report(result_df)
st.download_button(label="Download Report as Excel",
data=excel_data,
file_name="reconciliation_report.xlsx",
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
st.success("Reconciliation completed successfully!")
if __name__ == '__main__':
main()