"""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"""
Liquid AI · LFM2.5-350M backbone · encoder + LoRA

Fraud Pattern Co-Pilot

""" 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"""
{html.escape(m.display_name)}
{html.escape(m.stage_label_name)} / {html.escape(m.type_label_name)}
""") return f"""
{''.join(cards)}
""" def render_context(member: FraudPatternCastMember) -> str: accent = STAGE_COLORS[member.stage_label] return f"""
Fraud queue context · pattern: {html.escape(member.pattern)}
“{html.escape(member.context_text)}”
{html.escape(member.description)}
""" 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"""
64-transaction history · oldest → newest flagged   contributed
{''.join(dots)}
""" def _dot_flagged(i: int) -> str: return f"""
""" def _dot_attributed(i: int, alpha: float) -> str: glow = max(0.4, min(1.0, alpha)) return f"""
""" 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"""
""" 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"""
Pattern verdict
stage: {html.escape(STAGE_NAMES[result.predicted_stage])} · {result.stage_probs[result.predicted_stage]:.2f}
type: {html.escape(TYPE_NAMES[result.predicted_type])} · {result.type_probs[result.predicted_type]:.2f}
Stage distribution
{''.join(stage_rows)}
Type distribution
{''.join(type_rows)}
""" 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"""
{'★ ' if is_win else '   '}{html.escape(name)}
{float(p):.2f}
""") return rows def _scoreboard_placeholder() -> str: return f"""
Pattern verdict
Click Analyze to see pattern stage + type distributions.
""" def render_reasoning(text: str | None) -> str: if text is None: body = f"""
Reasoning will appear here once the model has read the customer's history.
""" else: body = f"""
{html.escape(text)}
""" return f"""
Reasoning
{body}
""" 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})"