Spaces:
Sleeping
Sleeping
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() |