""" app.py — Flask entry point + all routes + inline HTML/CSS/JS UI. No templates/ or static/ folders needed. Run locally: python app.py """ import os import uuid import logging from pathlib import Path from flask import Flask, render_template_string, request, jsonify, session from pipeline import pipeline_query, add_document_to_index, clear_session_memory from ingest import FAISS_INDEX_PATH, BASE_DIR logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)-8s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") logger = logging.getLogger(__name__) UPLOADED_DOCS_PATH = str(BASE_DIR / "data" / "uploads") FLASK_SECRET_KEY = os.environ.get("FLASK_SECRET_KEY", os.urandom(24).hex()) app = Flask(__name__) app.secret_key = FLASK_SECRET_KEY # ══════════════════════════════════════════════════════════════════════ # INLINE HTML / CSS / JS # ══════════════════════════════════════════════════════════════════════ _PAGE = """ Financial Document Intelligence Platform

🏦 Financial Document Intelligence Platform

Retrieval-Augmented Generation  ·  Banking & Insurance  ·  LangChain  ·  FAISS  ·  LLaMA 3.3 70B

📄 FDIC🛡️ NAIC📊 SEC 🏛️ Basel III🔍 MMR Retrieval💬 Multi-turn Memory
👋 Hello! I'm FinRAG. Ask me anything about FDIC deposit insurance, Basel III, life insurance, SEC filings, AML compliance, or any banking and insurance topic.
💡 Try asking:

Extend the Knowledge Base at Runtime

Upload your own financial PDFs and FinRAG will embed and index them immediately — no restart required.

  • Format: PDF only  ·  Recommended max size: 50 MB
  • Documents are chunked, embedded, and merged into the live FAISS index via vectorstore.merge_from()
  • Index is re-persisted to disk after each upload
📂

Click to select a PDF or drag and drop here

PDF only · Max 50 MB

No document uploaded yet.

System Architecture

┌─────────────────────────────────────────────────────────────────┐
│                   Financial-RAG-Platform                        │
│                                                                 │
│  ┌───────────┐   ┌──────────────────┐   ┌──────────────────┐  │
│  │ PDF / TXT │──▶│   ingest.py      │──▶│  FAISS Index     │  │
│  │  Sources  │   │ Chunk + Embed    │   │  (pre-baked in   │  │
│  │ FDIC/NAIC │   │ MiniLM-L6-v2     │   │  Docker image)   │  │
│  └───────────┘   └──────────────────┘   └────────┬─────────┘  │
│                                                   │             │
│  ┌───────────┐   ┌──────────────────┐   ┌────────▼─────────┐  │
│  │Guardrails │──▶│   pipeline.py    │◀──│  MMR Retriever   │  │
│  │  (domain  │   │  LangChain Conv. │   │  top-k = 5       │  │
│  │  filter)  │   │  RetrievalChain  │   └──────────────────┘  │
│  └───────────┘   └────────┬─────────┘                         │
│                           │            ┌──────────────────┐   │
│  ┌───────────┐            │            │  Confidence      │   │
│  │  Session  │◀───────────┤            │  Scoring         │   │
│  │  Memory   │            ▼            └──────────────────┘   │
│  └───────────┘   ┌──────────────────┐                         │
│                  │   Groq API       │                         │
│                  │  LLaMA 3.3 70B   │                         │
│                  └────────┬─────────┘                         │
│                           │                                   │
│                  ┌────────▼─────────┐                         │
│                  │     app.py       │                         │
│                  │  Flask + HTML    │                         │
│                  │  /api/chat       │                         │
│                  │  /api/upload     │                         │
│                  │  /api/clear      │                         │
│                  └──────────────────┘                         │
└─────────────────────────────────────────────────────────────────┘
ComponentTechnologyRole
Embeddingssentence-transformers/all-MiniLM-L6-v2Local, no external API, 384-dim vectors
Vector Storefaiss-cpu (IndexFlatL2)Millisecond ANN search, pre-baked in Docker image
LLMGroq · LLaMA 3.3 70B VersatileSub-second generation via Groq API
RAG OrchestrationLangChain 0.3 ConversationalRetrievalChainMulti-turn with condense + QA prompts
MemoryConversationBufferWindowMemory (k=6)Per-session isolated conversation history
GuardrailsKeyword scan + regex off-topic detectionDomain relevance enforcement
ConfidenceCosine similarity + exponential decay weightsHallucination risk flagging per query
BackendFlask 3.xREST API: /api/chat, /api/upload, /api/clear
FrontendVanilla HTML / CSS / JS (inline)Dark financial theme, tab-based dashboard
DeploymentDockerfile → HuggingFace Spaces Docker SDKPort 7860, non-root user
""" # ══════════════════════════════════════════════════════════════════════ # ROUTES # ══════════════════════════════════════════════════════════════════════ def _get_sid() -> str: if "sid" not in session: session["sid"] = str(uuid.uuid4()) return session["sid"] @app.route("/") def index(): _get_sid() return render_template_string(_PAGE) @app.route("/api/chat", methods=["POST"]) def chat(): data = request.get_json(silent=True) or {} question = (data.get("question") or "").strip() if not question: return jsonify({"error": "Empty question"}), 400 sid = _get_sid() logger.info("[%s] Q: %s", sid[:8], question[:120]) return jsonify(pipeline_query(question, sid)) @app.route("/api/upload", methods=["POST"]) def upload(): if "file" not in request.files: return jsonify({"message": "No file provided."}), 400 f = request.files["file"] if not f.filename or not f.filename.lower().endswith(".pdf"): return jsonify({"message": "Only PDF files are supported."}), 400 dest_dir = Path(UPLOADED_DOCS_PATH) dest_dir.mkdir(parents=True, exist_ok=True) dest = dest_dir / f.filename f.save(str(dest)) ok = add_document_to_index(str(dest)) msg = f"✅ {f.filename} was embedded and added to the knowledge base." if ok else f"⚠️ Could not process {f.filename}. Ensure it is a valid, non-encrypted PDF." return jsonify({"message": msg}) @app.route("/api/clear", methods=["POST"]) def clear(): sid = session.get("sid") if sid: clear_session_memory(sid) return jsonify({"message": "Conversation cleared."}) # ══════════════════════════════════════════════════════════════════════ # ENTRY POINT # ══════════════════════════════════════════════════════════════════════ if __name__ == "__main__": if not Path(FAISS_INDEX_PATH, "index.faiss").exists(): logger.warning("FAISS index not found — running ingestion now...") from ingest import run_ingestion run_ingestion() app.run(host="0.0.0.0", port=7860, debug=False)