coinsentry / app /utils.py
heisbuba's picture
Update app/utils.py
6b9f0a3 verified
Raw
History Blame Contribute Delete
5.31 kB
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'<p class="text-{color}-400 font-semibold text-sm">{text}</p>'
# ── 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