Download encoder/src/demo/copilot_render_fraud_pattern.py from cdotsanghvi/lfm2-transaction-encoder: direct link, hf CLI and curl.
- Browser
- Download file 15 kB
-
https://huggingface.co/spaces/cdotsanghvi/lfm2-transaction-encoder/resolve/main/encoder/src/demo/copilot_render_fraud_pattern.py
- Command line
-
hf download hf://spaces/cdotsanghvi/lfm2-transaction-encoder/encoder/src/demo/copilot_render_fraud_pattern.py
-
curl -L -o copilot_render_fraud_pattern.py https://huggingface.co/spaces/cdotsanghvi/lfm2-transaction-encoder/resolve/main/encoder/src/demo/copilot_render_fraud_pattern.py
15 kB
| """HTML rendering for the Fraud Pattern Co-Pilot demo. | |
| The unique panel for Fraud is a two-distribution scoreboard: 5 stage | |
| bars + 4 type bars side-by-side, with stage and type recommendation | |
| pills above. Visual vocabulary matches dispute and collections. | |
| """ | |
| from __future__ import annotations | |
| import html | |
| from typing import Iterable | |
| import numpy as np | |
| from encoder.src.data.synthetic_fraud_pattern import ( | |
| NUM_STAGES, | |
| NUM_TYPES, | |
| STAGE_NAMES, | |
| TYPE_NAMES, | |
| ) | |
| from encoder.src.demo.copilot_inference_fraud_pattern import ( | |
| STAGE_COLORS, | |
| TYPE_COLORS, | |
| FraudPatternCastMember, | |
| FraudPatternResult, | |
| ) | |
| _PAGE_BG = "#fafafa" | |
| _INK = "#171717" | |
| _INK_DIM = "#525252" | |
| _BORDER = "rgba(0,0,0,0.08)" | |
| _CARD_BG = "#ffffff" | |
| def render_header() -> str: | |
| return f""" | |
| <div style="text-align: center; margin-bottom: 22px;"> | |
| <div style=" | |
| font-size: 11px; | |
| color: {_INK_DIM}; | |
| font-family: 'JetBrains Mono', ui-monospace, monospace; | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| margin-bottom: 6px; | |
| "> | |
| Liquid AI · LFM2.5-350M backbone · encoder + LoRA | |
| </div> | |
| <h1 style=" | |
| margin: 0; | |
| font-size: 28px; | |
| font-weight: 700; | |
| color: {_INK}; | |
| letter-spacing: -0.02em; | |
| "> | |
| Fraud Pattern Co-Pilot | |
| </h1> | |
| </div> | |
| """ | |
| def render_cast_strip( | |
| cast: list[FraudPatternCastMember], | |
| selected_idx: int, | |
| ) -> str: | |
| cards: list[str] = [] | |
| for i, m in enumerate(cast): | |
| is_sel = i == selected_idx | |
| accent = STAGE_COLORS[m.stage_label] | |
| border = accent if is_sel else _BORDER | |
| bg = "#ffffff" if not is_sel else _hex_with_alpha(accent, 0.06) | |
| cards.append(f""" | |
| <div style=" | |
| flex: 1 1 0; | |
| min-width: 0; | |
| padding: 12px 14px; | |
| background: {bg}; | |
| border: 1.5px solid {border}; | |
| border-radius: 10px; | |
| "> | |
| <div style=" | |
| font-size: 13px; | |
| font-weight: 600; | |
| color: {_INK}; | |
| margin-bottom: 4px; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| ">{html.escape(m.display_name)}</div> | |
| <div style=" | |
| font-size: 11px; | |
| color: {_INK_DIM}; | |
| font-family: 'JetBrains Mono', ui-monospace, monospace; | |
| text-transform: uppercase; | |
| letter-spacing: 0.04em; | |
| "> | |
| <span style="color: {accent};">●</span> | |
| {html.escape(m.stage_label_name)} / {html.escape(m.type_label_name)} | |
| </div> | |
| </div> | |
| """) | |
| return f""" | |
| <div style="display: flex; gap: 10px; margin-bottom: 18px;"> | |
| {''.join(cards)} | |
| </div> | |
| """ | |
| def render_context(member: FraudPatternCastMember) -> str: | |
| accent = STAGE_COLORS[member.stage_label] | |
| return f""" | |
| <div style=" | |
| background: {_CARD_BG}; | |
| border: 1px solid {_BORDER}; | |
| border-left: 4px solid {accent}; | |
| border-radius: 12px; | |
| padding: 22px 26px; | |
| margin-bottom: 18px; | |
| "> | |
| <div style=" | |
| font-size: 11px; | |
| color: {_INK_DIM}; | |
| font-family: 'JetBrains Mono', ui-monospace, monospace; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| margin-bottom: 10px; | |
| "> | |
| Fraud queue context · pattern: | |
| <span style="color: {_INK}; font-weight: 600;">{html.escape(member.pattern)}</span> | |
| </div> | |
| <div style=" | |
| font-size: 18px; | |
| color: {_INK}; | |
| line-height: 1.5; | |
| font-style: italic; | |
| "> | |
| “{html.escape(member.context_text)}” | |
| </div> | |
| <div style=" | |
| font-size: 13px; | |
| color: {_INK_DIM}; | |
| margin-top: 12px; | |
| line-height: 1.5; | |
| "> | |
| {html.escape(member.description)} | |
| </div> | |
| </div> | |
| """ | |
| def render_timeline( | |
| flagged_idx: int, | |
| top_k_positions: Iterable[int] | None = None, | |
| attribution_probs: Iterable[float] | None = None, | |
| num_positions: int = 64, | |
| ) -> str: | |
| top_k_set: set[int] = set() | |
| if top_k_positions is not None: | |
| top_k_set = {int(i) for i in top_k_positions} | |
| probs = list(attribution_probs) if attribution_probs is not None else None | |
| dots: list[str] = [] | |
| for i in range(num_positions): | |
| if i == flagged_idx: | |
| dots.append(_dot_flagged(i)) | |
| elif i in top_k_set: | |
| alpha = float(probs[i]) if probs is not None else 1.0 | |
| dots.append(_dot_attributed(i, alpha)) | |
| else: | |
| faint = float(probs[i]) * 0.4 if probs is not None else 0.0 | |
| dots.append(_dot_neutral(i, faint)) | |
| return f""" | |
| <div style=" | |
| background: {_CARD_BG}; | |
| border: 1px solid {_BORDER}; | |
| border-radius: 12px; | |
| padding: 20px 22px; | |
| margin-bottom: 18px; | |
| "> | |
| <div style=" | |
| font-size: 11px; | |
| color: {_INK_DIM}; | |
| font-family: 'JetBrains Mono', ui-monospace, monospace; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| margin-bottom: 14px; | |
| display: flex; | |
| justify-content: space-between; | |
| "> | |
| <span>64-transaction history · oldest → newest</span> | |
| <span> | |
| <span style="color: #ef4444;">★</span> flagged | |
| <span style="color: #f59e0b;">●</span> contributed | |
| </span> | |
| </div> | |
| <div style=" | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| gap: 2px; | |
| "> | |
| {''.join(dots)} | |
| </div> | |
| </div> | |
| """ | |
| def _dot_flagged(i: int) -> str: | |
| return f""" | |
| <div title="position {i} · flagged" style=" | |
| width: 14px; height: 14px; | |
| color: #ef4444; | |
| font-size: 18px; | |
| line-height: 14px; | |
| text-align: center; | |
| font-weight: 700; | |
| ">★</div> | |
| """ | |
| def _dot_attributed(i: int, alpha: float) -> str: | |
| glow = max(0.4, min(1.0, alpha)) | |
| return f""" | |
| <div title="position {i} · contributed ({alpha:.2f})" style=" | |
| width: 12px; height: 12px; | |
| border-radius: 50%; | |
| background: rgba(245, 158, 11, {glow}); | |
| box-shadow: 0 0 8px rgba(245, 158, 11, {glow * 0.7}); | |
| "></div> | |
| """ | |
| def _dot_neutral(i: int, faint: float = 0.0) -> str: | |
| bg = f"rgba(245, 158, 11, {faint:.2f})" if faint > 0.0 else "rgba(0,0,0,0.18)" | |
| return f""" | |
| <div title="position {i}" style=" | |
| width: 8px; height: 8px; | |
| border-radius: 50%; | |
| background: {bg}; | |
| "></div> | |
| """ | |
| def render_two_dist_scoreboard(result: FraudPatternResult | None) -> str: | |
| """Stage 5-bar + type 4-bar side-by-side scoreboard.""" | |
| if result is None: | |
| return _scoreboard_placeholder() | |
| stage_rows = _bars_for( | |
| probs=result.stage_probs, | |
| names=STAGE_NAMES, | |
| colors=STAGE_COLORS, | |
| winning_idx=result.predicted_stage, | |
| ) | |
| type_rows = _bars_for( | |
| probs=result.type_probs, | |
| names=TYPE_NAMES, | |
| colors=TYPE_COLORS, | |
| winning_idx=result.predicted_type, | |
| ) | |
| stage_color = STAGE_COLORS[result.predicted_stage] | |
| type_color = TYPE_COLORS[result.predicted_type] | |
| return f""" | |
| <div style=" | |
| background: {_CARD_BG}; | |
| border: 1px solid {_BORDER}; | |
| border-radius: 12px; | |
| padding: 22px 26px; | |
| height: 100%; | |
| box-sizing: border-box; | |
| "> | |
| <div style=" | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: baseline; | |
| margin-bottom: 16px; | |
| "> | |
| <div style=" | |
| font-size: 11px; | |
| color: {_INK_DIM}; | |
| font-family: 'JetBrains Mono', ui-monospace, monospace; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| "> | |
| Pattern verdict | |
| </div> | |
| <div style="display: flex; gap: 8px;"> | |
| <div style=" | |
| display: inline-block; | |
| padding: 4px 12px; | |
| background: {_hex_with_alpha(stage_color, 0.12)}; | |
| color: {stage_color}; | |
| font-size: 12px; | |
| font-weight: 700; | |
| border-radius: 999px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.04em; | |
| "> | |
| stage: {html.escape(STAGE_NAMES[result.predicted_stage])} | |
| · {result.stage_probs[result.predicted_stage]:.2f} | |
| </div> | |
| <div style=" | |
| display: inline-block; | |
| padding: 4px 12px; | |
| background: {_hex_with_alpha(type_color, 0.12)}; | |
| color: {type_color}; | |
| font-size: 12px; | |
| font-weight: 700; | |
| border-radius: 999px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.04em; | |
| "> | |
| type: {html.escape(TYPE_NAMES[result.predicted_type])} | |
| · {result.type_probs[result.predicted_type]:.2f} | |
| </div> | |
| </div> | |
| </div> | |
| <div style="display: flex; gap: 20px;"> | |
| <div style="flex: 1;"> | |
| <div style=" | |
| font-size: 10px; | |
| color: {_INK_DIM}; | |
| font-family: 'JetBrains Mono', ui-monospace, monospace; | |
| text-transform: uppercase; | |
| margin-bottom: 8px; | |
| letter-spacing: 0.04em; | |
| ">Stage distribution</div> | |
| {''.join(stage_rows)} | |
| </div> | |
| <div style="flex: 1;"> | |
| <div style=" | |
| font-size: 10px; | |
| color: {_INK_DIM}; | |
| font-family: 'JetBrains Mono', ui-monospace, monospace; | |
| text-transform: uppercase; | |
| margin-bottom: 8px; | |
| letter-spacing: 0.04em; | |
| ">Type distribution</div> | |
| {''.join(type_rows)} | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| def _bars_for( | |
| probs: np.ndarray, | |
| names: list[str], | |
| colors: dict[int, str], | |
| winning_idx: int, | |
| ) -> list[str]: | |
| rows: list[str] = [] | |
| for i, (name, p) in enumerate(zip(names, probs, strict=True)): | |
| accent = colors[i] | |
| is_win = i == winning_idx | |
| bar_w = max(2, int(float(p) * 100)) | |
| rows.append(f""" | |
| <div style=" | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 5px 0; | |
| border-bottom: 1px solid {_BORDER}; | |
| "> | |
| <div style=" | |
| width: 95px; | |
| font-size: 11px; | |
| font-weight: {600 if is_win else 500}; | |
| color: {_INK if is_win else _INK_DIM}; | |
| font-family: 'JetBrains Mono', ui-monospace, monospace; | |
| "> | |
| {'★ ' if is_win else ' '}{html.escape(name)} | |
| </div> | |
| <div style=" | |
| flex: 1; | |
| height: 10px; | |
| background: rgba(0,0,0,0.05); | |
| border-radius: 5px; | |
| position: relative; | |
| overflow: hidden; | |
| "> | |
| <div style=" | |
| position: absolute; | |
| left: 0; top: 0; bottom: 0; | |
| width: {bar_w}%; | |
| background: {accent}; | |
| border-radius: 5px; | |
| "></div> | |
| </div> | |
| <div style=" | |
| width: 36px; | |
| text-align: right; | |
| font-size: 11px; | |
| font-weight: 600; | |
| color: {accent}; | |
| font-family: 'JetBrains Mono', ui-monospace, monospace; | |
| "> | |
| {float(p):.2f} | |
| </div> | |
| </div> | |
| """) | |
| return rows | |
| def _scoreboard_placeholder() -> str: | |
| return f""" | |
| <div style=" | |
| background: {_CARD_BG}; | |
| border: 1px solid {_BORDER}; | |
| border-radius: 12px; | |
| padding: 22px 26px; | |
| height: 100%; | |
| box-sizing: border-box; | |
| color: {_INK_DIM}; | |
| "> | |
| <div style=" | |
| font-size: 11px; | |
| font-family: 'JetBrains Mono', ui-monospace, monospace; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| margin-bottom: 16px; | |
| "> | |
| Pattern verdict | |
| </div> | |
| <div style="font-size: 14px; color: {_INK_DIM};"> | |
| Click <b>Analyze</b> to see pattern stage + type distributions. | |
| </div> | |
| </div> | |
| """ | |
| def render_reasoning(text: str | None) -> str: | |
| if text is None: | |
| body = f""" | |
| <div style="font-size: 14px; color: {_INK_DIM}; font-style: italic;"> | |
| Reasoning will appear here once the model has read the customer's history. | |
| </div> | |
| """ | |
| else: | |
| body = f""" | |
| <div style=" | |
| font-size: 14px; | |
| color: {_INK}; | |
| line-height: 1.6; | |
| white-space: pre-wrap; | |
| font-family: ui-monospace, 'JetBrains Mono', monospace; | |
| ">{html.escape(text)}<span style=" | |
| display: inline-block; | |
| width: 7px; | |
| height: 14px; | |
| background: {_INK}; | |
| margin-left: 2px; | |
| vertical-align: text-bottom; | |
| animation: copilot-blink 1s infinite; | |
| "></span></div> | |
| <style> | |
| @keyframes copilot-blink {{ | |
| 0%, 49% {{ opacity: 1; }} | |
| 50%, 100% {{ opacity: 0; }} | |
| }} | |
| </style> | |
| """ | |
| return f""" | |
| <div style=" | |
| background: {_CARD_BG}; | |
| border: 1px solid {_BORDER}; | |
| border-radius: 12px; | |
| padding: 22px 26px; | |
| height: 100%; | |
| box-sizing: border-box; | |
| "> | |
| <div style=" | |
| font-size: 11px; | |
| color: {_INK_DIM}; | |
| font-family: 'JetBrains Mono', ui-monospace, monospace; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| margin-bottom: 16px; | |
| "> | |
| Reasoning | |
| </div> | |
| {body} | |
| </div> | |
| """ | |
| def _hex_with_alpha(hex_color: str, alpha: float) -> str: | |
| h = hex_color.lstrip("#") | |
| if len(h) != 6: | |
| return hex_color | |
| r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16) | |
| return f"rgba({r}, {g}, {b}, {alpha:.3f})" | |