landing: kGOA units, full money stats, rootless resource display
Public bank/exchange/merchant landing stats and performance UI only. Secret goa-ui overlay stays on local/merchant-secret-ui (never push).
This commit is contained in:
parent
bf8a915a01
commit
fb1a0b0b8e
12 changed files with 1046 additions and 333 deletions
|
|
@ -1,38 +1,81 @@
|
|||
#!/usr/bin/env python3
|
||||
"""GOA (and generic CUR:amount) alt-unit formatting for landing stats.
|
||||
|
||||
Matches exchange currency_specification.alt_unit_names:
|
||||
0→GOA, 3→Kilo-GOA, 6→Mega-GOA, … and fractional scales.
|
||||
Display prefers compact alt names for large values so landing tiles fit.
|
||||
Display uses compact SI-style names (kGOA, MGOA, GGOA, …) so tiles stay short.
|
||||
Exchange /config may still send long names (Kilo-GOA); we compact them.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
|
||||
# Default GOA ladder (same shape as exchange /config)
|
||||
# Compact display ladder (preferred on landings)
|
||||
DEFAULT_ALT: Dict[str, str] = {
|
||||
"24": "Yotta-GOA",
|
||||
"21": "Zetta-GOA",
|
||||
"18": "Exa-GOA",
|
||||
"15": "Peta-GOA",
|
||||
"12": "Tera-GOA",
|
||||
"9": "Giga-GOA",
|
||||
"6": "Mega-GOA",
|
||||
"3": "Kilo-GOA",
|
||||
"24": "YGOA",
|
||||
"21": "ZGOA",
|
||||
"18": "EGOA",
|
||||
"15": "PGOA",
|
||||
"12": "TGOA",
|
||||
"9": "GGOA",
|
||||
"6": "MGOA",
|
||||
"3": "kGOA",
|
||||
"0": "GOA",
|
||||
"-1": "Deci-GOA",
|
||||
"-2": "Centi-GOA",
|
||||
"-3": "Milli-GOA",
|
||||
"-6": "Micro-GOA",
|
||||
"-7": "Deci-Micro-GOA",
|
||||
"-8": "Atomic-GOA",
|
||||
"-1": "dGOA",
|
||||
"-2": "cGOA",
|
||||
"-3": "mGOA",
|
||||
"-6": "µGOA",
|
||||
"-7": "dµGOA",
|
||||
"-8": "aGOA",
|
||||
}
|
||||
|
||||
# Use alt unit when |value| >= this (base units). Below: keep compact CUR:n.
|
||||
# Long names from exchange /config → compact
|
||||
_LONG_TO_COMPACT = {
|
||||
"yotta-goa": "YGOA",
|
||||
"zetta-goa": "ZGOA",
|
||||
"exa-goa": "EGOA",
|
||||
"peta-goa": "PGOA",
|
||||
"tera-goa": "TGOA",
|
||||
"giga-goa": "GGOA",
|
||||
"mega-goa": "MGOA",
|
||||
"kilo-goa": "kGOA",
|
||||
"goa": "GOA",
|
||||
"deci-goa": "dGOA",
|
||||
"centi-goa": "cGOA",
|
||||
"milli-goa": "mGOA",
|
||||
"micro-goa": "µGOA",
|
||||
"deci-micro-goa": "dµGOA",
|
||||
"atomic-goa": "aGOA",
|
||||
}
|
||||
|
||||
# Use alt unit when |value| >= this (base units). Below: keep CUR:n.
|
||||
ALT_THRESHOLD = Decimal("1000")
|
||||
|
||||
|
||||
def compact_alt_map(alt: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
||||
"""Normalize any alt_unit_names map to compact display labels."""
|
||||
base = dict(DEFAULT_ALT)
|
||||
if not alt:
|
||||
return base
|
||||
out: Dict[str, str] = {}
|
||||
for k, v in alt.items():
|
||||
sk = str(k)
|
||||
name = str(v).strip()
|
||||
low = name.lower().replace(" ", "")
|
||||
if low in _LONG_TO_COMPACT:
|
||||
out[sk] = _LONG_TO_COMPACT[low]
|
||||
elif sk in DEFAULT_ALT and (
|
||||
"goa" in low or name == DEFAULT_ALT.get(sk) or name in DEFAULT_ALT.values()
|
||||
):
|
||||
# keep scale key, prefer compact for GOA family
|
||||
out[sk] = DEFAULT_ALT.get(sk, name)
|
||||
else:
|
||||
out[sk] = name
|
||||
# ensure full default ladder for missing scales
|
||||
for sk, name in DEFAULT_ALT.items():
|
||||
out.setdefault(sk, name)
|
||||
return out
|
||||
|
||||
|
||||
def parse_amount(s: Any) -> Tuple[str, Decimal]:
|
||||
"""Parse 'GOA:12.5' or bare number → (currency, value)."""
|
||||
if s is None:
|
||||
|
|
@ -51,7 +94,6 @@ def parse_amount(s: Any) -> Tuple[str, Decimal]:
|
|||
def fmt_coeff(v: Decimal) -> str:
|
||||
if v == v.to_integral_value():
|
||||
return format(int(v), "d")
|
||||
# up to 4 significant fractional digits, strip trailing zeros
|
||||
q = v.quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP)
|
||||
s = format(q, "f").rstrip("0").rstrip(".")
|
||||
return s or "0"
|
||||
|
|
@ -60,7 +102,6 @@ def fmt_coeff(v: Decimal) -> str:
|
|||
def fmt_base(cur: str, val: Decimal) -> str:
|
||||
if val == val.to_integral_value():
|
||||
return f"{cur}:{int(val)}"
|
||||
# preserve up to 8 fractional digits (Taler style)
|
||||
s = format(val, "f").rstrip("0").rstrip(".")
|
||||
return f"{cur}:{s}"
|
||||
|
||||
|
|
@ -76,12 +117,12 @@ def format_amount_alt(
|
|||
|
||||
Keys:
|
||||
amount canonical CUR:n
|
||||
amount_alt short display (e.g. "1.23 Mega-GOA")
|
||||
amount_full "1.23 Mega-GOA (GOA:1230000)" when alt used
|
||||
value float for JSON (may lose precision above 2^53 — also value_str)
|
||||
amount_alt short display (e.g. "1.23 MGOA")
|
||||
amount_full "1.23 MGOA (GOA:1230000)" when alt used
|
||||
value float for JSON
|
||||
value_str exact decimal string
|
||||
"""
|
||||
alt = alt or DEFAULT_ALT
|
||||
alt = compact_alt_map(alt)
|
||||
try:
|
||||
cur, val = parse_amount(amount)
|
||||
except (InvalidOperation, ValueError, ArithmeticError):
|
||||
|
|
@ -95,17 +136,21 @@ def format_amount_alt(
|
|||
}
|
||||
|
||||
base = fmt_base(cur, val)
|
||||
value_str = format(val, "f").rstrip("0").rstrip(".") if val != val.to_integral_value() else str(int(val))
|
||||
value_str = (
|
||||
format(val, "f").rstrip("0").rstrip(".")
|
||||
if val != val.to_integral_value()
|
||||
else str(int(val))
|
||||
)
|
||||
try:
|
||||
value_f = float(val)
|
||||
except Exception:
|
||||
value_f = 0.0
|
||||
|
||||
base_name = alt.get("0") or cur
|
||||
# Never rename foreign currencies with a GOA alt map (merchant dual CHF+GOA).
|
||||
cur_u = (cur or "").upper()
|
||||
base_u = str(base_name).upper()
|
||||
if cur_u and cur_u != "GOA" and (base_u == "GOA" or base_u.endswith("-GOA") or "GOA" in base_u):
|
||||
# Foreign currencies must not pick up kGOA / MGOA labels
|
||||
if cur_u and cur_u != "GOA" and ("GOA" in base_u or base_u.endswith("GOA")):
|
||||
return {
|
||||
"amount": base,
|
||||
"amount_alt": base,
|
||||
|
|
@ -115,7 +160,6 @@ def format_amount_alt(
|
|||
}
|
||||
|
||||
if val == 0:
|
||||
# Keep canonical CUR:0 (avoids "0 GOA" for empty foreign balances)
|
||||
return {
|
||||
"amount": base,
|
||||
"amount_alt": base,
|
||||
|
|
@ -133,9 +177,7 @@ def format_amount_alt(
|
|||
scales.sort(key=lambda x: -x[0])
|
||||
|
||||
absval = abs(val)
|
||||
# Below threshold: short base form (saves noise on small demo amounts)
|
||||
if absval < threshold:
|
||||
# Prefer "GOA:12.5" style (matches existing landings) when small
|
||||
return {
|
||||
"amount": base,
|
||||
"amount_alt": base,
|
||||
|
|
@ -165,7 +207,7 @@ def format_amount_alt(
|
|||
|
||||
sc, name, coeff = chosen
|
||||
short = f"{fmt_coeff(coeff)} {name}"
|
||||
full = f"{short} ({base})" if with_base or True else short
|
||||
full = f"{short} ({base})"
|
||||
return {
|
||||
"amount": base,
|
||||
"amount_alt": short,
|
||||
|
|
@ -175,7 +217,9 @@ def format_amount_alt(
|
|||
}
|
||||
|
||||
|
||||
def attach_alt(obj: Dict[str, Any], amount_key: str = "amount", alt: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
||||
def attach_alt(
|
||||
obj: Dict[str, Any], amount_key: str = "amount", alt: Optional[Dict[str, str]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Mutate obj: ensure amount_alt / amount_full from amount_key."""
|
||||
if not isinstance(obj, dict):
|
||||
return obj
|
||||
|
|
@ -193,7 +237,7 @@ def attach_alt(obj: Dict[str, Any], amount_key: str = "amount", alt: Optional[Di
|
|||
|
||||
|
||||
def load_alt_from_config(url: str, timeout: float = 8.0) -> Dict[str, str]:
|
||||
"""GET exchange/bank /config → alt_unit_names (fallback DEFAULT_ALT)."""
|
||||
"""GET exchange/bank /config → compact alt_unit_names (fallback DEFAULT_ALT)."""
|
||||
import json
|
||||
import urllib.request
|
||||
|
||||
|
|
@ -215,16 +259,16 @@ def load_alt_from_config(url: str, timeout: float = 8.0) -> Dict[str, str]:
|
|||
break
|
||||
if not isinstance(au, dict) or "0" not in au:
|
||||
return dict(DEFAULT_ALT)
|
||||
return {str(k): str(v) for k, v in au.items()}
|
||||
return compact_alt_map({str(k): str(v) for k, v in au.items()})
|
||||
|
||||
|
||||
def enrich_stats_tree(data: Dict[str, Any], alt: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
||||
"""Walk common landing stats.json shapes and add amount_alt fields."""
|
||||
alt = alt or DEFAULT_ALT
|
||||
alt = compact_alt_map(alt)
|
||||
if not isinstance(data, dict):
|
||||
return data
|
||||
|
||||
# bank-style
|
||||
# bank-style nested blocks
|
||||
for path in (
|
||||
("flow", "incoming"),
|
||||
("flow", "withdraw"),
|
||||
|
|
@ -252,6 +296,7 @@ def enrich_stats_tree(data: Dict[str, Any], alt: Optional[Dict[str, str]] = None
|
|||
f = format_amount_alt(cur["last_amount"], alt)
|
||||
cur["last_amount"] = f["amount"]
|
||||
cur["last_amount_alt"] = f["amount_alt"]
|
||||
cur["last_amount_full"] = f["amount_full"]
|
||||
|
||||
if isinstance(data.get("flow"), dict):
|
||||
fl = data["flow"]
|
||||
|
|
@ -295,6 +340,7 @@ def enrich_stats_tree(data: Dict[str, Any], alt: Optional[Dict[str, str]] = None
|
|||
f = format_amount_alt(row["value"], alt)
|
||||
row["value"] = f["amount"]
|
||||
row["value_alt"] = f["amount_alt"]
|
||||
row["value_full"] = f["amount_full"]
|
||||
|
||||
# merchant dual currency
|
||||
for block in data.get("by_currency") or []:
|
||||
|
|
@ -315,4 +361,10 @@ def enrich_stats_tree(data: Dict[str, Any], alt: Optional[Dict[str, str]] = None
|
|||
attach_alt(row, "amount", alt)
|
||||
|
||||
data["alt_unit_names"] = alt
|
||||
# small legend for UI (positive scales only, compact)
|
||||
data["alt_unit_legend"] = [
|
||||
{"scale": int(k), "name": alt[k], "factor": f"10^{k}"}
|
||||
for k in sorted(alt.keys(), key=lambda x: -int(x))
|
||||
if int(k) >= 0
|
||||
]
|
||||
return data
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue