"""Interactive Gradio demo for the encoder-on-LFM2.5 transaction model.
Three-tab structure (Demo / Why Liquid / Integration). Demo is the
landing tab so a customer immediately sees what the model does; Why
Liquid is the architectural pitch; Integration is the build-it-yourself
playbook. Same Gradio theme + CSS as the rest of Liquid's customer-
facing demos for visual consistency.
The demo is intentionally self-contained: no side-by-side comparison
against an alternative architecture, no per-tab references to other
work. The argument is the architecture pattern itself — encoder +
frozen LFM2.5 backbone + LoRA + multi-head — and the reader gets a
clean read of it.
Usage:
python -m encoder.src.demo.app \\
--checkpoint encoder/experiments/.../step_004999.pt \\
--model-config encoder/configs/model_nocompress.yaml \\
--schema data/schema.yaml \\
--data-dir data/synthetic \\
--port 7860
"""
from __future__ import annotations
import argparse
import time
from pathlib import Path
import gradio as gr
import torch
from src.data.schema import load_schema
from src.demo.app import DemoData # reuse parent's curated-test-set loader
from src.demo.decode import TransactionDecoder
from src.demo.merchant_catalog import DemoMerchantCatalog
from src.demo.profile_inference import format_profile_html, infer_profile
from encoder.src.demo.cold_start import render_cold_start
from encoder.src.demo.inference import EncoderDemoModel
from encoder.src.demo.render import (
format_amount_predictions,
format_fraud_score,
format_mcc_predictions,
format_merchant_predictions,
format_timeline,
render_encoder_integration,
render_why_encoder,
)
# ---------------------------------------------------------------------------
# Header
# ---------------------------------------------------------------------------
_HEADER_HTML = """
Encoder on LFM2.5 — Transaction Foundation Model
Liquid AI · LFM2.5-350M base · Encoder + LoRA + multi-head
"""
# Container width applied to every tab's content so the three surfaces
# read at the same width. Kept in sync with render.py's _CONTAINER_WIDTH.
_CONTAINER_WIDTH = "1180px"
# ---------------------------------------------------------------------------
# Demo tab intro — standalone, no comparison framing
# ---------------------------------------------------------------------------
_DEMO_INTRO_HTML = f"""
Live multi-head inference on a frozen LFM2.5-350M backbone
Pick a curated customer archetype, click Run Inference, watch four task
heads predict in parallel from one shared backbone — fraud probability,
next-merchant, amount bucket, and merchant category (MCC). The encoder turns
the 64-transaction history into 960 pseudo-tokens; the frozen LFM2.5-350M
backbone with LoRA processes them; per-task heads pool the hidden states.
For the architectural rationale see Why Liquid; for the build-it-yourself
playbook see Integration.
"""
# ---------------------------------------------------------------------------
# CSS — scoped to the encoder demo
# ---------------------------------------------------------------------------
_CSS = f"""
/* Force light mode regardless of system preference */
:root, .dark {{ color-scheme: light !important; }}
.gradio-container {{
background: #f5f5f5 !important;
max-width: 1280px !important;
margin: auto !important;
padding: 1.5rem !important;
}}
/* Tabs: pill-style matching the rest of Liquid's customer-facing demos */
.tabs {{ background: transparent !important; }}
.tab-nav {{
background: #f5f5f5 !important;
border: none !important;
border-bottom: 1px solid rgba(0,0,0,0.1) !important;
gap: 4px !important;
padding: 4px 0 !important;
}}
.tab-nav button {{
font-weight: 500 !important;
font-size: 14px !important;
color: #737373 !important;
background: transparent !important;
border: none !important;
border-bottom: 2px solid transparent !important;
padding: 8px 16px !important;
border-radius: 0 !important;
transition: all 0.15s ease !important;
}}
.tab-nav button:hover {{
color: #171717 !important;
background: rgba(0,0,0,0.03) !important;
}}
.tab-nav button.selected {{
color: #171717 !important;
font-weight: 600 !important;
border-bottom: 2px solid #171717 !important;
background: transparent !important;
}}
/* Run button: pill style, dark fill. Scoped by elem_id so it does not
bleed into Gradio's internal radio/checkbox