import re from litestar.response import Response from litestar.enums import MediaType # ── Number parsing ──────────────────────────────────────────────────────────── def parse_human_number(val: str) -> float: """ Parse shorthand notation into a float. Supports k/m/b/t suffixes (case-insensitive), comma separators, a leading minus sign, and trailing '%' or 'x' so the function is safe to call on inputs that may come from mixed-type filter boxes. Examples: '5m' → 5_000_000 '100k' → 100_000 '1.5b' → 1_500_000_000 '-2b' → -2_000_000_000 '0' → 0.0 """ if not val: return 0.0 val = str(val).strip() negative = val.startswith('-') val = val.lower().replace(',', '').replace('x', '').replace('%', '').lstrip('-') parts = re.findall(r'(\d+\.?\d*)([kmbt]?)', val) mults = {'k': 1_000, 'm': 1_000_000, 'b': 1_000_000_000, 't': 1_000_000_000_000} total = sum(float(num) * mults.get(unit, 1) for num, unit in parts) return -total if negative else total # ── HTMX HTML helpers ───────────────────────────────────────────────────────── def html_response(content: str, headers: dict | None = None) -> Response: """Return a minimal HTML fragment response for HTMX swap targets.""" return Response(content=content, media_type=MediaType.HTML, headers=headers or {}) def msg_fragment(text: str, color: str = "red") -> str: """Render a small Tailwind-styled status message paragraph.""" return f'
{text}
' # ── Number & price formatting ───────────────────────────────────────────────── def format_price(price: float | None) -> str: if price is None: return "N/A" if price >= 10_000: return f"${price:,.0f}" if price >= 1: return f"${price:,.2f}" if price >= 0.001: return f"${price:.4f}" return f"${price:.6f}" def format_large_number(val: float | None) -> str: """ Format a large USD value with a T/B/M/K suffix. Returns "—" for None (no data). Returns "$0" for an explicit zero so callers can distinguish a missing value from a zero-valued one. """ if val is None: return "—" if val >= 1e12: return f"${val/1e12:.2f}T" if val >= 1e9: return f"${val/1e9:.2f}B" if val >= 1e6: return f"${val/1e6:.1f}M" if val >= 1e3: return f"${val/1e3:.1f}K" return f"${val:,.0f}" def format_pct(val: float | None) -> str: if val is None: return "—" return f"{val:+.1f}%" def color_class(val: float | None) -> str: if val is None: return "text-gray-400" if val > 0: return "text-green-400" if val < 0: return "text-red-400" return "text-gray-400" def vtmr_class(vtmr: float) -> str: if vtmr >= 1.0: return "text-green-300 font-bold" if vtmr >= 0.5: return "text-yellow-400" return "text-gray-500" # ── Token enrichment ────────────────────────────────────────────────────────── def enrich_token(raw: dict, *, starred: bool = False) -> dict: """ Return a display-ready shallow copy of a raw or scanner-enriched coin dict. The copy is intentionally shallow — callers must not mutate nested objects. The shared in-memory cache is never modified by this function. VTMR is extracted with an `is not None` guard so that a scanner-computed value of 0.0 is preserved rather than discarded in favour of a live recalculation, keeping filter and display values in sync. """ t = dict(raw) mcap = t.get("market_cap") or 0.0 vol24 = t.get("total_volume") or 0.0 _vtmr_raw = t.get("vtmr_raw") vtmr = _vtmr_raw if _vtmr_raw is not None else ( (vol24 / mcap) if mcap > 0 else 0.0 ) p24 = t.get("price_change_percentage_24h_in_currency") p7d = t.get("price_change_percentage_7d_in_currency") p30d = t.get("price_change_percentage_30d_in_currency") p1y = t.get("price_change_percentage_1y_in_currency") t.update( fmt_price = format_price(t.get("current_price")), fmt_mcap = format_large_number(mcap if t.get("market_cap") is not None else None), fmt_volume = format_large_number(vol24 if t.get("total_volume") is not None else None), fmt_24h = format_pct(p24), cls_24h = color_class(p24), fmt_7d = format_pct(p7d), cls_7d = color_class(p7d), fmt_30d = format_pct(p30d), cls_30d = color_class(p30d), fmt_1y = format_pct(p1y), cls_1y = color_class(p1y), vtmr_raw = vtmr, vtmr_display = f"{vtmr:.3f}x", vtmr_class = vtmr_class(vtmr), starred = starred, ) return t