Add bank statement analyzer with automatic work storage

This commit is contained in:
A R R R Associates
2026-07-13 10:12:19 +05:30
parent d391e9b443
commit bbc5afe1c0
23 changed files with 1047 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
from __future__ import annotations
import shutil
from pathlib import Path
from fastapi import APIRouter, File, Form, Request, UploadFile
from fastapi.responses import FileResponse, RedirectResponse
from starlette.background import BackgroundTask
from app.core.db.common import CommonSessionLocal
from app.core.http_responses import ui_access_denied, not_found_response
from app.core.security.csrf import get_or_create_csrf_token, validate_csrf
from app.core.security.session_auth import get_current_user
from app.core.templating import templates
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
from .service import can_use, create_job, save_uploads, analyze_job, resolve_owned_job, delete_job
router = APIRouter(prefix="/tools/bank-statement-analyzer", tags=["bank-statement-analyzer-ui"])
def _ctx(request, db, user, **extra):
data = {
"request": request,
"current_user": user,
"current_user_roles": get_user_roles(db, user.id),
"current_user_permissions": get_user_permissions(db, user.id),
"csrf_token": get_or_create_csrf_token(request),
"title": "Bank Statement Analyzer",
}
data.update(extra)
return data
def _auth(request, db):
user = get_current_user(request, db=db)
if not user:
return None, None, RedirectResponse("/login", status_code=303)
roles = get_user_roles(db, user.id)
if not can_use(roles):
return user, roles, ui_access_denied("Bank Statement Analyzer is available only to Partner, Manager, Staff and Consultant roles.")
return user, roles, None
@router.get("")
def index(request: Request):
db = CommonSessionLocal()
try:
user, roles, denied = _auth(request, db)
if denied:
return denied
return templates.TemplateResponse("modules/bank_statement_analyzer/templates/bank_statement_analyzer/index.html", _ctx(request, db, user, error=""))
finally:
db.close()
@router.post("/analyze")
async def analyze(request: Request, csrf_token: str = Form(...), customer_name: str = Form(""), account_number: str = Form(""), statements: list[UploadFile] = File(...)):
db = CommonSessionLocal()
job_dir: Path | None = None
try:
user, roles, denied = _auth(request, db)
if denied:
return denied
validate_csrf(request, csrf_token)
job_id, input_dir, output_dir = create_job(user, roles)
job_dir = input_dir.parent
paths = await save_uploads(statements, input_dir)
summary = analyze_job(user=user, roles=roles, job_id=job_id, paths=paths, output_dir=output_dir, customer_override=customer_name, account_override=account_number)
return templates.TemplateResponse("modules/bank_statement_analyzer/templates/bank_statement_analyzer/result.html", _ctx(request, db, user, summary=summary))
except Exception as exc:
if job_dir and job_dir.exists():
# Failed jobs are retained for the configured short retention period for troubleshooting/retry.
pass
user = get_current_user(request, db=db)
if not user:
return RedirectResponse("/login", status_code=303)
return templates.TemplateResponse("modules/bank_statement_analyzer/templates/bank_statement_analyzer/index.html", _ctx(request, db, user, error=str(exc)), status_code=400)
finally:
db.close()
@router.get("/{job_id}/download")
def download(job_id: str, request: Request):
db = CommonSessionLocal()
try:
user, roles, denied = _auth(request, db)
if denied:
return denied
try:
job, meta = resolve_owned_job(user, roles, job_id)
except FileNotFoundError:
return not_found_response(request, "Analysis job not found or already cleaned up.")
output = job / "Output" / meta["output_file"]
if not output.is_file():
return not_found_response(request, "Analysis workbook not found.")
return FileResponse(path=output, filename="Bank_Statement_Analysis.xlsx", media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", background=BackgroundTask(delete_job, job))
finally:
db.close()
@router.post("/{job_id}/delete")
def delete(job_id: str, request: Request, csrf_token: str = Form(...)):
db = CommonSessionLocal()
try:
user, roles, denied = _auth(request, db)
if denied:
return denied
validate_csrf(request, csrf_token)
try:
job, _ = resolve_owned_job(user, roles, job_id)
delete_job(job)
except FileNotFoundError:
pass
return RedirectResponse("/tools/bank-statement-analyzer", status_code=303)
finally:
db.close()