feat: show rate freshness and card network spread
This commit is contained in:
+70
-9
@@ -4,10 +4,11 @@ import os
|
|||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone, timedelta
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
CACHE = "/root/.openclaw/workspace/fx_realtime.json"
|
CACHE = "/root/.openclaw/workspace/fx_realtime.json"
|
||||||
|
REPORT_TZ = timezone(timedelta(hours=8))
|
||||||
FETCH = "/root/.openclaw/workspace/web_tasks/fx_fetch_real.js"
|
FETCH = "/root/.openclaw/workspace/web_tasks/fx_fetch_real.js"
|
||||||
PAIRS = {"USD/CNY", "EUR/CNY", "SGD/CNY", "GBP/CNY", "USD/EUR", "GBP/EUR"}
|
PAIRS = {"USD/CNY", "EUR/CNY", "SGD/CNY", "GBP/CNY", "USD/EUR", "GBP/EUR"}
|
||||||
PAT = re.compile(r'\[\[(20\d{2}),(\d{1,2}),(\d{1,2}),(\d{1,2}),(\d{1,2}),null,null,\[\]\],\[([0-9]+\.[0-9]+),')
|
PAT = re.compile(r'\[\[(20\d{2}),(\d{1,2}),(\d{1,2}),(\d{1,2}),(\d{1,2}),null,null,\[\]\],\[([0-9]+\.[0-9]+),')
|
||||||
@@ -63,23 +64,83 @@ def network_rates(raw, base, quote):
|
|||||||
try: yield "Mastercard", 1/float(data["data"]["data"]["conversionRate"]), data["data"]["data"].get("fxDate", "")
|
try: yield "Mastercard", 1/float(data["data"]["data"]["conversionRate"]), data["data"]["data"].get("fxDate", "")
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
|
def parse_date(value):
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
for fmt in ("%Y-%m-%d", "%m/%d/%Y", "%Y/%m/%d"):
|
||||||
|
try:
|
||||||
|
return datetime.strptime(value[:10], fmt).date()
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
def age_label(date_text):
|
||||||
|
d = parse_date(date_text)
|
||||||
|
if not d:
|
||||||
|
return ""
|
||||||
|
days = (datetime.now(REPORT_TZ).date() - d).days
|
||||||
|
if days <= 0:
|
||||||
|
return "🟢 最新"
|
||||||
|
if days == 1:
|
||||||
|
return "🟢 1天前"
|
||||||
|
if days <= 3:
|
||||||
|
return f"🟠 {days}天前"
|
||||||
|
return f"🔴 {days}天前"
|
||||||
|
|
||||||
|
def fmt_date(date_text):
|
||||||
|
d = parse_date(date_text)
|
||||||
|
return d.isoformat() if d else (date_text or "未知日期")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) != 4: raise SystemExit("usage: rate_query.py AMOUNT BASE QUOTE")
|
if len(sys.argv) != 4: raise SystemExit("usage: rate_query.py AMOUNT BASE QUOTE")
|
||||||
amount = float(sys.argv[1]); base = sys.argv[2].upper(); quote = sys.argv[3].upper()
|
amount = float(sys.argv[1]); base = sys.argv[2].upper(); quote = sys.argv[3].upper()
|
||||||
g, gt = google_rate(base, quote)
|
g, gt = google_rate(base, quote)
|
||||||
raw = ensure_real() if f"{base}/{quote}" in PAIRS or f"{quote}/{base}" in PAIRS else None
|
raw = ensure_real() if f"{base}/{quote}" in PAIRS or f"{quote}/{base}" in PAIRS else None
|
||||||
lines = [f"💱 {amount:g} {base} → {quote}", ""]
|
lines = [f"💱 {amount:g} {base} → {quote}", ""]
|
||||||
|
rates = []
|
||||||
if g is not None:
|
if g is not None:
|
||||||
lines += [f"Google 1 {base} = {g:.6f} {quote}", f" {amount:g} {base} = {amount*g:.2f} {quote}"]
|
lines += ["市场参考", f"Google 1 {base} = {g:.6f} {quote}", f" {amount:g} {base} = {amount*g:.2f} {quote}"]
|
||||||
else: lines.append("Google 暂时无法获取")
|
rates.append(("Google", g))
|
||||||
seen = set()
|
else:
|
||||||
|
lines += ["市场参考", "Google 暂时无法获取"]
|
||||||
|
|
||||||
|
network = []
|
||||||
if raw:
|
if raw:
|
||||||
for name, rate, date in network_rates(raw, base, quote):
|
for name, rate, date in network_rates(raw, base, quote):
|
||||||
if name in seen: continue
|
if any(n == name for n, _, _ in network):
|
||||||
seen.add(name)
|
continue
|
||||||
lines += [f"{name:<12}1 {base} = {rate:.6f} {quote}", f" {amount:g} {base} = {amount*rate:.2f} {quote} ({date})"]
|
network.append((name, rate, date))
|
||||||
if not seen: lines.append("Visa/MC 暂无最新官方结算数据")
|
lines += ["", "卡组织汇率"]
|
||||||
lines += ["", "Google 为参考市场价;Visa/万事达为官方计算器结算汇率。"]
|
if network:
|
||||||
|
for name, rate, date in network:
|
||||||
|
freshness = age_label(date)
|
||||||
|
date_part = f"📅 {fmt_date(date)}"
|
||||||
|
if freshness:
|
||||||
|
date_part += f" · {freshness}"
|
||||||
|
lines += [f"{name:<12}1 {base} = {rate:.6f} {quote}", f" {amount:g} {base} = {amount*rate:.2f} {quote}", f" {date_part}"]
|
||||||
|
rates.append((name, rate))
|
||||||
|
else:
|
||||||
|
lines.append("Visa/MC 暂无最新官方结算数据")
|
||||||
|
|
||||||
|
if network and g is not None:
|
||||||
|
lines += ["", "📊 相对 Google"]
|
||||||
|
for name, rate, _ in network:
|
||||||
|
diff = (rate / g - 1.0) * 100.0
|
||||||
|
sign = "+" if diff >= 0 else ""
|
||||||
|
lines.append(f"{name:<12}{sign}{diff:.3f}%")
|
||||||
|
if len(network) >= 1:
|
||||||
|
card_rates = [r for _, r, _ in network]
|
||||||
|
lo, hi = min(card_rates), max(card_rates)
|
||||||
|
if len(card_rates) == 1:
|
||||||
|
lines += ["", f"💳 卡组织参考:{amount*lo:.2f} {quote}"]
|
||||||
|
else:
|
||||||
|
lines += ["", f"💳 卡组织参考区间:{amount*lo:.2f}–{amount*hi:.2f} {quote}"]
|
||||||
|
|
||||||
|
stale = [(n, d) for n, _, d in network if parse_date(d) and (datetime.now(timezone.utc).date() - parse_date(d)).days >= 2]
|
||||||
|
if stale:
|
||||||
|
lines += ["", "⚠️ " + ";".join(f"{n} 数据距今天 {(datetime.now(REPORT_TZ).date()-parse_date(d)).days} 天" for n, d in stale) + ",不要将其视为当前实时市场价。"]
|
||||||
|
|
||||||
|
lines += ["", "Google 为当前市场参考价;Visa/万事达为官方卡组织结算汇率。实际扣款还可能受发卡行费用、入账时间等影响。"]
|
||||||
print("\n".join(lines))
|
print("\n".join(lines))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user