Banker's Wrapped

Banker's Wrapped — Your financial year, told as a story. · Backblaze Generative Media Hackathon 2026

CI Codecov Release License: MIT Watch Video

Agent Pattern Genblaze GMI Cloud NVIDIA NIM OpenAI FFmpeg Pydantic structlog Plaid

Next.js React TypeScript Node.js Vercel Hobby

Python FastAPI Ruff pytest Railway Hobby

Backblaze B2 SQLite

Vercel live frontend Railway live API


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

  1. 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)
  2. Document Agent parses and normalizes transactions into typed records
  3. Analytics Agent calculates income, expenses, savings rate, and top spending categories
  4. Financial Personality is assigned: Builder · Optimizer · Explorer · Achiever
  5. 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
  6. Scene images generated via Genblaze → GMI Cloud (Seedream 4.0, 1344×768) — all 5 in parallel (with automatic 3× retry + exponential backoff)
  7. Voice narration synthesised via Genblaze → OpenAI TTS (tts-1, alloy voice) — concatenated scene text
  8. 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)
  9. 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

Banker's Wrapped architecture — User to FastAPI (rate-limit 5/hr) to the typed async Agent Pipeline to Backblaze B2 (14 files); the pipeline runs Document, Analytics, Narrative (Genblaze Chat on GMI Cloud, NIM fallback), and Media Agent, which fans out to GMI Cloud Seedream and OpenAI TTS before FFmpeg composes the final video

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

Layer Technology Role
Media Generation Genblaze Sole orchestrator for all AI media calls
Storage Backblaze B2 Structured artifact store + presigned delivery
Backend FastAPI Python Async agentic pipeline
Agent Pattern asyncio Hand-rolled typed async agent pipeline — no heavyweight framework (ADR-002)
LLM Genblaze NVIDIA NIM Narrative script — Genblaze SDK chat on GMI Cloud (openai/gpt-5.4-mini), automatic NVIDIA NIM fallback
Images GMI Cloud Scene visuals 1344×768, seedream-4-0-250828 (via Genblaze) — 5 parallel, retry ×3
Video Compose FFmpeg Scene images + narration → H.264/AAC MP4 (segment + concat, dip-to-black)
Frontend Next.js React Node.js Upload portal + video player
Session State Backblaze B2 SQLite B2 manifest is the durable record (ADR-008); SQLite is a fast read cache
Hosting Railway Vercel Backend on Railway · Frontend on Vercel
Observability structlog Structured request + agent logging

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

Open in Jupyter

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
Upload Portal B2 Bucket + Lifecycle
Connect a Bank via Plaid Session Folder
Generated Video Result Generation JSON Details

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.

Banker's Wrapped — One connection. Five scenes. Your financial story. · Powered by Genblaze on Backblaze B2

Built With

  • backblaze-b2
  • fastapi
  • ffmpeg
  • genblaze-sdk
  • gmi-cloud-seedream
  • next.js
  • nvidia-nim-(llama-3.1-70b)
  • openai-tts
  • pytest
  • python
  • railway
  • sqlite
  • structlog
  • tenacity
  • vercel
Share this project:

Updates

Submission history