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() |