File size: 40,789 Bytes
083b138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
"""Encoder-specific render module.

Reuses parent's format functions (fraud bar, top-k predictions, timeline)
directly via import — those are model-agnostic. The encoder demo only
adds two pieces of new content:

1. `render_why_encoder()` — the architectural pitch for the encoder pattern
2. `render_encoder_integration()` — build-it-yourself integration guide

The fraud / merchant / amount / mcc / timeline / profile formatters are
unchanged from parent. We import them so the encoder demo's prediction
cards are visually consistent.
"""

from __future__ import annotations

# Re-export parent's format functions so encoder app.py can do a single
# import from this module.
from src.demo.render import (  # noqa: F401
    format_amount_predictions,
    format_fraud_score,
    format_mcc_predictions,
    format_merchant_predictions,
    format_timeline,
    format_topk_predictions,
)

# Liquid design tokens
_TEXT = "#171717"
_TEXT_MUTED = "#525252"
_TEXT_DIM = "#737373"
_BG_CARD = "#ffffff"
_BG_CARD_ALT = "#fafafa"
_BORDER = "rgba(0,0,0,0.1)"
_BORDER_SUBTLE = "rgba(0,0,0,0.05)"
_ACCENT_GREEN = "#10B981"
_ACCENT_BLUE = "#3B82F6"
_ACCENT_AMBER = "#F59E0B"
_ACCENT_PURPLE = "#7c3aed"
_RADIUS_CARD = "16px"
_RADIUS_SM = "8px"
_FONT_MONO = "JetBrains Mono, ui-monospace, SFMono-Regular, monospace"

# Single max-width applied to every tab's content so the three surfaces
# read at the same width. Picked to fit the Gradio container (1280px)
# with a small inset on either side.
_CONTAINER_WIDTH = "1180px"


def render_why_encoder() -> str:
    """Why Liquid tab content.

    Opens with the buyer's problem (multi-customer / multi-task
    transaction-FM economics), then the published precedent
    (LFM2.5-Audio / LFM2.5-VL), then the architectural and operational
    properties, then scope-of-claim. Written for an external audience —
    no internal codenames, no design-log register, no "we claim" framing.
    """

    def _table_header(cols: list[str]) -> str:
        ths = ""
        for i, c in enumerate(cols):
            align = "right" if i > 0 else "left"
            ths += (
                f'<th style="padding: 6px 10px; font-size: 10px; color: {_TEXT_DIM};'
                f' text-transform: uppercase; letter-spacing: 0.05em; text-align: {align};'
                f' font-weight: 600;">{c}</th>'
            )
        return f"<tr style='border-bottom: 1px solid {_BORDER};'>{ths}</tr>"

    def _table_row(cells: list[str], highlight: bool = False) -> str:
        bg = "background: rgba(16,185,129,0.05);" if highlight else ""
        tds = ""
        for i, c in enumerate(cells):
            align = "right" if i > 0 else "left"
            tds += (
                f'<td style="padding: 6px 10px; font-family: {_FONT_MONO}; font-size: 11px;'
                f' color: {_TEXT}; text-align: {align};">{c}</td>'
            )
        return f"<tr style='border-bottom: 1px solid {_BORDER_SUBTLE}; {bg}'>{tds}</tr>"

    return f"""
    <div style="max-width: {_CONTAINER_WIDTH}; margin: 0 auto; padding: 16px;
                font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;">

      <!-- Lead -->
      <h2 style="margin: 0 0 4px 0; color: {_TEXT}; font-size: 22px; font-weight: 700;
                 letter-spacing: -0.02em;">
        Why LFM2.5 for Your Transaction Foundation Model
      </h2>
      <p style="color: {_TEXT_DIM}; font-size: 13px; margin: 0 0 24px 0; line-height: 1.5;">
        If you are putting a transaction foundation model into production &mdash;
        especially across more than one business unit, customer, or downstream task &mdash;
        the architectural choice determines per-customer training cost,
        time-to-first-production-task, and the marginal cost of adding the second
        and third tasks. The encoder-on-pretrained-backbone architecture applies
        a recipe Liquid AI already ships in LFM2.5-Audio and LFM2.5-VL to
        discrete-feature payment sequences. Three properties, each with a
        different kind of evidence.
      </p>

      <!-- 1. Recipe is validated -->
      <div style="padding: 16px 20px; background: {_BG_CARD}; border: 1px solid {_BORDER};
                  border-radius: {_RADIUS_CARD}; margin-bottom: 12px;">
        <h3 style="color: {_TEXT}; margin: 0 0 4px 0; font-size: 15px; font-weight: 600;">
          1. The Recipe Is Already Shipping for Two Other Modalities
        </h3>
        <p style="color: {_TEXT_MUTED}; font-size: 13px; line-height: 1.6; margin: 0 0 10px 0;">
          A small per-modality encoder produces continuous embeddings; a projection
          adapter (when needed) maps them into the LFM2.5 text backbone's hidden space;
          LoRA adapts the attention layers per customer. LFM2.5-Audio ingests waveforms
          this way. LFM2.5-VL ingests vision patches this way. This demo applies the
          same shape to discrete transaction tokens.
        </p>
        <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px;">
          <div style="padding: 10px; background: {_BG_CARD_ALT}; border-radius: 8px;">
            <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_ACCENT_BLUE};
                        font-weight: 600; margin-bottom: 4px;">LFM2.5-AUDIO</div>
            <div style="font-size: 11px; color: {_TEXT_DIM}; line-height: 1.4;">
              Audio encoder → projection → LFM2.5 backbone. Ships in production.
            </div>
          </div>
          <div style="padding: 10px; background: {_BG_CARD_ALT}; border-radius: 8px;">
            <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_ACCENT_BLUE};
                        font-weight: 600; margin-bottom: 4px;">LFM2.5-VL</div>
            <div style="font-size: 11px; color: {_TEXT_DIM}; line-height: 1.4;">
              Vision encoder → projection → LFM2.5 backbone. Ships at multiple sizes.
            </div>
          </div>
          <div style="padding: 10px; background: rgba(16,185,129,0.06);
                      border: 1px solid rgba(16,185,129,0.2); border-radius: 8px;">
            <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_ACCENT_GREEN};
                        font-weight: 600; margin-bottom: 4px;">TRANSACTIONS (THIS DEMO)</div>
            <div style="font-size: 11px; color: {_TEXT_DIM}; line-height: 1.4;">
              Structured encoder → frozen LFM2.5-350M + LoRA → multi-head outputs.
            </div>
          </div>
        </div>
      </div>

      <!-- 2. Serving cost / backbone speed -->
      <div style="padding: 16px 20px; background: {_BG_CARD}; border: 1px solid {_BORDER};
                  border-radius: {_RADIUS_CARD}; margin-bottom: 12px;">
        <h3 style="color: {_TEXT}; margin: 0 0 4px 0; font-size: 15px; font-weight: 600;">
          2. The Backbone Serves at Production Latency
        </h3>
        <p style="color: {_TEXT_MUTED}; font-size: 13px; line-height: 1.6; margin: 0 0 12px 0;">
          LFM2.5's conv-dominant layer stack gives O(N) prefill scaling on most layers
          where a pure-attention model pays O(N&sup2;). Published hardware-in-the-loop
          benchmarks from the LFM2 technical report, S25 / 4K context:
        </p>
        <table style="width: 100%; border-collapse: collapse; margin-bottom: 8px;">
          {_table_header(["Model", "Prefill (tok/s)", "Decode (tok/s)"])}
          {_table_row(["LFM2-2.6B", "<b>116</b>", "<b>30.0</b>"], highlight=True)}
          {_table_row(["Qwen3-4B", "35", "11.4"])}
          {_table_row(["Llama-3.2-3B", "51", "15.8"])}
        </table>
        <p style="font-size: 11px; color: {_TEXT_DIM}; margin: 0; line-height: 1.5;">
          Your serving path is the published LFM2.5 backbone unchanged — only
          the input side differs from a text deployment. The published latency
          advantage transfers directly.
          Source: <a href="https://arxiv.org/abs/2511.23404" style="color: {_TEXT_DIM};
          text-decoration: underline;">LFM2 Technical Report, arXiv 2511.23404</a>.
        </p>
      </div>

      <!-- 3. Frozen base + LoRA is the local maximum -->
      <div style="padding: 16px 20px; background: {_BG_CARD}; border: 1px solid {_BORDER};
                  border-radius: {_RADIUS_CARD}; margin-bottom: 12px;">
        <h3 style="color: {_TEXT}; margin: 0 0 4px 0; font-size: 15px; font-weight: 600;">
          3. Frozen Backbone + LoRA Is the Higher-Quality Configuration at Typical Label Budgets
        </h3>
        <p style="color: {_TEXT_MUTED}; font-size: 13px; line-height: 1.6; margin: 0 0 10px 0;">
          Freezing the LFM2.5 backbone and adapting it with LoRA produces higher
          quality than unfreezing the full backbone end-to-end at typical finserv
          label budgets. LoRA&rsquo;s low-rank update structure acts as effective
          regularization; lifting that constraint lets the backbone memorize the
          training labels rather than generalize. <b>The frozen-backbone commitment
          is not a quality compromise &mdash; it is the higher-quality operating
          point.</b>
        </p>
        <table style="width: 100%; border-collapse: collapse; margin-bottom: 6px;">
          {_table_header(["Configuration", "Trainable", "Fraud ROC-AUC", "MCC top-1"])}
          {_table_row(["Frozen backbone + LoRA (this demo)", "~16M", "<b>0.951</b>", "<b>40.5%</b>"],
                      highlight=True)}
          {_table_row(["Full backbone unfreeze", "~370M", "0.900", "38.1%"])}
        </table>
        <p style="font-size: 11px; color: {_TEXT_DIM}; margin: 0; line-height: 1.5;">
          Measured on 200K synthetic sequences (64 transactions × 15 features each).
          At ~16M trainable parameters (encoder + LoRA + heads), per-customer
          adaptation is small relative to the deployed footprint and completes
          in hours, not days.
        </p>
      </div>

      <!-- 4. Multi-head, multi-customer -->
      <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px;">
        <div style="padding: 16px 20px; background: {_BG_CARD}; border: 1px solid {_BORDER};
                    border-radius: {_RADIUS_CARD};">
          <h3 style="color: {_TEXT}; margin: 0 0 4px 0; font-size: 15px; font-weight: 600;">
            One Backbone, Many Heads
          </h3>
          <p style="color: {_TEXT_MUTED}; font-size: 12px; line-height: 1.6; margin: 0 0 8px 0;">
            A single forward pass through the backbone produces hidden states that
            four task heads pool independently — fraud detection, next-merchant
            prediction, amount-bucket forecasting, MCC classification. New
            use-cases (disputes, authorization optimization, AML) add a head, not
            a foundation model.
          </p>
          <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_TEXT_DIM};
                      padding: 6px 8px; background: {_BG_CARD_ALT}; border-radius: 6px;">
            Per-head MLP: ~0.5M params. Add a new task in hours.
          </div>
        </div>

        <div style="padding: 16px 20px; background: {_BG_CARD}; border: 1px solid {_BORDER};
                    border-radius: {_RADIUS_CARD};">
          <h3 style="color: {_TEXT}; margin: 0 0 4px 0; font-size: 15px; font-weight: 600;">
            One Backbone, Many Customers
          </h3>
          <p style="color: {_TEXT_MUTED}; font-size: 12px; line-height: 1.6; margin: 0 0 8px 0;">
            The pretrained LFM2.5 weights ship once. Per-customer training is the
            encoder + LoRA + heads — under 5% of base size in bf16 slim format.
            Adding a customer is loading new artifacts on top of the cached
            backbone, not retraining from scratch.
          </p>
          <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_TEXT_DIM};
                      padding: 6px 8px; background: {_BG_CARD_ALT}; border-radius: 6px;">
            Slim per-customer artifact: ~30 MB bf16 at LFM2.5-1.2B scale.
          </div>
        </div>
      </div>

      <!-- 5. Architectural fit -->
      <div style="padding: 16px 20px; background: {_BG_CARD}; border: 1px solid {_BORDER};
                  border-radius: {_RADIUS_CARD}; margin-bottom: 12px;">
        <h3 style="color: {_TEXT}; margin: 0 0 4px 0; font-size: 15px; font-weight: 600;">
          The Architecture Matches Transaction Data Structure
        </h3>
        <p style="color: {_TEXT_MUTED}; font-size: 13px; line-height: 1.6; margin: 0 0 8px 0;">
          Transaction data is information-dense locally (within-transaction
          feature correlations, adjacent-transaction continuity) with sparse
          long-range signal (behavioral baselines across the full history).
          LFM2.5 allocates O(N) conv to the dense local patterns and O(N&sup2;)
          attention to the sparse global ones. A pure transformer would spend
          O(N&sup2;) compute uniformly.
        </p>
        <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px;">
          <div style="padding: 10px; background: {_BG_CARD_ALT}; border-radius: 8px;">
            <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_TEXT};
                        font-weight: 600; margin-bottom: 4px;">Within Transaction</div>
            <div style="font-size: 11px; color: {_TEXT_DIM}; line-height: 1.4;">
              Merchant determines MCC. Entry mode correlates with amount. Dense,
              local, often deterministic. A 3-wide conv kernel captures this.
            </div>
          </div>
          <div style="padding: 10px; background: {_BG_CARD_ALT}; border-radius: 8px;">
            <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_TEXT};
                        font-weight: 600; margin-bottom: 4px;">Adjacent Transactions</div>
            <div style="font-size: 11px; color: {_TEXT_DIM}; line-height: 1.4;">
              Strong temporal continuity. A customer at Starbucks at 8am is
              likely at a similar merchant tomorrow. Local conv handles it.
            </div>
          </div>
          <div style="padding: 10px; background: {_BG_CARD_ALT}; border-radius: 8px;">
            <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_TEXT};
                        font-weight: 600; margin-bottom: 4px;">Distant Transactions</div>
            <div style="font-size: 11px; color: {_TEXT_DIM}; line-height: 1.4;">
              Weak but non-zero signal. Behavioral profile matters for fraud
              baseline. This is where attention earns its quadratic cost.
            </div>
          </div>
        </div>
      </div>

      <!-- 6. Data sovereignty -->
      <div style="padding: 16px 20px; background: {_BG_CARD}; border: 1px solid {_BORDER};
                  border-radius: {_RADIUS_CARD}; margin-bottom: 12px;">
        <h3 style="color: {_TEXT}; margin: 0 0 4px 0; font-size: 15px; font-weight: 600;">
          Your Data, Your Model, Your Infrastructure
        </h3>
        <p style="color: {_TEXT_MUTED}; font-size: 13px; line-height: 1.6; margin: 0;">
          LFM2.5 base weights are open. Liquid licenses the architecture, training
          recipe, and engineering support. Customers train on their proprietary
          data behind their firewall. No data leaves customer infrastructure.
          No dependency on external model APIs. The result is a foundation model
          the customer owns, adapted to their transaction distribution.
        </p>
      </div>

      <!-- Scope: what this demo validates / what a POC would establish -->
      <div style="padding: 16px 20px; background: {_BG_CARD}; border: 1px solid {_BORDER};
                  border-radius: {_RADIUS_CARD}; margin-bottom: 16px;">
        <h3 style="color: {_TEXT}; margin: 0 0 8px 0; font-size: 14px; font-weight: 600;">
          Scope of Claim
        </h3>
        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; font-size: 12px;">
          <div>
            <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_ACCENT_GREEN};
                        font-weight: 600; margin-bottom: 6px; text-transform: uppercase;
                        letter-spacing: 0.05em;">What this demo validates</div>
            <ul style="margin: 0; padding-left: 14px; color: {_TEXT_MUTED}; line-height: 1.6;">
              <li>The encoder-on-pretrained-backbone architecture used by LFM2.5-Audio
                  and LFM2.5-VL applies to discrete-feature transaction sequences
                  without modifying the transformers library.</li>
              <li>Per-customer training touches ~2&ndash;5% of the deployed footprint
                  and trains in hours rather than days.</li>
              <li>On synthetic data, frozen-backbone-plus-LoRA outperforms
                  full-backbone unfreezing on every measured head.</li>
              <li>One pretrained backbone serves all task heads and is identical
                  across customer deployments.</li>
            </ul>
          </div>
          <div>
            <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_ACCENT_AMBER};
                        font-weight: 600; margin-bottom: 6px; text-transform: uppercase;
                        letter-spacing: 0.05em;">What a POC on your data would establish</div>
            <ul style="margin: 0; padding-left: 14px; color: {_TEXT_MUTED}; line-height: 1.6;">
              <li>Whether synthetic-data quality numbers reproduce on your
                  transaction distribution.</li>
              <li>Production-scale quality at LFM2.5-1.2B on your hardware and
                  sequence lengths (this demo runs at LFM2.5-350M).</li>
              <li>Inference latency against your authorization-decision budget
                  at your concurrency.</li>
              <li>Cross-customer or cross-business-unit transfer of the encoder
                  and LoRA artifacts.</li>
            </ul>
          </div>
        </div>
      </div>

      <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_TEXT_DIM}; text-align: center;">
        Architecture: <a href="https://arxiv.org/abs/2511.23404" style="color: {_TEXT_DIM};
        text-decoration: underline;">arXiv 2511.23404</a> &middot;
        Weights: <a href="https://huggingface.co/LiquidAI" style="color: {_TEXT_DIM};
        text-decoration: underline;">huggingface.co/LiquidAI</a>
      </div>
    </div>
    """


def render_encoder_integration() -> str:
    """Build-it-yourself integration guide.

    Walks the reader through every component a customer team would build
    to reproduce this demo on their own data: preprocessing, encoder,
    backbone wiring, heads, postprocessing, training, deployment.
    Includes hyperparameter cards, gotchas, and an engagement timeline.
    """

    def _phase_card(num: str, title: str, body: str, detail: str) -> str:
        return f"""
        <div style="padding: 14px 16px; background: {_BG_CARD}; border: 1px solid {_BORDER};
                    border-radius: {_RADIUS_CARD};">
          <div style="display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px;">
            <span style="font-family: {_FONT_MONO}; font-size: 11px; color: {_TEXT_DIM};
                         font-weight: 600;">{num}</span>
            <span style="font-size: 14px; font-weight: 600; color: {_TEXT};">{title}</span>
          </div>
          <p style="font-size: 12px; color: {_TEXT_MUTED}; line-height: 1.5; margin: 0 0 8px 0;">
            {body}</p>
          <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_TEXT_DIM};
                      padding: 6px 8px; background: {_BG_CARD_ALT}; border-radius: 6px;
                      line-height: 1.5;">
            {detail}</div>
        </div>"""

    def _gotcha(num: str, title: str, desc: str) -> str:
        return f"""
        <div style="display: flex; gap: 8px; padding: 5px 0;
                    border-bottom: 1px solid {_BORDER_SUBTLE};">
          <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_TEXT_DIM};
                      font-weight: 600; min-width: 18px;">{num}.</div>
          <div>
            <span style="font-size: 12px; font-weight: 600; color: {_TEXT};">{title}</span>
            <span style="font-size: 12px; color: {_TEXT_MUTED};"> — {desc}</span>
          </div>
        </div>"""

    def _pill(text: str) -> str:
        return (
            f'<span style="padding: 5px 12px; background: {_TEXT}; color: #fff;'
            f' border-radius: 9999px; font-family: {_FONT_MONO};'
            f' font-size: 10px; font-weight: 600;">{text}</span>'
        )

    arrow = f'<span style="color: {_TEXT_DIM}; font-size: 12px;">&rarr;</span>'

    return f"""
    <div style="max-width: {_CONTAINER_WIDTH}; margin: 0 auto; padding: 16px;
                font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;">

      <h2 style="margin: 0 0 4px 0; color: {_TEXT}; font-size: 22px; font-weight: 700;
                 letter-spacing: -0.02em;">
        Integration Architecture
      </h2>
      <p style="color: {_TEXT_DIM}; font-size: 13px; margin: 0 0 20px 0; line-height: 1.5;">
        How a customer team builds this stack end to end. Six components, three
        ship from Liquid (LFM2.5 base weights, training recipes, architecture
        support); three are customer-bespoke (schema, encoder, task heads).
        Per-customer adaptation is one ML engineer for a few weeks, not a
        research project.
      </p>

      <!-- Pipeline flow -->
      <div style="display: flex; align-items: center; justify-content: center; gap: 6px;
                  margin-bottom: 24px; padding: 10px 0; flex-wrap: wrap;">
        {_pill("Preprocess")}{arrow}
        {_pill("Encode")}{arrow}
        {_pill("Backbone + LoRA")}{arrow}
        {_pill("Heads")}{arrow}
        {_pill("Postprocess")}{arrow}
        {_pill("Deploy")}
      </div>

      <!-- Phase cards -->
      <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 20px;">

        {_phase_card(
            "1",
            "Schema & Preprocessing",
            "Define the discrete feature schema first — features, vocab sizes, ordering. "
            "Categorical features (merchant_id, MCC, country) map directly to integer IDs. "
            "Continuous features (amount, days-since-last) get quantile-bucketed into N bins. "
            "High-cardinality features (10K+ merchants) have their long tail bucketed or "
            "factored. Reserve 3 token IDs per feature for MASK / OOV / NULL. "
            "Final per-customer batch shape: (B, T_tx, F).",
            "Sequence: 64 tx &times; 15 feat = 960 tokens &nbsp;|&nbsp; "
            "amount → 16 quantile bins &nbsp;|&nbsp; "
            "merchant_id top-10K + frequency bucketing for the tail &nbsp;|&nbsp; "
            "unseen values at inference → OOV (ID 1)"
        )}

        {_phase_card(
            "2",
            "Structured Encoder",
            "One embedding table per feature (sized to its vocab) plus a shared "
            "feature-type table. Value + type embeddings are summed to identify "
            "which feature each token represents. The 15 per-tx feature embeddings "
            "are kept as separate positions in the sequence — compressing them to "
            "one token per transaction collapses fraud quality (fraud ROC-AUC drops "
            "to 0.535 on this demo's data), because the per-tx MLP averages away the "
            "intra-tx feature combinations fraud depends on. This is the same shape "
            "as the audio and vision encoders' input embedding step.",
            "Output shape: (B, T_tx*F, d_lfm) = (B, 960, 1024) at LFM2.5-350M &nbsp;|&nbsp; "
            "value_tables[f](token) + type_table(f) &nbsp;|&nbsp; "
            "Encoder params dominated by high-cardinality value tables (~14M at 350M)"
        )}

        {_phase_card(
            "3",
            "Projection Adapter (When Needed)",
            "When the encoder's output dimension matches d_lfm directly, no adapter "
            "is needed — the encoder outputs flow straight into the backbone. When "
            "d_encoder &lt; d_lfm (typical at LFM2.5-1.2B where d_lfm=2048), a single "
            "linear projection lifts the encoder output into the backbone hidden space, "
            "exactly mirroring the audio/VL projection adapter. Layer init: identity "
            "for d_encoder=d_lfm, Xavier for the projection case.",
            "350M: d_lfm=1024, d_encoder=1024, no adapter (identity) &nbsp;|&nbsp; "
            "1.2B: d_lfm=2048, project from d_encoder=512-1024 → 2048 &nbsp;|&nbsp; "
            "Adds ~2M params at 1.2B scale"
        )}

        {_phase_card(
            "4",
            "Backbone + LoRA",
            "Load the pretrained LFM2.5 base from Hugging Face. The backbone&rsquo;s "
            "parameters are excluded from the optimizer&rsquo;s parameter set during "
            "training — gradients flow through the backbone to update the upstream "
            "encoder and downstream heads, but the backbone&rsquo;s own weights are "
            "never modified. Forward pass executes through all 354M backbone "
            "parameters at full capacity, at both training and inference time. "
            "Customer-distribution adaptation enters through (i) LoRA&rsquo;s low-rank "
            "delta on the attention projections (q_proj / k_proj / v_proj / out_proj) "
            "and (ii) the per-feature encoder, both trained from scratch on customer "
            "labels. Encoder outputs are injected via the published "
            "<code>inputs_embeds</code> hook in <code>Lfm2Model.forward</code>. "
            "Adding LoRA to the conv layers does not improve quality enough to justify the ~50% increase in training cost; attention-only LoRA is the recommended starting configuration.",
            "Backbone params excluded from optimizer; backbone forward at full capacity &nbsp;|&nbsp; "
            "LoRA r=16, &alpha;=32, dropout 0.05 on q_proj / k_proj / v_proj / out_proj &nbsp;|&nbsp; "
            "PEFT wraps the leaf modules &nbsp;|&nbsp; "
            "~1M LoRA params at 350M, ~2M at 1.2B"
        )}

        {_phase_card(
            "5",
            "Task Heads",
            "Per-task downstream heads pool backbone hidden states and predict via "
            "small MLPs. Fraud (BCE loss) pools the last-transaction stripe — "
            "mean of positions T-F..T (positions 945..959). Categorical heads "
            "(next-merchant, amount-bucket, MCC) use cross-entropy and pool the "
            "<i>pre-last</i> transaction stripe (positions 930..944) to avoid "
            "leaking the prediction target. New tasks add a head, backbone "
            "untouched.",
            "Per-head MLP: 128 hidden, dropout 0.1 &nbsp;|&nbsp; "
            "Pool: <code>last_tx_mean</code> for sequence tasks &nbsp;|&nbsp; "
            "Pool: <code>pre_last_tx_mean</code> for next-tx tasks &nbsp;|&nbsp; "
            "~0.5M params per head"
        )}

        {_phase_card(
            "6",
            "Postprocessing",
            "Fraud logits → sigmoid → probability in [0, 1]; calibrate against the "
            "customer's operational threshold (typical: 70% precision @ 60% recall "
            "for review-queue handoff). Categorical logits → softmax → top-k "
            "distribution. Use the predicted distribution for downstream "
            "decisioning, not just argmax — the runner-up matters when the top-1 "
            "is uncertain. Behavioral attribution: gradient-based saliency on the "
            "per-feature embeddings identifies which input features drove the score.",
            "Fraud: sigmoid(logits) → operational threshold &nbsp;|&nbsp; "
            "Categorical: softmax(logits) → top-k + calibration &nbsp;|&nbsp; "
            "Saliency: ∂loss/∂value_embed identifies driving features"
        )}
      </div>

      <!-- Training recipe (full-width card) -->
      <div style="padding: 16px 20px; background: {_BG_CARD}; border: 1px solid {_BORDER};
                  border-radius: {_RADIUS_CARD}; margin-bottom: 16px;">
        <h3 style="color: {_TEXT}; margin: 0 0 8px 0; font-size: 15px; font-weight: 600;">
          Training Recipe
        </h3>
        <p style="color: {_TEXT_MUTED}; font-size: 13px; line-height: 1.6; margin: 0 0 12px 0;">
          Single-stage supervised fine-tune on the customer&rsquo;s labelled data — no
          separate pretraining stage. Three trainable parameter groups (LoRA delta,
          per-feature encoder, task heads), three learning rates, because each group
          differs in initialization, parameter scale, and gradient-norm profile.
        </p>
        <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px;
                    margin-bottom: 12px;">
          <div style="padding: 12px; background: {_BG_CARD_ALT}; border-radius: 8px;">
            <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_ACCENT_BLUE};
                        font-weight: 600; margin-bottom: 6px; letter-spacing: 0.05em;">
              LORA GROUP</div>
            <div style="font-family: {_FONT_MONO}; font-size: 11px; color: {_TEXT};
                        margin-bottom: 4px;">
              lr = 1e-3 &middot; ~1M params
            </div>
            <div style="font-size: 12px; color: {_TEXT_MUTED}; line-height: 1.5;">
              Low-rank adapters on the backbone&rsquo;s attention projections.
              Initialized so the LoRA path contributes zero at step 0, then steers
              attention behavior toward the customer&rsquo;s distribution. Higher LR
              than the encoder group is fine — the low-rank constraint regularizes
              the update by construction.
            </div>
          </div>
          <div style="padding: 12px; background: {_BG_CARD_ALT}; border-radius: 8px;">
            <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_ACCENT_BLUE};
                        font-weight: 600; margin-bottom: 6px; letter-spacing: 0.05em;">
              ENCODER GROUP</div>
            <div style="font-family: {_FONT_MONO}; font-size: 11px; color: {_TEXT};
                        margin-bottom: 4px;">
              lr = 3e-4 &middot; ~14M params
            </div>
            <div style="font-size: 12px; color: {_TEXT_MUTED}; line-height: 1.5;">
              Per-feature value tables + feature-type table, from random init on the
              customer&rsquo;s tokenized vocabulary. Lower LR than LoRA because
              random-init embedding matrices destabilize at higher rates;
              high-cardinality tables (10K-vocab merchant) dominate gradient norm if
              not damped.
            </div>
          </div>
          <div style="padding: 12px; background: {_BG_CARD_ALT}; border-radius: 8px;">
            <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_ACCENT_BLUE};
                        font-weight: 600; margin-bottom: 6px; letter-spacing: 0.05em;">
              HEADS GROUP</div>
            <div style="font-family: {_FONT_MONO}; font-size: 11px; color: {_TEXT};
                        margin-bottom: 4px;">
              lr = 1e-3 &middot; ~2M params
            </div>
            <div style="font-size: 12px; color: {_TEXT_MUTED}; line-height: 1.5;">
              Per-task MLPs (fraud, next-merchant, amount-bucket, MCC), from random
              init. Higher LR is fine — small per-head parameter count, well-conditioned
              loss surface. New downstream tasks attach as additional heads without
              retraining the backbone or the encoder.
            </div>
          </div>
        </div>
        <div style="display: grid; grid-template-columns: auto 1fr; gap: 6px 16px;
                    font-size: 12px; padding: 10px 12px; background: {_BG_CARD_ALT};
                    border-radius: 8px;">
          <div style="font-family: {_FONT_MONO}; color: {_TEXT_DIM};">Optimizer</div>
          <div style="color: {_TEXT_MUTED};">
            AdamW, &beta; = (0.9, 0.95), weight decay 0.1
          </div>
          <div style="font-family: {_FONT_MONO}; color: {_TEXT_DIM};">Schedule</div>
          <div style="color: {_TEXT_MUTED};">
            200-step linear warmup, cosine decay to 10% of peak over ~5K steps
          </div>
          <div style="font-family: {_FONT_MONO}; color: {_TEXT_DIM};">Precision</div>
          <div style="color: {_TEXT_MUTED};">
            bf16 forward and backward, fp32 loss accumulation
          </div>
          <div style="font-family: {_FONT_MONO}; color: {_TEXT_DIM};">Multi-task</div>
          <div style="color: {_TEXT_MUTED};">
            fraud 1.0, categorical heads 0.5 each — chosen to match per-task
            gradient norm in the first 200 warmup steps
          </div>
          <div style="font-family: {_FONT_MONO}; color: {_TEXT_DIM};">Compute</div>
          <div style="color: {_TEXT_MUTED};">
            ~2 hours end-to-end on a single A100 at LFM2.5-350M scale
          </div>
        </div>
      </div>

      <!-- Deployment card (full-width) -->
      <div style="margin-bottom: 16px;">
        {_phase_card(
            "Deploy",
            "Per-Customer Adapter on a Shared Backbone",
            "The deployable per-customer artifact is the trained LoRA delta + "
            "per-feature encoder + task heads. The LFM2.5 base is not included; "
            "it is loaded once per serving GPU from the public weights. At "
            "LFM2.5-350M the artifact is ~190 MB in bf16; the unstripped version "
            "including the base would be ~900 MB. Multi-tenant serving keeps "
            "one backbone resident on the GPU and switches the active LoRA "
            "delta + encoder per request, so a new customer adds a small "
            "adapter rather than a second foundation model. The conv-dominant "
            "backbone quantizes cleanly to INT8 for cost-sensitive deployments. "
            "Same code path runs CPU or GPU.",
            "Per-customer artifact: ~190 MB bf16 at LFM2.5-350M (LoRA + encoder + heads) &nbsp;|&nbsp; "
            "Multi-tenant: shared backbone forward + per-request LoRA switching &nbsp;|&nbsp; "
            "CPU (~5s/inference) or H100 (&lt;100ms), same code &nbsp;|&nbsp; "
            "INT8 quantization clean for conv layers"
        )}
      </div>

      <!-- Gotchas -->
      <div style="padding: 16px 20px; background: {_BG_CARD}; border: 1px solid {_BORDER};
                  border-radius: {_RADIUS_CARD}; margin-bottom: 20px;">
        <div style="font-size: 14px; font-weight: 600; color: {_TEXT}; margin-bottom: 8px;">
          Configuration Choices That Look Right and Aren&rsquo;t
        </div>
        {_gotcha("1", "Do not compress each transaction to one token",
                 "an MLP that averages the 15 feature embeddings into a single "
                 "vector destroys the intra-tx fraud signal. Fraud ROC-AUC "
                 "collapsed to 0.535. Keep the full T_tx*F stripe.")}
        {_gotcha("2", "Pool the pre-last transaction, not the last, for next-tx heads",
                 "pooling the last-tx stripe for next-merchant prediction "
                 "leaks the prediction target into the input. Use the prior tx.")}
        {_gotcha("3", "Don't unfreeze the backbone at typical label budgets",
                 "full-backbone unfreezing produces lower quality than frozen-plus-LoRA "
                 "(fraud ROC-AUC 0.951 → 0.900 on this demo's data). LoRA acts as "
                 "effective regularization; lifting it forces overfitting.")}
        {_gotcha("4", "Tied embedding heads need SSL-pretrained value tables",
                 "tying the next-merchant head to the encoder's merchant value table "
                 "without self-supervised pretraining of those tables reduces "
                 "next-merchant top-1 from 7.78% to 3.74%. Use a fresh MLP head "
                 "until SSL pretraining anchors the value tables.")}
        {_gotcha("5", "Average per-feature losses; do not sum",
                 "summing CE losses across features makes high-cardinality "
                 "features (10K-vocab merchant) dominate the gradient. The "
                 "low-cardinality features stop training.")}
        {_gotcha("6", "Match the schema fingerprint between training and inference",
                 "if the tokenizer's vocab changes between training and "
                 "deployment, the encoder's value tables index into a different "
                 "semantic space. Embed the fingerprint in checkpoint metadata.")}
      </div>

      <!-- Engagement model -->
      <div style="padding: 16px 20px; background: {_BG_CARD}; border: 1px solid {_BORDER};
                  border-radius: {_RADIUS_CARD}; margin-bottom: 16px;">
        <div style="font-size: 14px; font-weight: 600; color: {_TEXT}; margin-bottom: 10px;">
          Typical Engagement
        </div>
        <table style="width: 100%; border-collapse: collapse;">
          <tr style="border-bottom: 1px solid {_BORDER};">
            <th style="padding: 6px 10px; text-align: left; font-size: 10px;
                       color: {_TEXT_DIM}; text-transform: uppercase;
                       letter-spacing: 0.05em; font-weight: 600;">Phase</th>
            <th style="padding: 6px 10px; text-align: left; font-size: 10px;
                       color: {_TEXT_DIM}; text-transform: uppercase; font-weight: 600;">
              Duration</th>
            <th style="padding: 6px 10px; text-align: left; font-size: 10px;
                       color: {_TEXT_DIM}; text-transform: uppercase; font-weight: 600;">
              What Happens</th>
          </tr>
          <tr style="border-bottom: 1px solid {_BORDER_SUBTLE};">
            <td style="padding: 5px 10px; font-size: 12px; font-weight: 600;
                       color: {_TEXT};">Discovery</td>
            <td style="padding: 5px 10px; font-family: {_FONT_MONO}; font-size: 11px;
                       color: {_TEXT_MUTED};">1-2 weeks</td>
            <td style="padding: 5px 10px; font-size: 12px; color: {_TEXT_MUTED};">
              Schema design, data sample (~100K-1M sequences), compliance review,
              architectural fit assessment.
            </td>
          </tr>
          <tr style="border-bottom: 1px solid {_BORDER_SUBTLE};">
            <td style="padding: 5px 10px; font-size: 12px; font-weight: 600;
                       color: {_TEXT};">POC</td>
            <td style="padding: 5px 10px; font-family: {_FONT_MONO}; font-size: 11px;
                       color: {_TEXT_MUTED};">1 week</td>
            <td style="padding: 5px 10px; font-size: 12px; color: {_TEXT_MUTED};">
              Fine-tune encoder + LoRA + heads on customer sample, measurement
              report, go/no-go recommendation.
            </td>
          </tr>
          <tr style="border-bottom: 1px solid {_BORDER_SUBTLE};">
            <td style="padding: 5px 10px; font-size: 12px; font-weight: 600;
                       color: {_TEXT};">Production</td>
            <td style="padding: 5px 10px; font-family: {_FONT_MONO}; font-size: 11px;
                       color: {_TEXT_MUTED};">2-3 months</td>
            <td style="padding: 5px 10px; font-size: 12px; color: {_TEXT_MUTED};">
              Customer engineering team builds with Liquid architectural support,
              weekly design review, scale-up to LFM2.5-1.2B.
            </td>
          </tr>
          <tr>
            <td style="padding: 5px 10px; font-size: 12px; font-weight: 600;
                       color: {_TEXT};">Scale</td>
            <td style="padding: 5px 10px; font-family: {_FONT_MONO}; font-size: 11px;
                       color: {_TEXT_MUTED};">Ongoing</td>
            <td style="padding: 5px 10px; font-size: 12px; color: {_TEXT_MUTED};">
              Multi-task expansion (add heads), multi-tenant serving, retraining
              cadence, architecture evolution.
            </td>
          </tr>
        </table>
      </div>

      <div style="font-family: {_FONT_MONO}; font-size: 10px; color: {_TEXT_DIM}; text-align: center;">
        Architecture: <a href="https://arxiv.org/abs/2511.23404" style="color: {_TEXT_DIM};
        text-decoration: underline;">arXiv 2511.23404</a> &middot;
        Base weights: <a href="https://huggingface.co/LiquidAI" style="color: {_TEXT_DIM};
        text-decoration: underline;">huggingface.co/LiquidAI</a>
      </div>
    </div>
    """