Files
eink-push/data.py
2026-06-29 09:43:20 +08:00

181 lines
6.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
数据层 —— 汇总仪表盘所需的全部数据。
橘喵今日经营 + 近30天趋势来自 jm_apijm-devops 后端统计接口,免鉴权 GET
天气来自 weather_api和风天气定位南京·秦淮。日期/时间用真实系统时间。
任何接口失败时相关数据以占位符 "--" 呈现,不回退 mock。
"""
import json
from datetime import datetime
import config
import jm_api
import weather_api
WEEK_CN = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
TREND_DAYS = 30 # 近 N 天趋势
def _read_weather_cache():
try:
with open(config.WEATHER_CACHE_PATH, encoding="utf-8") as f:
return json.load(f) # {"ts": float, "data": {...}}
except Exception:
return None
def _write_weather_cache(ts, data):
try:
with open(config.WEATHER_CACHE_PATH, "w", encoding="utf-8") as f:
json.dump({"ts": ts, "data": data}, f, ensure_ascii=False)
except Exception as e:
print(f"[警告] 天气缓存写入失败:{e}")
def _read_trend_cache():
try:
with open(config.TREND_CACHE_PATH, encoding="utf-8") as f:
return json.load(f) # {"date": "YYYY-MM-DD", "data": {...}}
except Exception:
return None
def _write_trend_cache(date_key, data):
try:
with open(config.TREND_CACHE_PATH, "w", encoding="utf-8") as f:
json.dump({"date": date_key, "data": data}, f, ensure_ascii=False)
except Exception as e:
print(f"[警告] 趋势缓存写入失败:{e}")
def _trend(now):
"""近30天趋势按日期缓存recent-days 每天只更新一次。
当天已有缓存(且有数据)→ 直接复用,不发请求;当天还没有 → 拉取并写缓存。
拉取失败时沿用任意旧缓存(哪怕非当天),仍无缓存才回退空。
API 降序 → 升序:左旧右新。
"""
date_key = now.strftime("%Y-%m-%d")
cached = _read_trend_cache()
if cached and cached.get("date") == date_key and cached.get("data", {}).get("series"):
return cached["data"]
try:
asc = list(reversed(jm_api.get_recent_days(TREND_DAYS)))
trend = {
"dates": [d["date"][5:].replace("-", "/") for d in asc], # yyyy-MM-dd → MM/DD
"series": [
{"k": "单量", "style": "solid", "data": [int(d["orderCount"]) for d in asc]},
{"k": "流水", "style": "dashed", "data": [float(d["payAmount"]) for d in asc]},
{"k": "毛利", "style": "dotted", "data": [float(d["grossProfit"]) for d in asc]},
],
}
_write_trend_cache(date_key, trend)
return trend
except Exception as e:
print(f"[警告] 近30天趋势拉取失败{e}")
if cached and cached.get("data", {}).get("series"): # 失败沿用旧缓存,避免趋势图整片 "--"
print("[警告] 沿用上次趋势缓存")
return cached["data"]
return {"dates": [], "series": []}
def _weather(now):
"""和风天气(南京·秦淮),带 WEATHER_TTL 文件缓存。
缓存未过期 → 直接复用,不发请求(每分钟刷新时省掉 14/15 次天气调用)。
过期 → 重新拉取并写缓存;全部接口失败时沿用上次缓存,仍无缓存才回退占位 "--"
"""
ts = now.timestamp()
cached = _read_weather_cache()
if cached and ts - cached.get("ts", 0) < config.WEATHER_TTL:
return cached["data"]
out = {"loc": config.WEATHER_LOC_NAME, "cond": "--", "temp": "--", "range": "--", "icon": ""}
ok = False
try:
w = weather_api.get_now()
out["cond"] = w.get("text") or "--"
out["temp"] = f"{w['temp']}°"
out["icon"] = w.get("icon") or "" # 和风图标代码,供 renderer 映射图标
ok = True
except Exception as e:
print(f"[警告] 实时天气拉取失败:{e}")
try:
today = weather_api.get_today()
out["range"] = f"{today['tempMin']}°~{today['tempMax']}°"
ok = True
except Exception as e:
print(f"[警告] 今日温区拉取失败:{e}")
if ok:
_write_weather_cache(ts, out)
return out
if cached: # 全部失败:沿用上次缓存,避免天气区整片 "--"
print("[警告] 天气全部拉取失败,沿用上次缓存")
return cached["data"]
return out
# ---------- 格式化 ----------
def _fmt_count(n):
return f"{int(n):,}" # 千分位,如 1,284
def _fmt_money(yuan):
"""元金额 → 紧凑展示≥1万显示「X.XX万」(两位小数),否则取整元。"""
v = float(yuan)
return f"{v / 10000:.2f}" if abs(v) >= 10000 else f"{v:.0f}"
def _fmt_rate(rate):
"""增长率字符串 → (方向, 文本)。null/空 → (None, '--'),方向 None 表示不画三角。
数值统一两位小数;整数部分不足两位用前导 0 补齐(6.5→06.50),便于环比/同比上下对齐;
三/四位数(≥100%)原样完整输出不截断。"""
if rate is None or rate == "":
return (None, "--")
v = float(rate)
return ("down" if v < 0 else "up", f"{abs(v):05.2f}%")
def _col(k, v, wow, yoy):
return {"k": k, "v": v, "hb": _fmt_rate(wow), "tb": _fmt_rate(yoy)}
def _placeholder_col(k):
return {"k": k, "v": "--", "hb": (None, "--"), "tb": (None, "--")}
# ---------- 橘喵业务数据 ----------
def _mao(now):
upd = now.strftime("%H:%M")
# 今日经营realtime
try:
rt = jm_api.get_realtime()
upd = (rt.get("dataTime") or "")[11:16] or upd
cols = [
_col("单量", _fmt_count(rt["todayOrderCount"]), rt["orderCountWowRate"], rt["orderCountYoyRate"]),
_col("流水", _fmt_money(rt["todayPayAmount"]), rt["payAmountWowRate"], rt["payAmountYoyRate"]),
_col("毛利", _fmt_money(rt["todayGrossProfit"]), rt["grossProfitWowRate"], rt["grossProfitYoyRate"]),
]
except Exception as e:
print(f"[警告] 今日经营拉取失败:{e}")
cols = [_placeholder_col(k) for k in ("单量", "流水", "毛利")]
# 近30天趋势按日期缓存每天只拉一次
trend = _trend(now)
return {"upd": upd, "cols": cols, "trend": trend}
def get_dashboard_data(now=None):
"""返回渲染所需的完整数据字典。"""
now = now or datetime.now()
return {
"date": {
"greg": now.strftime("%Y/%m/%d"),
"week": WEEK_CN[now.weekday()],
"time": now.strftime("%H:%M"),
},
"weather": _weather(now),
"mao": _mao(now),
}