最初系统没有任何持久化:数据分发是进程内的,统计存在内存里,界面的”历史”是一张上限 500 行的表格控件。 进程一退,数据全没了。随后有两个需求:手机上要能看历史;数据要能传到云上保存。
顺序是确定的:先做本地历史,再做上云。没有本地历史库,上传器只能自己再攒一份数据,等于把持久化做两遍; 有了它,上云的”断网后补传”也直接拿它当待发队列。
三个通道、约 3 秒一轮,即约 1 条/秒:一天约 8.4 万条、4 MB;一年约 3000 万条、1.5 GB。
标准库 sqlite3 足够,不需要新依赖,也不需要外部数据库服务。
历史库的接口(HistoryStore 协议)定义在消费方 service/history.py,这与语言模型客户端的做法一致。
分歧在于 SQLite 实现放哪:
| 方案 | 取舍 |
|---|---|
新增顶层包 storage/ |
与 llm/ 同构:”接外部资源的适配层”,只是接的是磁盘。边界最清楚;代价是多一个包 |
放进 application/ |
不动包结构;但组合根里塞进一个带表结构与事务的存储实现,这一层的边界会变模糊 |
放进 service/ |
最省事;但服务层从此持有文件路径和数据库连接,与”服务层不碰物理资源”冲突 |
选第一种。现在”谁可以碰外部资源”只需一句话回答:communication 接串口,llm 接模型服务,
storage 接磁盘,三者都不被 service import,都只在组合根里创建。
历史记录器是数据分发服务的又一个订阅者,与统计、通风两个处理器地位相同。 但数据回调跑在采集线程上,下一步就是取模型结果,每条读数同步写盘会把界面拖慢。
所以记录器在内存里攒批,满 50 条或距最早一条超过 5 秒才写一次,退出前刷盘;SQLite 开 WAL。 这是 05 那条结论的又一次应用。
读数的值类型是”任意”——硬件与 Modbus 两条路径都从 JSON 里取值。非数值的读数计数而不抛异常, 一条坏读数不会连累同一批里的其他读数。
最初的设想是后台定时上传。改成了手动运行一个脚本:
系统运行期间只往本地库写,不含任何定时器、后台线程或上传队列。 想上云时双击启动器,它把”还没导出的时段”一次补齐并上传。
这样断网重试、退避策略、后台失败告警统统不需要——下次运行自然补上,而”下次”由人决定。
几个具体规则:
env_YYYYMMDD_HH.csv)。这是切分粒度,不是上传频率:补传、去重、出错重跑都以小时为单位--snapshot:也导当前小时,但不登记台账、文件名带分钟并标明”快照”,整点后那一小时仍会完整导出一次| 选项 | 取舍 |
|---|---|
| 导出到网盘客户端的同步目录 | 零网络代码、零凭证;但上传是外部工具代劳的,系统本身不具备上云能力 |
| 网盘开放平台 API | 要申请开发者审核、令牌 30 天要刷新,第三方实践里有按 IP 限流与风控提示 |
| 对象存储(OSS) | 需要一份子账号密钥与一个存储桶;无审核、无风控、可脚本化,上传是系统自己的能力 |
选对象存储。只有 PC 一端上传,一份长期密钥即可,不需要临时凭证。
凭证放在本地配置文件里,被 .gitignore 排除。
测试不可能用到真实凭证。 曾经有一个没打桩的用例读到了默认凭证、真的连上了公网。
现在 tests/conftest.py 自动兜底,并按模块列表遍历——逐个打桩的话,每加一个碰凭证的脚本就要记得补一处,漏一处就是拿真密钥连公网。
本期不做。它要在模型的分类提示里新增标签,从而作废现有的两套评测基线; “昨天”“前两个小时”这类时间范围必须由代码解析、不能让模型给(与”阈值数值只从原话里取”同理), 还要把日期时刻登记进事实对象,否则改写会被接地检查整句退回。
于是有一个必须说清楚的口径差:历史库从第一次启动起是连续的,而问答的统计每次重启清零。 “今天最高多少度”可能与历史页对不上——两个数都是真的,差在覆盖时段。处理方式是在回答里写明覆盖范围。
api.query_history() 取数