Banker's Wrapped
What Is This?
Banker's Wrapped is an agentic AI platform that transforms raw transaction data into a personalized narrated financial recap video — fully generated, stored, and served via Backblaze B2, with every AI media call routed through the Genblaze SDK.
Upload a CSV. Receive a 60-second video that tells the story of your financial year. Inspired by Spotify Wrapped. Built for banking. Designed for production.
The Problem
Banks generate mountains of transaction data but deliver it as an unreadable table. Customers disengage; apps go unused. Financial institutions lose the relationship. There is no moment that makes money feel meaningful.
Banker's Wrapped solves this with an agentic pipeline that reads your transactions, assigns a financial personality, writes a 5-scene cinematic narrative, synthesizes voice narration, generates scene images, and composes a sharable MP4 with cinematic dip-to-black transitions — end to end, no human in the loop.
How It Works
- Upload a CSV transaction export — or connect a bank via Plaid Sandbox (zero setup, no real credentials; the connector normalises Plaid transactions into the same schema)
- Document Agent parses and normalizes transactions into typed records
- Analytics Agent calculates income, expenses, savings rate, and top spending categories
- Financial Personality is assigned: Builder · Optimizer · Explorer · Achiever
- Narrative Agent generates a structured 5-scene cinematic video script (Opening → Achievement → Insight → Advice → Close) — routed through Genblaze chat (ADR-007) on GMI Cloud (
openai/gpt-5.4-mini), with automatic NVIDIA NIM fallback on provider failure or invalid JSON - Scene images generated via Genblaze → GMI Cloud (Seedream 4.0, 1344×768) — all 5 in parallel (with automatic 3× retry + exponential backoff)
- Voice narration synthesised via Genblaze → OpenAI TTS (tts-1, alloy voice) — concatenated scene text
- FFmpeg composes the final MP4: each scene rendered to a segment, then concat-joined with narration — dip-to-black transitions between scenes, browser-safe H.264
yuv420p+faststart/ AAC (memory-bounded so it runs on any host) - 14 files (10 artifact types) uploaded to Backblaze B2 (video, thumbnail, script, analytics, prompts, generation provenance, 5 scenes, narration, CSV, metadata); presigned URL + download ZIP returned
Architecture
Click to enlarge (opens full-resolution SVG — scales without pixelation): light / dark · Source: assets/architecture/architecture-diagram.mmd — rendered to brand-themed SVG/PNG (dark + light) via mermaid-cli; see assets/architecture/README.md for the regenerate command.
In short: ~2–4 minutes end to end, with image generation dominating (5 scenes generated in parallel); 14 files across 10 artifact types persisted to Backblaze B2 as the source of truth; every artifact carries a SHA-256 and every step logs model, latency, and retries.
Deep dive →
docs/ARCHITECTURE.md— per-step pipeline timing, the full B2 storage layout, lifecycle & integrity (45-day retention + per-artifact SHA-256), and the four machine-readable provenance manifests.
Architecture Decision Records
Twelve decisions documented (001–012), all accepted and implemented — see docs/adr/ for full rationale.
| ADR | Decision |
|---|---|
| 001 | Genblaze as sole media generation layer — no direct provider calls |
| 002 | Lightweight typed async agent pipeline — Semantic Kernel considered, not adopted (no framework dependency) |
| 003 | FFmpeg for composition; Runway ML / Luma AI excluded (impl revised in v1.6.0) |
| 004 | SQLite for MVP, PostgreSQL as documented production upgrade |
| 005 | Financial Personality promoted to required scope — the emotional hook |
| 006 | structlog JSON logging only — OpenTelemetry is post-hackathon |
| 007 | Route the narrative LLM through Genblaze (GMI Cloud chat, NIM fallback) — 3 of 4 AI steps via Genblaze |
| 008 | B2 as session source of truth — SQLite is a cache; sessions survive redeploys |
| 009 | B2 lifecycle rules (45-day retention) + per-artifact SHA-256 integrity |
| 010 | Plaid sandbox connector — optional "connect a bank" path (live in production) |
| 011 | Memory-bounded segment+concat compositor + non-blocking event loop (v1.6.0 redesign) |
| 012 | Custom domain for the frontend (bankers-wrapped.arjunganesh.dev) + explicit Railway CORS origins |
Genblaze Integration
Genblaze is not optional — it is the AI orchestration layer. Three of the four AI steps route through Genblaze: scene images (GMI Cloud Seedream), narration audio (OpenAI TTS wrapped in GenblazeClient), and narrative script generation (Genblaze chat on GMI Cloud — openai/gpt-5.4-mini — with automatic NVIDIA NIM fallback via the same SDK wrapper). Zero direct provider API calls outside genblaze_client.py.
# Scene images — Genblaze → GMI Cloud Seedream (all 5 in parallel via asyncio.gather)
pr = (
Pipeline("bankers-wrapped-image")
.step(GMICloudImageProvider(),
model="seedream-4-0-250828",
prompt=scene.visual_prompt,
modality=Modality.IMAGE,
width=1344,
height=768)
.run(timeout=300, raise_on_failure=True)
)
image_bytes = httpx.get(pr.run.steps[0].assets[0].url).content
# Narration audio — OpenAI TTS wrapped inside GenblazeClient (no direct provider calls outside)
audio_result = await genblaze_client.generate_narration_audio(
narration_text=script.full_narration, # all 5 scene narrations joined
model="tts-1",
voice="alloy",
)
Every run produces a SHA-256 provenance manifest stored in B2 metadata — full model traceability per video.
Provenance is verifiable from both sides: the pipeline's own generation.json per session, and
GMI Cloud's console — see assets/gmi-cloud/ for the provider's record of the
same calls (recently-used models, per-model spend, and the generated scene images with timestamps
matching the committed runs).
Financial Personality
The emotional centrepiece of every recap. One of four labels is assigned based on spending and saving patterns:
| Personality | Trigger | Scene 1 Hook |
|---|---|---|
| Financial Builder | Savings rate ≥ 15% or active debt reduction | "You're laying the foundation — brick by brick." |
| Financial Explorer | Top spend in travel or entertainment | "You invest in experiences that last a lifetime." |
| Financial Achiever | Active investing or steady 8–14% savings | "Your discipline is paying off — literally." |
| Financial Optimizer | Lean discretionary spend, efficient budget | "Every dollar has a purpose in your world." |
The personality label opens Scene 1 and drives the entire visual and narrative tone.
Tech Stack
Live & Interactive Demo
| App | https://bankers-wrapped.arjunganesh.dev ✅ live (verified 2026-07-25) — previously bankers-wrapped.vercel.app, still live as a fallback |
| API | https://bankers-wrapped-api-production.up.railway.app |
| Demo Video | https://youtu.be/eTw1TCcYFk4 — 2:50, captions included |
| Try It Now | make demo — runs the full pipeline with synthetic data, no real bank account needed |
Hackathon judging criteria and submission checklist: submission/SUBMISSION.md
Run it yourself — the interactive notebook
notebooks/DEMO_RUNBOOK.ipynb — a self-contained interactive walkthrough of the full pipeline. No frontend, no local backend required. Just set your API keys and run against the live Railway deployment.
| Scenario | API cost | Time | What you'll see |
|---|---|---|---|
| A — Financial Builder (Jan 2026) | LLM + 5 images + TTS | ~2–3 min | Full end-to-end run · amber personality · 5-scene video |
| B — Financial Explorer (Q4 2025) | LLM + 5 images + TTS | ~2–3 min | Second personality type · teal theme · 39-transaction dataset |
| C — Pre-generated session | None | < 5 s | Fetch a completed session from B2 · inspect all 14 artifacts |
| Comparison chart | — | instant | Income vs expenses side-by-side across both datasets |
| Timing chart | — | instant | Where the ~2 min goes — image generation dominates |
| B2 inspection | — | instant | Full 14-file (10-type) B2 layout printed per session |
# Run against the live production API — no local setup needed
pip install httpx matplotlib jupyter
jupyter notebook notebooks/DEMO_RUNBOOK.ipynb
Screenshots
Full judge-facing galleries and raw evidence for two complete runs (CSV upload and Plaid Sandbox) are organized in assets/README.md.
| Product Flow | B2 Provenance |
|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Quick Start
# 1. Clone
git clone https://github.com/iarjunganesh/bankers-wrapped
cd bankers-wrapped
# 2. Configure
cp .env.example .env
# Fill in: GMI_API_KEY, NVIDIA_NIM_API_KEY, B2_KEY_ID, B2_APPLICATION_KEY, B2_ENDPOINT_URL
# 3. Install (requires Python 3.14 and ffmpeg)
make install
# 4. Start backend + frontend
make demo-start # bash scripts/start_demo.sh
# — or backend only:
make dev # uvicorn on :8000 with hot-reload
# 5. Run the demo pipeline against both synthetic datasets
make demo # python scripts/demo_run.py
# 6. Stop all services when done
make demo-stop # bash scripts/stop_demo.sh
On Windows, use the PowerShell equivalents directly:
.\scripts\start_demo.ps1
python scripts\demo_run.py
.\scripts\stop_demo.ps1
Synthetic Demo Data
No real bank data required. Two datasets committed to data/synthetic/:
| File | Period | Transactions | Personality Triggered |
|---|---|---|---|
transactions_jan_2026.csv |
Jan 2026 | 22 | Financial Builder |
transactions_q4_2025.csv |
Oct–Dec 2025 | 39 | Financial Explorer |
CSV Schema
| Column | Type | Required | Notes |
|---|---|---|---|
date |
YYYY-MM-DD | ✅ | Transaction date |
description |
string | ✅ | Merchant or transfer description |
amount |
float | ✅ | Positive = income/credit, negative = expense/debit |
currency |
string | ❌ | ISO 4217 code (default: USD) |
category |
string | ❌ | Auto-inferred from description if omitted |
Valid categories: income · savings · housing · food · travel · entertainment · utilities · investment · debt · other
date,description,amount,currency,category
2026-01-03,Salary Deposit,6500.00,USD,income
2026-01-05,ICA Maxi Grocery,-128.40,USD,food
2026-01-15,Savings Transfer,-1200.00,USD,savings
2026-01-20,Lufthansa Flight,-312.00,USD,travel
# Run the full pipeline with the January demo file
make demo
# or directly via curl
curl -X POST http://localhost:8000/api/v1/recap/generate \
-F "file=@data/synthetic/transactions_jan_2026.csv"
Project Structure
bankers-wrapped/
├── backend/
│ ├── agents/ # 4 typed async agents (Document, Analytics, Narrative, Media)
│ ├── api/
│ │ ├── limiter.py # slowapi rate limiter (5 req/hr/IP)
│ │ ├── middleware/ # Request logging
│ │ └── v1/
│ │ ├── recap.py # POST /generate · GET /{session_id} (B2 fallback) · /download ZIP
│ │ ├── progress.py # GET /{session_id}/progress (SSE)
│ │ ├── plaid.py # POST /plaid/link-token · /plaid/exchange (feature-flagged)
│ │ └── health.py # status + version + plaid_enabled
│ ├── ingest/ # PlaidConnector — sandbox "connect a bank" path (ADR-010)
│ ├── media/ # GenblazeClient (images + chat + TTS) · FFmpegComposer
│ ├── storage/ # B2Client (source of truth) · SessionStore (SQLite cache)
│ ├── models/ # Pydantic models (Transaction, Insights, Script, Session)
│ └── config.py # Pydantic Settings
├── frontend/
│ └── app/
│ ├── page.tsx # Upload portal + Plaid Link + live SSE progress
│ └── recap/[session_id]/page.tsx # Public share page
├── tests/
│ ├── unit/ # Per-agent unit tests (all providers mocked)
│ ├── integration/ # API end-to-end tests (incl. B2-fallback + SSE stream)
│ └── load/ # k6 smoke test (manual — p95 + rate-limiter behavior)
├── infra/ # b2-lifecycle.json — bucket retention as code (ADR-009)
├── scripts/ # demo_run · apply_b2_lifecycle · recompose · start/stop_demo
├── notebooks/ # DEMO_RUNBOOK.ipynb — interactive pipeline walkthrough
├── data/synthetic/ # Demo CSVs — committed, no PII
├── prompts/ # LLM system prompts (narrative_agent.txt)
├── submission/ # Judge deliverables
│ ├── DEVPOST.md # Devpost form content
│ ├── DEVPOST_README.md # Devpost-paste mirror of this README (absolute links)
│ ├── SUBMISSION.md # Evidence-first judging alignment + deliverables checklist
│ ├── DEMO_SCRIPT.md # ≤3-min video shooting script + OBS runbook + narration script
│ └── COSTS.md # Component budget — everything stays live through judging
├── docs/
│ ├── ARCHITECTURE.md # Deep dive — timing, B2 layout, lifecycle, provenance manifests
│ └── adr/ # 12 Architecture Decision Records (all accepted)
├── Dockerfile # python:3.14-slim + FFmpeg + uv
├── railway.json # Railway backend deployment
├── render.yaml # Render backend deployment (alternative)
├── CLAUDE.md # Claude Code project context
└── .github/workflows/ci.yml # Lint → type-check → test (≥80% coverage gate) → Codecov
Production & Quality
push → ruff lint → mypy type-check → pytest (≥80% coverage gate) → Codecov
See .github/workflows/ci.yml. Coverage sits at 99%; the API is rate-limited (5 uploads/hr/IP), emits structured JSON logs via structlog, wraps every media call in tenacity retries (3 attempts, exponential backoff), and exposes a /health endpoint.
Load & Resilience
tests/load/k6_smoke.js ramps 20 concurrent users against /health (threshold: p95 < 500 ms), exercises the B2 fallback 404 path, and verifies the rate limiter answers /generate abuse with graceful 429s (never 5xx):
winget install k6 # or: brew install k6
k6 run tests/load/k6_smoke.js # local backend
k6 run -e BASE_URL=https://<railway-url> tests/load/k6_smoke.js # production
Future Roadmap
Shipped: every planned workstream is implemented and live — one-click Plaid ingestion alongside CSV upload, the narrative LLM routed through Genblaze → GMI Cloud with automatic NVIDIA NIM fallback (3 of 4 AI steps now go through Genblaze), B2 as the source of truth with a SHA-256 per artifact and a committed lifecycle rule, a memory-bounded FFmpeg compositor, and 99% test coverage. Full history in the CHANGELOG.
What's next — from demo to product:
- Multi-period recaps — monthly and quarterly stories, not just annual, with trend detection across periods
- White-label widget — an embeddable drop-in banks and fintechs ship inside their own apps (the core go-to-market)
- Richer personalities + peer benchmarking — more archetypes and "how you compare" framing
- Broader ingestion — PDF statement parsing (Azure Document Intelligence) and live Plaid production access beyond sandbox
- Goal Tracking Agent — savings-milestone and debt-payoff detection woven into the narrative
- Deeper generative media — animated scene clips (Genblaze → Runway) and an optional AI banker avatar (HeyGen)
Disclaimer
All transaction data used in development and demonstration is fully synthetic. No real customer data or PII is processed, stored, or transmitted. Not affiliated with any bank or financial institution. Not financial advice.
Built by Arjun Ganesh for the Backblaze Generative Media Hackathon 2026.






Log in or sign up for Devpost to join the conversation.