Files
home-automation/docs/current-system-inventory.md
T

558 lines
24 KiB
Markdown
Raw 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.
# 当前系统盘点
本文档用于盘点当前 branch 上的 Go 实现,并将其作为后续 Python 重构的唯一事实基线。
## 范围与基线
- 当前事实基线:`legacy/go-backend/src/` 下的 Go 代码
- 不纳入当前基线:更早的 Python 版本
- 主入口:[`legacy/go-backend/src/cmd/serve.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/cmd/serve.go:62)
## 系统概览
当前应用是一个单进程 Go HTTP 服务,具备以下特征:
- 暴露少量 REST API
- 使用本地 SQLite 做持久化
- 调用 Home Assistant API 和 webhook
- 通过 OAuth 和 REST API 集成 TickTick
- 当前仍依赖 Notion 做 poo 记录同步
- 内置一个每日执行的定时同步任务
进程启动后会先读取 YAML 配置文件,再初始化 Notion 与 TickTick 工具层、初始化各业务组件自己的 SQLite 数据库、注册路由、启动调度器,最后在配置的端口上提供 HTTP 服务。可参考 [`src/cmd/serve.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/cmd/serve.go:65) 和 [`src/cmd/serve.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/cmd/serve.go:104)。
## API 盘点
### `GET /status`
- 路由定义:[`src/cmd/serve.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/cmd/serve.go:106)
- 用途:基础存活检查
- 请求参数:无
- 请求体:无
- 响应:纯文本 `OK`
- 鉴权:当前代码中无鉴权
- 调用方类型:通用健康检查,可能用于本地监控或 supervisor 级别的探活
### `GET /poo/latest`
- 路由定义:[`src/cmd/serve.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/cmd/serve.go:110)
- 处理函数:[`pooRecorder.HandleNotifyLatestPoo`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/pooRecorder/pooRecorder.go:87)
- 用途:将最新一条 poo 状态重新发布到 Home Assistant 的 sensor state
- 请求参数:无
- 请求体:无
- 响应:
- 成功:空响应体,默认 HTTP 200
- 失败:通过 `http.Error(...)` 返回文本错误信息
- 鉴权:当前代码中无鉴权
- 外部调用方:
- 会被 `POST /homeassistant/publish` 间接触发,当 `target=poo_recorder``action=get_latest` 时,代码会通过内部 HTTP 请求访问 `http://localhost:{port}/poo/latest`,见 [`src/components/homeassistant/homeassistant.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/homeassistant/homeassistant.go:110)
- 副作用:
-`poo_records` 读取最新一条记录
- 调用 Home Assistant `/api/states/{entity_id}` 更新 sensor 状态,见 [`src/util/homeassistantutil/homeassistantutil.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/util/homeassistantutil/homeassistantutil.go:65)
### `POST /poo/record`
- 路由定义:[`src/cmd/serve.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/cmd/serve.go:111)
- 处理函数:[`pooRecorder.HandleRecordPoo`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/pooRecorder/pooRecorder.go:57)
- 用途:记录一条 poo 事件,同时镜像到 Notion、刷新 Home Assistant sensor,并可选触发一个 Home Assistant webhook
- 请求体 JSON
- `status: string`
- `latitude: string`
- `longitude: string`
- 请求校验:
- JSON decoder 开启了 `DisallowUnknownFields`
- 如果配置里缺少 `pooRecorder.tableId`,请求会直接失败,虽然从纯本地 DB 角度看本来仍有可能写入成功
- 响应:
- 成功:空响应体,默认 HTTP 200
- 请求错误:返回 decoder 错误文本,HTTP 400
- 服务端错误:返回错误文本,HTTP 500
- 鉴权:当前代码中无鉴权
- 外部调用方:大概率是 Home Assistant、移动端 shortcut、或手工调用;代码中没有明确写死调用方
- 副作用:
- 向 SQLite `poo_records` 插入一条记录
- 异步向 Notion 追加一行
- 同步发布最新 Home Assistant sensor 状态
- 如果存在 `pooRecorder.webhookId`,异步触发一个 Home Assistant webhook
### `POST /homeassistant/publish`
- 路由定义:[`src/cmd/serve.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/cmd/serve.go:112)
- 处理函数:[`HomeAssistant.HandleHaMessage`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/homeassistant/homeassistant.go:36)
- 用途:接收自动化消息,根据 `target``action` 做分发
- 请求体 JSON
- `target: string`
- `action: string`
- `content: string`
- 请求校验:
- JSON decoder 开启了 `DisallowUnknownFields`
- 响应:
- 成功路径:空响应体,默认 HTTP 200
- 失败路径:通常为空响应体并返回 HTTP 500TickTick auth 回调是单独的接口,不在这里
- 鉴权:当前代码中无鉴权
- 外部调用方:设计意图上是给 Home Assistant automation 消息调用
当前代码支持的消息契约如下:
- `target=poo_recorder`, `action=get_latest`
- 转发到本地 `GET /poo/latest`
- 见 [`src/components/homeassistant/homeassistant.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/homeassistant/homeassistant.go:72)
- `target=location_recorder`, `action=record`
- `content` 预期是一个 JSON 风格字符串,实际很可能使用单引号
- 当前代码会用 `strings.ReplaceAll(message.Content, "'", "\"")` 做归一化
- 然后转发到本地 `POST /location/record`
- 见 [`src/components/homeassistant/homeassistant.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/homeassistant/homeassistant.go:82)
- `target=ticktick`, `action=create_action_task`
- `content` 预期可解析为:
- `action: string`
- `due_hour: int`
- 当前代码会忽略调用方传来的 `title` 字段,而是把 `action` 映射为 TickTick task title
- 到期时间的计算方式是:取 `now + due_hour` 后所在日期的“次日零点”,再转成 TickTick 使用的时间格式
- 最终在配置指定的 TickTick project 中创建任务
- 见 [`src/components/homeassistant/homeassistant.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/homeassistant/homeassistant.go:124)
不支持的 `target``action` 会返回 HTTP 500,并打 warning 日志。相关测试在 [`src/components/homeassistant/homeassistant_test.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/homeassistant/homeassistant_test.go:68)。
### `POST /location/record`
- 路由定义:[`src/cmd/serve.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/cmd/serve.go:114)
- 处理函数:[`locationRecorder.HandleRecordLocation`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/locationRecorder/locationRecorder.go:43)
- 用途:记录人的位置点,用于人生轨迹 / movement history
- 请求体 JSON
- `person: string`
- `latitude: string`
- `longitude: string`
- `altitude: string`,从请求结构上看是可选,但代码里即使为空也会被解析成 `0`
- 请求校验:
- JSON decoder 开启了 `DisallowUnknownFields`
- 数值解析错误会被忽略;如果 `latitude` / `longitude` / `altitude` 不是合法数字,当前实现会静默落成 `0`
- 响应:
- 成功:空响应体,默认 HTTP 200
- 请求错误:返回 decoder 错误文本,HTTP 400
- 鉴权:当前代码中无鉴权
- 外部调用方:
- 可被任意客户端直接调用
- 也会被 `POST /homeassistant/publish` 间接触发
- 副作用:
- 向 SQLite `location` 表插入一条记录
### `GET /ticktick/auth/code`
- 路由定义:[`src/cmd/serve.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/cmd/serve.go:116)
- 处理函数:[`TicktickUtilImpl.HandleAuthCode`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/util/ticktickutil/ticktickutil.go:103)
- 用途:TickTick OAuth redirect callback
- Query 参数:
- `state`
- `code`
- 响应:
- 成功:纯文本 `Authorization successful`
- 失败:纯文本错误信息,HTTP 400 或 500
- 鉴权:
- 通过 OAuth `state` 与进程内保存的 `authState` 做校验
- 没有额外的 session 或用户级鉴权
- 外部调用方:TickTick OAuth redirect
- 副作用:
- 用 authorization code 换取 access token
- 通过 `viper.WriteConfig()``ticktick.token` 写回 YAML 配置文件
## 外部集成盘点
### TickTick
- 在 Python 重构中的状态:应保留
- 主要文件:
- [`src/util/ticktickutil/ticktickutil.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/util/ticktickutil/ticktickutil.go:1)
- [`src/components/homeassistant/homeassistant.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/homeassistant/homeassistant.go:100)
- 当前职责:
- 初始化 TickTick 鉴权状态
- 当 token 缺失时启动 OAuth 授权
- 接收 OAuth callback 并持久化 token
- 读取 project 下的 tasks
- 若不存在同名任务,则创建新任务
- 连接方式:
- OAuth authorization code flow
- 调用 `https://ticktick.com/oauth/token`
- 调用 `https://api.ticktick.com/open/v1/...`
- 依赖的配置项:
- `ticktick.clientId`
- `ticktick.clientSecret`
- `ticktick.redirectUri`
- `ticktick.token`
- 关键实现依赖:
- 原生 `net/http`
- `viper`,同时承担配置读取和配置回写
- 迁移高风险点:
- OAuth callback 的 `state` 只保存在进程内;如果服务在授权开始和回调完成之间重启,流程会断
- token 直接写回 YAML 配置文件,虽然简单,但运维上比较脆弱
- 去重逻辑只按 task title 精确匹配
- due date 的计算语义是隐含在代码里的,重构前应先冻结
- `Init()` 会在启动时积极检查配置,且在 token 缺失时打印手动授权 URL;Python 版需要明确是否仍要在启动阶段卡住这一流程
### Home Assistant
- 在 Python 重构中的状态:应保留
- 主要文件:
- [`src/components/homeassistant/homeassistant.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/homeassistant/homeassistant.go:1)
- [`src/util/homeassistantutil/homeassistantutil.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/util/homeassistantutil/homeassistantutil.go:1)
- 当前职责:
- 接收来自 Home Assistant automations 的命令 envelope
- 将命令转发给本地模块
- 把 sensor state 发布回 Home Assistant
- 在 poo 记录后触发 Home Assistant webhook
- 连接方式:
- 入站 webhook 风格 JSON 接口:`POST /homeassistant/publish`
- 出站 REST 调用:Home Assistant `/api/states/{entity_id}`
- 出站 webhook 调用:`/api/webhook/{webhook_id}`
- 出站调用使用 bearer token
- 依赖的配置项:
- `homeassistant.ip`
- `homeassistant.port`
- `homeassistant.authToken`
- `homeassistant.actionTaskProjectId`
- `pooRecorder.webhookId`
- `pooRecorder.sensorEntityName`
- `pooRecorder.sensorFriendlyName`
- 关键实现依赖:
- 原生 `net/http`
- 通过 `localhost:{port}` 发起自调用,而不是直接走函数调用
- 迁移高风险点:
- 入站 `/homeassistant/publish` 当前没有鉴权
- 当前命令 envelope 里的 `content` 是字符串,且常带单引号,现有客户端可能依赖这种非标准格式
- 模块间当前是通过自调用 HTTP 和 1 秒 timeout 编排的
- sensor 发布和 webhook 触发都属于强副作用行为,需要在兼容性测试里单独覆盖
### Notion
- 在 Python 重构中的状态:当前存在,但按已知目标不计划默认保留
- 主要文件:
- [`src/util/notion/notion.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/util/notion/notion.go:1)
- [`src/components/pooRecorder/pooRecorder.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/pooRecorder/pooRecorder.go:191)
- helper CLI[`src/helper/poo_recorder_helper/cmd/reverse.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/helper/poo_recorder_helper/cmd/reverse.go:21)
- 当前职责:
- 使用 config token 初始化 Notion client
- 读取 / 写入 poo 记录对应的表格行
- 每日做 SQLite 和 Notion 的双向同步
- 提供一个反转 Notion 表顺序的辅助 CLI
- 连接方式:
- 通过 `github.com/jomei/notionapi` 调用 Notion API
- token 鉴权
- 依赖的配置项:
- `notion.token`
- `pooRecorder.tableId`
- 关键实现依赖:
- `github.com/jomei/notionapi`
- 迁移高风险点:
- 当前服务启动时如果缺少 `notion.token` 会直接退出,即便 Notion 并不是系统所有功能都需要的基础能力
- `POST /poo/record` 当前要求 `pooRecorder.tableId` 存在,并会异步镜像到 Notion
- 每日定时同步会同时改写 Notion 和 SQLite,若 Python 版移除这一行为,数据一致性预期会发生变化
## 数据库与 Schema 盘点
### 数据库类型
- 当前使用 SQLite 做组件级持久化
- poo recorder 中显式导入了 SQLite driver,见 [`src/components/pooRecorder/pooRecorder.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/pooRecorder/pooRecorder.go:20)
- location recorder 也通过 driver 名 `sqlite` 打开 SQLite,见 [`src/components/locationRecorder/locationRecorder.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/locationRecorder/locationRecorder.go:80)
### Poo recorder 数据库
- 配置项:`pooRecorder.dbPath`
- 默认路径:`pooRecorder.db`
- migration 机制:
- 手写 `PRAGMA user_version`
- 当前有效版本可以认为是 `1`
- 目前只实现了 `0 -> 1`
- 表:
- `poo_records`
- schema 定义见 [`src/components/pooRecorder/pooRecorder.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/pooRecorder/pooRecorder.go:162)
- 字段:
- `timestamp TEXT PRIMARY KEY`
- `status TEXT NOT NULL`
- `latitude REAL NOT NULL`
- `longitude REAL NOT NULL`
- 核心用途:
- 用于查询最新 poo 状态并发布到 Home Assistant sensor
- 作为本地 poo 历史的持久化来源
- 作为 Notion 双向同步的本地数据源和数据汇
- 明显核心字段:
- `timestamp`
- `status`
- `latitude`
- `longitude`
- 可能属于历史包袱 / 后续需要再判断的点:
- 当前实现与 Notion 表行结构高度耦合
- 时间戳是字符串,格式为 `2006-01-02T15:04Z07:00`,不是带秒的完整 RFC3339
- API 请求模型接受的经纬度是字符串,因此 Python 版的类型规范化要小心兼容
### Location recorder 数据库
- 配置项:`locationRecorder.dbPath`
- 默认路径:`location_recorder.db`
- migration 机制:
- 手写 `PRAGMA user_version`
- 当前版本 `2`
- 已实现 migration
- `0 -> 1`:建表
- `1 -> 2`:把旧 datetime 字符串改写成 RFC3339 UTC
- 表:
- `location`
- schema 定义见 [`src/components/locationRecorder/locationRecorder.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/locationRecorder/locationRecorder.go:115)
- 字段:
- `person TEXT NOT NULL`
- `datetime TEXT NOT NULL`
- `latitude REAL NOT NULL`
- `longitude REAL NOT NULL`
- `altitude REAL`
- 主键 `(person, datetime)`
- 核心用途:
- 持久化人生轨迹 / 位置点记录
- 明显核心字段:
- `person`
- `datetime`
- `latitude`
- `longitude`
- 可能属于历史包袱 / 后续需要再判断的点:
- `altitude` 在语义上是可选,但当前入站解析会把缺失和非法值一起压成 `0`
- 当前没有查询 API,这张表目前主要承担“只写不读”的存储角色
### 跨模块数据库观察
- 当前没有统一的共享 schema;每个组件各自打开自己的 SQLite 文件
- 没有使用 ORM
- 没有统一 migration 框架
- 除主键外,没有看到额外索引
- `poo` 的常规写入和异步 Notion 镜像之间没有事务保证,一致性更接近 best-effort
## 业务模块拆分
### 1. HTTP 外壳 / 应用启动层
- 职责:
- 读取配置
- 设置日志级别
- 管理 scheduler 生命周期
- 注册路由
- 处理优雅退出
- 主要文件:[`src/cmd/serve.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/cmd/serve.go:62)
- 依赖:
- 所有业务模块
- 迁移判断:
- 这部分后续应成为 FastAPI 的 app 装配层
- 可以在早期先迁为“薄壳”
### 2. Poo recorder
- 职责:
- 接收 poo 记录
- 持久化本地 poo 历史
- 向 Home Assistant 发布最新状态 sensor
- 触发可选 Home Assistant webhook
- 与 Notion 做同步
- 主要文件:[`src/components/pooRecorder/pooRecorder.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/pooRecorder/pooRecorder.go:50)
- 依赖:
- SQLite
- Home Assistant util
- Notion util
- scheduler
- 迁移判断:
- 这是功能上重要、但耦合也最重的模块
- 适合在设计上先拆成:
- poo API / service
- Home Assistant 发布适配层
- legacy Notion sync adapter
### 3. Location recorder
- 职责:
- 接收位置更新
- 持久化人生轨迹点
- 主要文件:[`src/components/locationRecorder/locationRecorder.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/locationRecorder/locationRecorder.go:39)
- 依赖:
- SQLite
- 迁移判断:
- 相对独立
- 很适合作为优先迁移对象
- 但需要先明确数值校验规则,因为当前实现会把非法数字静默压成 `0`
### 4. Home Assistant 命令路由层
- 职责:
- 接收命令 envelope
- 根据 target/action 调度 poo、location、ticktick 行为
- 主要文件:[`src/components/homeassistant/homeassistant.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/homeassistant/homeassistant.go:36)
- 依赖:
- 本地服务端口
- TickTick util
- 迁移判断:
- 它是外部 automations 的关键契约
- 应在迁移早中期就被冻结和复刻
### 5. TickTick adapter
- 职责:
- OAuth callback
- project / task REST 操作
- 任务去重
- 主要文件:[`src/util/ticktickutil/ticktickutil.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/util/ticktickutil/ticktickutil.go:81)
- 依赖:
- TickTick API
- 可写配置文件
- 迁移判断:
- 作为内部 adapter 相对独立
- 复杂度中等,主要难点是 OAuth 和 token 持久化
### 6. Home Assistant 出站 client
- 职责:
- 发布 sensor state
- 触发 webhook
- 主要文件:[`src/util/homeassistantutil/homeassistantutil.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/util/homeassistantutil/homeassistantutil.go:30)
- 依赖:
- Home Assistant API/token
- 迁移判断:
- 小而独立
- 很适合作为较早迁移的适配层
### 7. Notion adapter 与辅助 CLI
- 职责:
- 读写 Notion table rows
- 维护 poo 相关表格的辅助操作
- 主要文件:
- [`src/util/notion/notion.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/util/notion/notion.go:14)
- [`src/helper/poo_recorder_helper/cmd/reverse.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/helper/poo_recorder_helper/cmd/reverse.go:21)
- 迁移判断:
- 当前存在,但按已知方向应视为 planned non-migration
- 更适合被标记为 legacy 模块,而不是直接带进 Python 主体
### 8. 辅助 helper CLI
- `src/helper/poo_recorder_helper`
- 用于 Notion 表反转的运维辅助工具
- `src/helper/location_recorder`
- 当前基本还是脚手架,没有实质业务逻辑
- 迁移判断:
- 二者都不是后端重构的核心目标
- `poo_recorder_helper` 应随着 Notion 一并视作 legacy
## 运行方式与部署形态
### 配置方式
- 配置文件名:`config.yaml`
- 搜索路径:
- 当前工作目录
- `$HOME/.config/home-automation`
- 配置加载代码:[`src/cmd/serve.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/cmd/serve.go:65)
- 开启了 `viper.WatchConfig()`
代码中实际出现的配置项包括:
- `port`
- `logLevel`
- `notion.token`
- `ticktick.clientId`
- `ticktick.clientSecret`
- `ticktick.redirectUri`
- `ticktick.token`
- `homeassistant.ip`
- `homeassistant.port`
- `homeassistant.authToken`
- `homeassistant.actionTaskProjectId`
- `pooRecorder.tableId`
- `pooRecorder.webhookId`
- `pooRecorder.sensorEntityName`
- `pooRecorder.sensorFriendlyName`
- `pooRecorder.dbPath`
- `locationRecorder.dbPath`
### 进程模型
- 单个二进制,通过 Cobra 子命令 `serve` 启动
- 监听 `SIGINT` / `SIGTERM` 做优雅退出
- scheduler 与 HTTP server 在同一进程内运行
- 路由层使用 `gorilla/mux`
### 定时任务
- 每天 `0 5 * * *` 执行一次 poo records 与 Notion 的同步
- 定义在 [`src/components/pooRecorder/pooRecorder.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/pooRecorder/pooRecorder.go:180)
### 被动接收式接口
- 上面列出的入站 REST API
- TickTick OAuth callback endpoint
### 运行时依赖的外部服务
- Home Assistant HTTP API 与 webhook
- TickTick OAuth 与 REST API
- 当前 poo 流程仍依赖 Notion API
- 本地可写文件系统,用于配置文件和 SQLite DB
### 当前本地 / 服务部署形态
- 安装脚本会构建 Go 二进制,并安装到 `$HOME/.local/home-automation-backend`
- 使用 Supervisor 管理进程
- 生成的 supervisor 配置最终执行 `{binary} serve`
- 参考:
- [`helper/install.sh`](/home/tianyu/workspace/home-automation/legacy/go-backend/helper/install.sh:45)
- [`helper/home_automation_backend_template.conf`](/home/tianyu/workspace/home-automation/legacy/go-backend/helper/home_automation_backend_template.conf:1)
### 容器化情况
- 这一轮代码扫描中没有发现 `Dockerfile` 或 compose 文件
- 当前部署形态是 supervisor-based,而不是 container-based
## 测试与文档现状
### 测试现状
- 只发现一个测试文件:[`src/components/homeassistant/homeassistant_test.go`](/home/tianyu/workspace/home-automation/legacy/go-backend/src/components/homeassistant/homeassistant_test.go:1)
- 当前测试覆盖:
- 入站命令 JSON 解码
- target/action 路由分发
- 转发到 poo/location handler 的行为
- TickTick task 创建委托
- 错误日志和失败路径
- 当前没有覆盖:
- poo recorder 的 DB 行为
- location recorder 的 DB 行为
- TickTick OAuth 流程
- Home Assistant 出站发布
- Notion sync 逻辑
- 启动与配置加载
- scheduler 行为
### 本轮测试执行情况
- 我尝试在 `legacy/go-backend/src/` 下执行 `go test ./...`
- 但当前会话环境里没有安装 `go` 命令,因此无法实际运行测试
- 所以这轮关于测试的判断,基于静态阅读,而不是实际执行结果
### 文档现状
- 仓库里的 `README.md` 基本只有标题和 badge,内容非常少
- 没有用户可读的 API 文档
- 没有 schema 文档
- 没有 Home Assistant / TickTick 的契约说明文档
- 没有关于配置项、OAuth 初始化、数据库文件位置的运维文档
### 后续最需要补齐的文档
- Home Assistant 命令 envelope 与支持的 action
- 各 API 的 request / response 契约
- TickTick OAuth 初始化与 token 持久化方式
- 数据库归属、用途与保留策略
- Notion 下线 / 不迁移说明
## 对 Python 重构特别重要的事实
- 当前 API 行为整体比较“轻响应、重副作用”,很多成功请求返回的都是空响应体
- 当前所有入站 API 都没有看到鉴权
- 当前系统本地真相来源是 SQLite,但 poo 数据还同时与 Notion 同步
- `notion.token` 现在不是可选项,缺失时服务会在 `initUtil()` 阶段直接退出
- Home Assistant 命令路由当前是通过本地 HTTP 自调用实现的,而不是直接服务层调用
- TickTick callback 会改写应用本身使用的 YAML 配置文件