From 08386f8544137745330793ec9089558b7dfd7e7b Mon Sep 17 00:00:00 2001 From: Iyeoluwa Akinrinola Date: Wed, 2 Jul 2025 21:02:11 +0100 Subject: [PATCH] Add auto-matching endpoint and data storage for easier demo workflow --- main.py | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 5c57f1d..e279fe5 100644 --- a/main.py +++ b/main.py @@ -41,6 +41,10 @@ drive_sync = GoogleDriveSync() # In-memory storage for uploaded files (in production, use a database) uploaded_files = {} +# Store imported transactions globally for easy access +stored_transactions = [] +processed_receipts = {} + @app.get("/") async def root(): """Health check endpoint""" @@ -137,6 +141,10 @@ async def import_quickbooks_transactions_csv(file: UploadFile = File(...)): }) except Exception as e: errors.append(f"Row {idx+1}: {str(e)}") + # Store transactions globally for auto-matching + global stored_transactions + stored_transactions = transactions + # Use the same logic as the JSON import endpoint request_obj = QuickBooksImportRequest(transactions=transactions) response = await import_quickbooks_transactions(request_obj) @@ -219,6 +227,21 @@ async def process_document(file_id: str): else: uploaded_files[file_id]["status"] = "processed" uploaded_files[file_id]["extracted_data"] = result + + # Store processed receipt data for auto-matching + global processed_receipts + processed_receipts[file_id] = { + "filename": file_info["filename"], + "upload_date": file_info["upload_date"], + "extraction_success": result.get("extraction_success", False), + "vendor": result.get("vendor"), + "total_amount": result.get("total_amount"), + "tax_amount": result.get("tax_amount"), + "date": result.get("date"), + "category": result.get("category"), + "confidence": result.get("confidence"), + "error": result.get("error") + } return DocumentProcessResponse( file_id=file_id, @@ -422,6 +445,74 @@ async def match_receipts_transactions(request: MatchingRequest): except Exception as e: raise HTTPException(status_code=500, detail=str(e)) +@app.post("/match-auto", response_model=MatchingResponse) +async def match_auto(): + """ + Automatically match all processed receipts against all imported transactions. + + This endpoint uses the stored transaction data from CSV import and + all processed receipts to perform matching without requiring manual data input. + """ + try: + if not stored_transactions: + raise HTTPException(status_code=400, detail="No transactions imported. Please upload CSV first.") + + if not processed_receipts: + raise HTTPException(status_code=400, detail="No receipts processed. Please upload and process receipts first.") + + # Convert stored transactions to Receipt/Transaction models + transactions = [ + Transaction( + id=t["id"], + transaction_date=datetime.strptime(t["txn_date"], "%Y-%m-%d"), + amount=abs(t["amount"]), + vendor=t["payee_name"], + notes=t.get("memo", "") + ) for t in stored_transactions + ] + + receipts = [] + for file_id, receipt_data in processed_receipts.items(): + if receipt_data.get("extraction_success"): + receipts.append(Receipt( + id=file_id, + file_name=receipt_data.get("filename", ""), + upload_date=receipt_data.get("upload_date", datetime.now()), + receipt_date=datetime.strptime(receipt_data.get("date", "2024-01-01"), "%Y-%m-%d"), + amount=receipt_data.get("total_amount", 0.0), + tax=receipt_data.get("tax_amount", 0.0), + vendor=receipt_data.get("vendor", ""), + category=receipt_data.get("category", "") + )) + + if not receipts: + raise HTTPException(status_code=400, detail="No successfully processed receipts found.") + + # Process matching using AI engine + matches = matching_engine.process_matching(receipts, transactions) + + # Convert to response format + match_responses = [ + MatchResponse( + receipt_id=match.receipt.id, + transaction_id=match.transaction.id, + confidence_score=match.confidence_score, + match_reason=match.match_reason, + receipt_vendor=match.receipt.vendor, + receipt_amount=match.receipt.amount, + transaction_vendor=match.transaction.vendor, + transaction_amount=match.transaction.amount + ) for match in matches + ] + + # Get statistics + stats = matching_engine.get_matching_stats(matches) + + return MatchingResponse(matches=match_responses, stats=stats) + + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + @app.post("/approve") async def approve_match(request: ApprovalRequest): """ @@ -504,7 +595,26 @@ async def get_stats(): "recent_feedback_logs": len(recent_logs), "active_rules": len([r for r in matching_engine.rules_engine.rules if r.status == "active"]), "uploaded_documents": len(uploaded_files), - "processed_documents": len([f for f in uploaded_files.values() if f["status"] == "processed"]) + "processed_documents": len([f for f in uploaded_files.values() if f["status"] == "processed"]), + "stored_transactions": len(stored_transactions), + "processed_receipts": len(processed_receipts) + } + + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + +@app.get("/status") +async def get_status(): + """Get current system status for demo purposes""" + try: + return { + "csv_uploaded": len(stored_transactions) > 0, + "transactions_count": len(stored_transactions), + "receipts_uploaded": len(uploaded_files), + "receipts_processed": len(processed_receipts), + "ready_for_matching": len(stored_transactions) > 0 and len(processed_receipts) > 0, + "sample_transactions": stored_transactions[:3] if stored_transactions else [], + "sample_receipts": list(processed_receipts.keys())[:3] if processed_receipts else [] } except Exception as e: