From ea26f4129878e1d7e9bdadda6c7fc48b8520f095 Mon Sep 17 00:00:00 2001 From: YANG JIANKUAN Date: Mon, 26 Oct 2026 15:43:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20E1002=20=E4=BB=8A=E6=97=A5=E5=AE=9E?= =?UTF-8?q?=E6=97=B6=E5=9B=A2=E8=B4=AD=E4=B8=A4=E5=88=97=E6=9B=BF=E6=8D=A2?= =?UTF-8?q?=E4=B8=BA=E8=B5=8F=E9=87=91=E5=8D=95=E9=87=8F=EF=BC=8F=E8=B5=8F?= =?UTF-8?q?=E9=87=91=E6=B5=81=E6=B0=B4=EF=BC=88=E5=90=AB=E7=8E=AF=E6=AF=94?= =?UTF-8?q?=EF=BC=8F=E5=90=8C=E6=AF=94=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - REALTIME_COLS 改取接口 bountyOrderCount/bountyPayAmount,旧后端缺字段时占位 -- - 毛利口径注释同步为含当天审核通过的赏金;小屏仍只显示三列不受影响 Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 4 ++-- data.py | 9 +++++---- renderer.py | 2 +- renderer_e1002.py | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ced602f..2434fbe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co > **分支 `feature/colorful`**:新设备在黑白外支持红色,本分支把渲染升级为黑/白/红三色。当前版式为**上中下三段**:A 大时钟+日期 / 天气(定位南京·江宁);B **Claude Usage** 主角(5h/7d 用量进度条 + 时间维度对比 pace,用量吃紧转红预警);C 橘喵今日实时三列。红作**语义强调**(用量预警、涨、定位、天气太阳/闪电),仍**无真灰阶、不抖动**,全图只三种纯色。近30天趋势图当前**隐藏**(`renderer._trend_chart` 及依赖保留、未调用,随时可挂回)。 -> **分支 `feature/reterminal-e1002`**(基于 `feature/colorful`):新增第二个目标设备 **Seeed reTerminal E1002**(7.3 寸 **800×480**,E Ink **Spectra 6 原生六色**:黑/白/黄/红/绿/蓝,真机已验证六色量化干净)。该设备不接受局域网直推,而是由 **SenseCraft HMI** 云端的 **HTML 控件**按设备刷新间隔(最小 5 分钟)**每周期重新抓取**一个公网 HTTPS URL、无头浏览器截图后量化下发;不支持 ETag/304,内容不变也会整屏重绘(全刷约 17 秒)。**真机验证(2026-09)**:Image 控件只在填写 URL 时抓一次快照、此后不再更新;HTML 控件填同一 URL 则每周期重抓——所以对外只用 HTML 控件。当前主形态是 **`server.py` 动态服务**(部署公网 HTTPS;局域网地址/明文 HTTP 云端抓不到):三条恒定 URL 并行供对比——方案一 `IMAGE_PATH`(默认 `/e1002.png`)每次请求**内存中现渲染 PNG**(Pillow 点阵版);方案二 `HTML_PATH`(默认 `/e1002.html`)每次请求生成 **800×480 现代网页**(`html_renderer.py`,系统/网络字体 + 内联 SVG K 线,无 JS)由平台截图——**真机实拍:文字抗锯齿灰边被平台抖动成毛边、小字与复杂汉字发糊**;方案三 `HTML_PNG_PATH`(默认 `/e1002-web.png`)同一网页由**服务端无头 Chrome 截图后按最近色(不抖动)固化为六色 PNG**(`html_shot.py`),平台拿到纯色图无从抖动,需服务器装 Chrome。**结论(2026-09-10):方案一为主力**——方案二/三的矢量字体在 125 PPI 下小字必然毛糙(抗锯齿灰边无论由平台还是我们二值化都只能变成锯齿/断笔,见 `output/e1002/font_compare.png`:矢量字体单色渲染 ≈ 抗锯齿后二值化,都不如点阵),Canvas 逐像素画也改变不了这点(Pillow 本身就是逐像素画)。方案二/三端点保留供对比,不再投入。宽版**只用点阵字体的整数倍字号 12/24/36/48/84**(`E1002Renderer.__init__` 把基类的 F13/F16/FPACE 都映射到 12px——用量行 标签常规字重、右对齐贴进度条(基类类属性 `LABEL_BOLD`/`LABEL_ALIGN`/`LABEL_GAP`),百分比与 pace 数字粗体;24px 试过被嫌大;另加 F84 给顶部大时钟),可用 `all(v % 12 == 0 for v in 字体尺寸)` 自查。天气/橘喵/BTC/设备遥测 走**服务端内存缓存 + 有效期**(`WEATHER_TTL`/`MAO_TTL`/`BTC_TTL`/`DEVICE_TTL`,默认 900/60/300/300 秒,0=每次实拉;请求时过期才实拉并更新时间戳,失败沿用旧值、不更新时间戳),服务**不读本机任何文件**;Claude/Codex 用量为**账户级、形态 A**:本机 `main.py --target push` 采集(Claude 复用 Claude Code 钥匙串登录态调 `/api/oauth/usage`;Codex 经 `codex app-server` RPC)后推送原始快照(`X-Push-Token` 鉴权),服务端内存暂存、**不持任何凭证**、渲染时现算 pace。静态形态 `main.py --target e1002`(`output/e1002/index.html` + `dashboard.png`)仍保留。渲染由 `renderer_e1002.py` 完成:继承 `DashboardRenderer` 组件、**k=1 字号与三色版完全相同(不放大,放大后字太大很傻)**,四段版式:A 大时钟 **F84**(墨迹 16~79)+ 其右一列三行、左缘对齐(F12,行距 22):① 电池图标+电量(充电中绿、≤20% 红)② 温度计+温度 | 蓝色水滴+湿度(①② 数字 F12 伪粗体、字号不变,占位 -- 不加粗,2026-09-14)③ 星期/日期(与时钟基线对齐);**右侧天气(2026-09-15 定稿)上下两层**:上层 天气图标 **×1.25** | **实时温度 F48 粗 3**(墨迹顶沿 16 与时钟齐平、底沿 52,右贴页边 790;「°」用 F24 粗 1 作上标、墨迹顶沿对齐数字顶沿、补回字形右侧空白使墨迹右缘贴页边;图标按类别**真实墨迹范围**(`E1002Renderer._ICON_EXTENT`,多云太阳光芒探出名义框顶部 4 格、雷阵雨闪电垂到云下至 46)竖直居中于温度墨迹中心);下层 **一行「红定位水滴+城市 天气 温区」F12 右对齐贴 790**,与日期行同基线(底沿 79,与时钟基线对齐)。历史(均已否):同日上午「三行信息(天气/温区/城市)左缘对齐|图标 ×1.75|温度 F84 与时钟同高」;2026-09-14「两行(城市/天气·温区)|图标 ×1.5|温度 F72 小一号」;更早「城市 / F24 温度 / 天气·温区」三行右对齐+图标在左)——设备数据来自 **SenseCraft 设备遥测** `sensecraft_api.py`(2026-09-14 新增;先做过「状态栏横排在时钟上方+时钟缩 F60」被否)/天气;B 左 **Claude Usage 两行(5h / 合并行 7d——整行高度不变、**一个大边框**,边框内上下精确对半两条 7px **实心**填充(紧挨、无空隙,2026-09-14 用户要求去掉白隔):上=总 7d、下=fable 7d,各按自身档位取色;右侧百分比与 pace **只显示两者中较大者**,边框与文字随其档位取色;`E1002Renderer._usage_row_split` 以较大者合成一条交给基类 `_usage_row`(已返回条几何)画边框/文字后在内部重绘两条半高实心。2026-09-14 为与右块对称而合并,先做过「两条各带边框的细条+两行数字」版被否;标签去掉 all 前缀)**、右 **Codex Usage 两行(5h / 7d——ChatGPT 订阅的 Codex 额度池;服务端未收到推送时 mock 标「示例数据」)**。宽版用量行的**空值不显示 "--"**:pct 为 None 按 0 画("00%"、pace "00"、空条、全黑中性色),且 pace 列在 diff=0 时三角位画一个等宽等号「=」(2026-09-14 用户要求,原为留空、后短暂用过「-」;已下沉到基类 `_pace`,小屏同样)——各行进度条右端齐平、等宽(2026-09-14;Codex Plus 接口 `secondary_window` 就是 null,原始 `wham/usage` 已核对,5h 行为空是数据如此)。**真 0 的窗口也必须有 pace 列**:Claude 5h 空闲时接口回 `utilization: 0.0, resets_at: null`,算不出 time_pct,早先渲染器遇「有 pct 无 time_pct」整列跳过,该行条就比别行长——现 `data.usage_from_raw` 对 pct=0 且无重置时刻的窗口记 time_pct=0(窗口未开始),基类 `_usage_row` 只要有 pct 就画 pace(time_pct 缺失按 0 差值画 "00"),紧凑单行样式(曾试过撑满高度的大卡片,被否);C **橘喵·今日实时**五列全宽(探店单量/探店流水/团购单量/团购流水/毛利,2026-09-11 接口新增团购字段后由三列改五列;列内四行逐行居中:名 F12 / 值 F24 粗 / 环比 / 同比,与小屏同构——五列每列仅 ~157px,此前「值 F36 粗 + 右侧叠放的环比/同比」并排组约 194px 放不下、降 F24 后 156px 仍贴分隔线,故改竖排;更早试过名与值同行、左上角角标,均已否);D **BTC/USDT 时 K 蜡烛图(近 48 小时 = 2 天,2026-09-14 由 72 根改 48 根)**(免费公开行情:OKX 主源、Coinbase/Huobi 备源,`btc_api.py`,5 分钟缓存;周期/根数 `BTC_BAR`/`BTC_LIMIT` 可配,日 K 也支持;**涨绿跌红**)。此前放过 30 天折线图(用户明确不要)和「近 7 天」数据表(已被 K 线替换)。六色语义:用量进度条(含边框、百分比数字三者同色——2026-09-14 用户要求百分比随条色)与 pace 各自**三档、颜色顺序固定 绿→黑→红、两者独立取色**:用量 ≤ `USAGE_OK` 30 绿「健康」|30~80 黑「正常」|≥ `USAGE_WARN` 80 红「危险」;pace ≤ −`PACE_TOL` 10 绿「节余」|±10 内黑「持平」|≥ +10 红「偏快」。**黄不用**:2026-09-14 曾按要求加过 60%~80% 黄档(含 `USAGE_NOTICE`/`PACE_WARN` 两个阈值),同日被否并整体移除,别再加回。**今日实时的环比/同比「三角 + 数值」涨绿跌红**(2026-09-14 改,与 K 线一致;基类钩子 `_cmp_color`,三色小屏仍涨红);红另用于定位/太阳闪电,蓝=雨雪。 +> **分支 `feature/reterminal-e1002`**(基于 `feature/colorful`):新增第二个目标设备 **Seeed reTerminal E1002**(7.3 寸 **800×480**,E Ink **Spectra 6 原生六色**:黑/白/黄/红/绿/蓝,真机已验证六色量化干净)。该设备不接受局域网直推,而是由 **SenseCraft HMI** 云端的 **HTML 控件**按设备刷新间隔(最小 5 分钟)**每周期重新抓取**一个公网 HTTPS URL、无头浏览器截图后量化下发;不支持 ETag/304,内容不变也会整屏重绘(全刷约 17 秒)。**真机验证(2026-09)**:Image 控件只在填写 URL 时抓一次快照、此后不再更新;HTML 控件填同一 URL 则每周期重抓——所以对外只用 HTML 控件。当前主形态是 **`server.py` 动态服务**(部署公网 HTTPS;局域网地址/明文 HTTP 云端抓不到):三条恒定 URL 并行供对比——方案一 `IMAGE_PATH`(默认 `/e1002.png`)每次请求**内存中现渲染 PNG**(Pillow 点阵版);方案二 `HTML_PATH`(默认 `/e1002.html`)每次请求生成 **800×480 现代网页**(`html_renderer.py`,系统/网络字体 + 内联 SVG K 线,无 JS)由平台截图——**真机实拍:文字抗锯齿灰边被平台抖动成毛边、小字与复杂汉字发糊**;方案三 `HTML_PNG_PATH`(默认 `/e1002-web.png`)同一网页由**服务端无头 Chrome 截图后按最近色(不抖动)固化为六色 PNG**(`html_shot.py`),平台拿到纯色图无从抖动,需服务器装 Chrome。**结论(2026-09-10):方案一为主力**——方案二/三的矢量字体在 125 PPI 下小字必然毛糙(抗锯齿灰边无论由平台还是我们二值化都只能变成锯齿/断笔,见 `output/e1002/font_compare.png`:矢量字体单色渲染 ≈ 抗锯齿后二值化,都不如点阵),Canvas 逐像素画也改变不了这点(Pillow 本身就是逐像素画)。方案二/三端点保留供对比,不再投入。宽版**只用点阵字体的整数倍字号 12/24/36/48/84**(`E1002Renderer.__init__` 把基类的 F13/F16/FPACE 都映射到 12px——用量行 标签常规字重、右对齐贴进度条(基类类属性 `LABEL_BOLD`/`LABEL_ALIGN`/`LABEL_GAP`),百分比与 pace 数字粗体;24px 试过被嫌大;另加 F84 给顶部大时钟),可用 `all(v % 12 == 0 for v in 字体尺寸)` 自查。天气/橘喵/BTC/设备遥测 走**服务端内存缓存 + 有效期**(`WEATHER_TTL`/`MAO_TTL`/`BTC_TTL`/`DEVICE_TTL`,默认 900/60/300/300 秒,0=每次实拉;请求时过期才实拉并更新时间戳,失败沿用旧值、不更新时间戳),服务**不读本机任何文件**;Claude/Codex 用量为**账户级、形态 A**:本机 `main.py --target push` 采集(Claude 复用 Claude Code 钥匙串登录态调 `/api/oauth/usage`;Codex 经 `codex app-server` RPC)后推送原始快照(`X-Push-Token` 鉴权),服务端内存暂存、**不持任何凭证**、渲染时现算 pace。静态形态 `main.py --target e1002`(`output/e1002/index.html` + `dashboard.png`)仍保留。渲染由 `renderer_e1002.py` 完成:继承 `DashboardRenderer` 组件、**k=1 字号与三色版完全相同(不放大,放大后字太大很傻)**,四段版式:A 大时钟 **F84**(墨迹 16~79)+ 其右一列三行、左缘对齐(F12,行距 22):① 电池图标+电量(充电中绿、≤20% 红)② 温度计+温度 | 蓝色水滴+湿度(①② 数字 F12 伪粗体、字号不变,占位 -- 不加粗,2026-09-14)③ 星期/日期(与时钟基线对齐);**右侧天气(2026-09-15 定稿)上下两层**:上层 天气图标 **×1.25** | **实时温度 F48 粗 3**(墨迹顶沿 16 与时钟齐平、底沿 52,右贴页边 790;「°」用 F24 粗 1 作上标、墨迹顶沿对齐数字顶沿、补回字形右侧空白使墨迹右缘贴页边;图标按类别**真实墨迹范围**(`E1002Renderer._ICON_EXTENT`,多云太阳光芒探出名义框顶部 4 格、雷阵雨闪电垂到云下至 46)竖直居中于温度墨迹中心);下层 **一行「红定位水滴+城市 天气 温区」F12 右对齐贴 790**,与日期行同基线(底沿 79,与时钟基线对齐)。历史(均已否):同日上午「三行信息(天气/温区/城市)左缘对齐|图标 ×1.75|温度 F84 与时钟同高」;2026-09-14「两行(城市/天气·温区)|图标 ×1.5|温度 F72 小一号」;更早「城市 / F24 温度 / 天气·温区」三行右对齐+图标在左)——设备数据来自 **SenseCraft 设备遥测** `sensecraft_api.py`(2026-09-14 新增;先做过「状态栏横排在时钟上方+时钟缩 F60」被否)/天气;B 左 **Claude Usage 两行(5h / 合并行 7d——整行高度不变、**一个大边框**,边框内上下精确对半两条 7px **实心**填充(紧挨、无空隙,2026-09-14 用户要求去掉白隔):上=总 7d、下=fable 7d,各按自身档位取色;右侧百分比与 pace **只显示两者中较大者**,边框与文字随其档位取色;`E1002Renderer._usage_row_split` 以较大者合成一条交给基类 `_usage_row`(已返回条几何)画边框/文字后在内部重绘两条半高实心。2026-09-14 为与右块对称而合并,先做过「两条各带边框的细条+两行数字」版被否;标签去掉 all 前缀)**、右 **Codex Usage 两行(5h / 7d——ChatGPT 订阅的 Codex 额度池;服务端未收到推送时 mock 标「示例数据」)**。宽版用量行的**空值不显示 "--"**:pct 为 None 按 0 画("00%"、pace "00"、空条、全黑中性色),且 pace 列在 diff=0 时三角位画一个等宽等号「=」(2026-09-14 用户要求,原为留空、后短暂用过「-」;已下沉到基类 `_pace`,小屏同样)——各行进度条右端齐平、等宽(2026-09-14;Codex Plus 接口 `secondary_window` 就是 null,原始 `wham/usage` 已核对,5h 行为空是数据如此)。**真 0 的窗口也必须有 pace 列**:Claude 5h 空闲时接口回 `utilization: 0.0, resets_at: null`,算不出 time_pct,早先渲染器遇「有 pct 无 time_pct」整列跳过,该行条就比别行长——现 `data.usage_from_raw` 对 pct=0 且无重置时刻的窗口记 time_pct=0(窗口未开始),基类 `_usage_row` 只要有 pct 就画 pace(time_pct 缺失按 0 差值画 "00"),紧凑单行样式(曾试过撑满高度的大卡片,被否);C **橘喵·今日实时**五列全宽(探店单量/探店流水/赏金单量/赏金流水/毛利,2026-09-11 接口新增团购字段后由三列改五列,2026-10-26 团购两列替换为赏金两列——赏金流水=任务奖励 ×2、毛利已含当天审核通过的赏金奖励;列内四行逐行居中:名 F12 / 值 F24 粗 / 环比 / 同比,与小屏同构——五列每列仅 ~157px,此前「值 F36 粗 + 右侧叠放的环比/同比」并排组约 194px 放不下、降 F24 后 156px 仍贴分隔线,故改竖排;更早试过名与值同行、左上角角标,均已否);D **BTC/USDT 时 K 蜡烛图(近 48 小时 = 2 天,2026-09-14 由 72 根改 48 根)**(免费公开行情:OKX 主源、Coinbase/Huobi 备源,`btc_api.py`,5 分钟缓存;周期/根数 `BTC_BAR`/`BTC_LIMIT` 可配,日 K 也支持;**涨绿跌红**)。此前放过 30 天折线图(用户明确不要)和「近 7 天」数据表(已被 K 线替换)。六色语义:用量进度条(含边框、百分比数字三者同色——2026-09-14 用户要求百分比随条色)与 pace 各自**三档、颜色顺序固定 绿→黑→红、两者独立取色**:用量 ≤ `USAGE_OK` 30 绿「健康」|30~80 黑「正常」|≥ `USAGE_WARN` 80 红「危险」;pace ≤ −`PACE_TOL` 10 绿「节余」|±10 内黑「持平」|≥ +10 红「偏快」。**黄不用**:2026-09-14 曾按要求加过 60%~80% 黄档(含 `USAGE_NOTICE`/`PACE_WARN` 两个阈值),同日被否并整体移除,别再加回。**今日实时的环比/同比「三角 + 数值」涨绿跌红**(2026-09-14 改,与 K 线一致;基类钩子 `_cmp_color`,三色小屏仍涨红);红另用于定位/太阳闪电,蓝=雨雪。 ## 命令 @@ -42,7 +42,7 @@ EPD_HOST=epd400a44.local python3 main.py `main.py` 串联三层,各层职责单一、低耦合: -- **`data.py`** — 两种形态:本机 `get_dashboard_data()`(带文件缓存,两路用量经本机采集器);服务端用纯拉取函数 `weather_fetch()`/`mao_fetch()`/`btc_fetch()` + `usage_from_raw(now, raw, windows, title)`(`_usage(now, raw=)`/`_usage_gpt(now, raw=, allow_local=)` 都基于它,raw=None 时前者经 `claude_usage` 本机采集、后者经 `codex_usage` 本机采集,Codex 采集失败或服务端 `allow_local=False` 时用 `gpt_mock_raw()` 并标 mock)。所有「现在」用 `now_tz()`、时间戳格式化用 `_local()`(`TZ=ZoneInfo(config.TZ_NAME)`),本机与境外服务端结果一致。返回固定结构的 dict:`date`/`weather`/`mao`/`usage`/`usage_gpt`/`btc`/`device`(`device` = `{battery, charging, temp, humidity}`,由 `_device(now)` 经 `sensecraft_api` 拉取、文件缓存 `.device_cache.json` TTL=`DEVICE_TTL`,服务端用纯拉取 `device_fetch()`/占位 `device_empty()`)。日期时间用真实系统时间;橘喵今日经营由 `_mao()` 经 `jm_api`、天气由 `_weather()` 经 `weather_api`(和风,定位南京·江宁)、Claude 用量由 `_usage()` 经 `claude_usage`、Codex 用量由 `_usage_gpt()` 经 `codex_usage`,**Claude 任何异常都回退占位 `"--"`,不回退 mock**(Codex 无数据时回退 mock 并显式标「示例数据」)。改数据保持返回结构不变即可(renderer 依赖其 key:`weather.icon` 为和风图标代码;`mao.cols[]` 为 `{k, v, vp, hb, tb, wide}`(`vp` 为数值三段 `(前缀, 主数字, 后缀)`,金额为 `("¥", "2.14", "万")`/不足万 `("¥", "2590", "")`、单量前后缀为空,`v` 是三段拼接的整串供网页版用;2026-09-14 用户要求 ¥ 与 万 字号小一点),五列由 `REALTIME_COLS`(标签、接口字段前缀、格式化、是否仅宽版)驱动,`_col_from()` 用 `.get()` 取 `today{Base}`/`{base}WowRate`/`{base}YoyRate`,字段缺失(旧后端未上团购)该列占位 `--`;`wide=True`(团购两列)小屏 `render()` 跳过、宽版全显;`hb/tb` 为环比/同比 `(direction, text)`,direction ∈ `up`/`down`/`flat`/`None`:`flat` 为增长率恰等于 0,三角位画等号「=」(2026-09-14 用户要求,与用量行 pace 持平一致,此前 0 也画上三角)、`None` 不画三角;接口文档见 `../docs/首页看板接口文档-20260911.md`(毛利口径已含团购、金额与增长率为字符串、增长率可 `null`);`usage.bars[]` 为 `{k(短标签,小屏用), label(长标签 5h/7d/fable 7d,宽版用), pct(或 None), time_pct?, reset?, scoped}`(宽版把 scoped 行并入紧邻其前的非 scoped 行作同一边框内的下半实心)、`usage.warn`/`usage.ok`/`usage.pace_tol` 为用量与 pace 的三档阈值(小屏只用 warn)、`usage.updated` 为快照采集时刻;`usage_gpt` 与 `usage` 同构、标题「Codex Usage」、只有 5h/7d 两行(快照各窗口可带 `window` 真实秒数,pace 优先按它算),服务端未收到推送时为 mock(`mock: True`,88/22 覆盖红/绿两端)。注:`_mao` 仍会拉 `mao.trend`,但两个版面当前都不用它。宽版另有 `btc`:`_btc()` 经 `btc_api.get_candles(BTC_BAR, BTC_LIMIT)` 取 K 线(默认 1H×48),结构 `{symbol, bar, src, candles:[{t, o, h, l, c}], last, chg, chg_label}`,chg 时 K 取相对 24 根前收盘(24h)、日 K 取相对上一根;缓存 `.btc_cache.json`(TTL=`BTC_TTL` 默认 5 分钟,周期变更即失效,失败沿用旧缓存,仍无则 candles 为空 → 面板 `--`)。 +- **`data.py`** — 两种形态:本机 `get_dashboard_data()`(带文件缓存,两路用量经本机采集器);服务端用纯拉取函数 `weather_fetch()`/`mao_fetch()`/`btc_fetch()` + `usage_from_raw(now, raw, windows, title)`(`_usage(now, raw=)`/`_usage_gpt(now, raw=, allow_local=)` 都基于它,raw=None 时前者经 `claude_usage` 本机采集、后者经 `codex_usage` 本机采集,Codex 采集失败或服务端 `allow_local=False` 时用 `gpt_mock_raw()` 并标 mock)。所有「现在」用 `now_tz()`、时间戳格式化用 `_local()`(`TZ=ZoneInfo(config.TZ_NAME)`),本机与境外服务端结果一致。返回固定结构的 dict:`date`/`weather`/`mao`/`usage`/`usage_gpt`/`btc`/`device`(`device` = `{battery, charging, temp, humidity}`,由 `_device(now)` 经 `sensecraft_api` 拉取、文件缓存 `.device_cache.json` TTL=`DEVICE_TTL`,服务端用纯拉取 `device_fetch()`/占位 `device_empty()`)。日期时间用真实系统时间;橘喵今日经营由 `_mao()` 经 `jm_api`、天气由 `_weather()` 经 `weather_api`(和风,定位南京·江宁)、Claude 用量由 `_usage()` 经 `claude_usage`、Codex 用量由 `_usage_gpt()` 经 `codex_usage`,**Claude 任何异常都回退占位 `"--"`,不回退 mock**(Codex 无数据时回退 mock 并显式标「示例数据」)。改数据保持返回结构不变即可(renderer 依赖其 key:`weather.icon` 为和风图标代码;`mao.cols[]` 为 `{k, v, vp, hb, tb, wide}`(`vp` 为数值三段 `(前缀, 主数字, 后缀)`,金额为 `("¥", "2.14", "万")`/不足万 `("¥", "2590", "")`、单量前后缀为空,`v` 是三段拼接的整串供网页版用;2026-09-14 用户要求 ¥ 与 万 字号小一点),五列由 `REALTIME_COLS`(标签、接口字段前缀、格式化、是否仅宽版)驱动,`_col_from()` 用 `.get()` 取 `today{Base}`/`{base}WowRate`/`{base}YoyRate`,字段缺失(旧后端未上赏金字段)该列占位 `--`;`wide=True`(赏金两列)小屏 `render()` 跳过、宽版全显;`hb/tb` 为环比/同比 `(direction, text)`,direction ∈ `up`/`down`/`flat`/`None`:`flat` 为增长率恰等于 0,三角位画等号「=」(2026-09-14 用户要求,与用量行 pace 持平一致,此前 0 也画上三角)、`None` 不画三角;接口文档见 `../docs/首页看板接口文档-20260911.md`(毛利口径已含团购与当天审核通过的赏金、金额与增长率为字符串、增长率可 `null`);`usage.bars[]` 为 `{k(短标签,小屏用), label(长标签 5h/7d/fable 7d,宽版用), pct(或 None), time_pct?, reset?, scoped}`(宽版把 scoped 行并入紧邻其前的非 scoped 行作同一边框内的下半实心)、`usage.warn`/`usage.ok`/`usage.pace_tol` 为用量与 pace 的三档阈值(小屏只用 warn)、`usage.updated` 为快照采集时刻;`usage_gpt` 与 `usage` 同构、标题「Codex Usage」、只有 5h/7d 两行(快照各窗口可带 `window` 真实秒数,pace 优先按它算),服务端未收到推送时为 mock(`mock: True`,88/22 覆盖红/绿两端)。注:`_mao` 仍会拉 `mao.trend`,但两个版面当前都不用它。宽版另有 `btc`:`_btc()` 经 `btc_api.get_candles(BTC_BAR, BTC_LIMIT)` 取 K 线(默认 1H×48),结构 `{symbol, bar, src, candles:[{t, o, h, l, c}], last, chg, chg_label}`,chg 时 K 取相对 24 根前收盘(24h)、日 K 取相对上一根;缓存 `.btc_cache.json`(TTL=`BTC_TTL` 默认 5 分钟,周期变更即失效,失败沿用旧缓存,仍无则 candles 为空 → 面板 `--`)。 - **缓存**(为 launchd 每 5 分钟独立进程而设,必须落盘):天气 `.weather_cache.json`(TTL=`WEATHER_TTL`,默认 15min)、趋势 `.trend_cache.json`(按日期 key,每天只拉一次)、BTC K 线 `.btc_cache.json`(TTL=`BTC_TTL`,默认 5min);三者拉取失败沿用旧缓存。两路用量各有采集缓存 `.claude_usage_cache.json`/`.codex_usage_cache.json`(`usage_common.cached_snapshot`:距上次成功 0 超前↑ / <0 节余↓);窗口空闲(pct=0 且 `resets_at` 为 null)记 time_pct=0;故即使 `utilization` 来自缓存/推送暂存,pace 仍随时钟准确推进。5h 窗口 18000s、7d 窗口 604800s(Codex 快照带 `window` 时用真实值)。 - **`jm_api.py`** — jm-devops 后端统计接口客户端,**设备密钥授权**(`grant_type=client_secret`,免验证码)。先用 `CLIENT_ID + CLIENT_SECRET` 调 `POST /auth/login` 换 token(进程内缓存、按 `expire_in` 提前刷新),再带 `Authorization: Bearer ` + `clientid` 头调 `/system/statistics/{realtime,recent-days}`;遇 401 清 token 重登一次重试。token 字段为蛇形 `access_token`/`expire_in`。注:历史上曾免鉴权直调、更早曾用账号密码+验证码 OCR 登录(ddddocr 不稳定),现统一为 client_secret 授权——密钥即长期凭证,务必走 HTTPS、优先用 `JM_CLIENT_SECRET` 环境变量、可在后端 `sys_client` 轮换。 diff --git a/data.py b/data.py index a65f87e..41eba2a 100644 --- a/data.py +++ b/data.py @@ -179,13 +179,14 @@ def _placeholder_col(k, wide=False): # 今日实时五列:(标签、接口字段前缀、格式化、是否仅宽版)。字段前缀 base 对应 today{Base} / {base}WowRate / {base}YoyRate, -# 见 docs/首页看板接口文档-20260911.md。团购两列为新增字段,旧后端无此键 → 占位 "--"(不报错、不影响其余列)。 +# 见 docs/首页看板接口文档-20260911.md。赏金两列为新增字段,旧后端无此键 → 占位 "--"(不报错、不影响其余列)。 +# 2026-10-26 宽版团购两列替换为赏金两列(团购字段接口仍返回,此处不再展示)。 REALTIME_COLS = ( ("探店单量", "orderCount", _fmt_count, False), ("探店流水", "payAmount", _fmt_money, False), - ("团购单量", "groupbuyOrderCount", _fmt_count, True), - ("团购流水", "groupbuyPayAmount", _fmt_money, True), - ("毛利", "grossProfit", _fmt_money, False), # 口径已含团购毛利(接口合计,未拆分) + ("赏金单量", "bountyOrderCount", _fmt_count, True), # 当天报名单量 + ("赏金流水", "bountyPayAmount", _fmt_money, True), # 任务奖励 × 2(奖励为返给用户的部分) + ("毛利", "grossProfit", _fmt_money, False), # 口径已含团购毛利+当天审核通过的赏金奖励(接口合计,未拆分) ) diff --git a/renderer.py b/renderer.py index 62efc6c..b2e3ae9 100644 --- a/renderer.py +++ b/renderer.py @@ -508,7 +508,7 @@ class DashboardRenderer: # ---- C. 底部:橘喵·今日实时(三列,版式对齐主分支:标题+18=名/+34=值/+64=环比/+82=同比)---- self._label(193, "橘喵·今日实时", right=f'上次更新 {mao["upd"]}') - cols = [c for c in mao["cols"] if not c.get("wide")]; n = len(cols) # 仅宽版的列(团购)小屏放不下,跳过 + cols = [c for c in mao["cols"] if not c.get("wide")]; n = len(cols) # 仅宽版的列(赏金)小屏放不下,跳过 ax0, ax1 = 8, 392 cw = (ax1 - ax0) / n for i in range(1, n): diff --git a/renderer_e1002.py b/renderer_e1002.py index 87a2521..97e6eb8 100644 --- a/renderer_e1002.py +++ b/renderer_e1002.py @@ -15,7 +15,7 @@ reTerminal E1002 渲染层 —— 复用 renderer.DashboardRenderer 的组件( 行组件沿用三色版的紧凑单行 `_usage_row`(行距 31);**三处文字统一 12px**(基类 F13/F16/FPACE 都映射到 12px): 标签**常规字重、在标签列内右对齐**(贴进度条),百分比与 pace 数字**粗体**(曾试过 24px,用户嫌大), 标签用 bar["label"](statusline 同款写法),列宽按最长标签自适应;标题右侧「上次更新 HH:MM」与今日实时同款。 - C 今日(218~322):**橘喵·今日实时** 五列全宽(探店单量/探店流水/团购单量/团购流水/毛利):列内四行逐行居中—— + C 今日(218~322):**橘喵·今日实时** 五列全宽(探店单量/探店流水/赏金单量/赏金流水/毛利):列内四行逐行居中—— 名 F12 / 值 F24 粗 / 环比 / 同比(五列每列 ~157px,原「值 F36 + 右侧叠放环比/同比」并排组放不下)。 D 行情(~336~470):**BTC/USDT 时K(近 48 小时)** 蜡烛图(`_btc_chart`,数据来自 btc_api:OKX 主源、Coinbase/Huobi 备源,5 分钟缓存; 周期/根数由 BTC_BAR/BTC_LIMIT 配置)——标题右侧最新价 + 24h 涨跌幅;**涨(收≥开)绿、跌红**,实心蜡烛 + 1px 影线; @@ -202,7 +202,7 @@ class E1002Renderer(DashboardRenderer): self.d.rectangle([bx0 + k, top, min(bx0 + fw, bx1 - k), bot], fill=fill) def _realtime(self, y0, mao, x0=8, x1=None): - """橘喵·今日实时五列(宽版):探店单量 / 探店流水 / 团购单量 / 团购流水 / 毛利,列内四行逐行水平居中: + """橘喵·今日实时五列(宽版):探店单量 / 探店流水 / 赏金单量 / 赏金流水 / 毛利,列内四行逐行水平居中: 指标名 F12(y0+21)→ 值 F24 粗(y0+38)→ 环比(y0+72)→ 同比(y0+88,行距 16);按墨迹框算,标题↔名 ~9px(原 ~5px 太挤,整列下移 4px)、 名↔值 ~9px、值↔环比 ~6px、环比↔同比 5px。 为什么不再是「值 F36 + 右侧叠放环比/同比」的并排组:五列每列仅 ~157px,F36 并排组(如「2.12万」102+间隙+环比 77)