Files
home-automation/docs/location-recorder.md

5.5 KiB

Location Recorder

本文档说明 location recorder 在 Python 项目中的当前数据库接管策略,以及 legacy SQLite 接管 runbook。

当前 Python 版本的 POST /location/record 请求校验策略是:

  • latitudelongitude 为必填,缺失或无法解析成合法数值时返回 400 bad request
  • altitude 为可选,缺失或非法时按 0 处理
  • unknown field 仍返回 400 bad request
  • 对 caller 的错误响应保持简洁,不直接暴露底层校验细节;详细原因只写日志

Legacy 事实基线

当前 legacy SQLite 中 location 表的真实 schema 为:

CREATE TABLE location (
  person TEXT NOT NULL,
  datetime TEXT NOT NULL,
  latitude REAL NOT NULL,
  longitude REAL NOT NULL,
  altitude REAL,
  PRIMARY KEY (person, datetime)
);

历史上 legacy Go 实现使用:

PRAGMA user_version = 2;

这代表旧系统曾依赖 user_version 管理 location 数据库版本,但这不再是 Python 项目的长期 migration 机制。

当前策略

当前采用的最小必要接管方案是:

  1. 把上述 location schema 视为 Alembic baseline
  2. 新数据库通过 Alembic upgrade head 初始化
  3. 已有 legacy SQLite 数据库,只要确认 schema 与 baseline 一致,再通过 alembic stamp 接管
  4. 如果数据库已经存在 alembic_version,则必须先确认当前 revision 与项目预期 baseline 一致
  5. 只有 revision 一致时,才视为该库已经被正确接管
  6. 未来不再以 PRAGMA user_version 作为主 migration 机制

当前 baseline revision 是:

  • 20260419_01_location_baseline

当前提供的最小脚本入口是:

python scripts/location_db_adopt.py

如果你更喜欢模块方式运行,也可以用:

python -m scripts.location_db_adopt

它只针对 LOCATION_DATABASE_URL 工作,并且遵守保守接管原则:

  • 本地已有 DB 文件:先校验,再接管
  • 本地没有 DB 文件:按新库初始化
  • 任一校验不通过:立即报错并停止

应用本身在启动时不会自动替你初始化 location 数据库。 应用启动时会对 LOCATION_DATABASE_URL 做只读校验:

  • 文件不存在:直接报错,并提示先运行接管脚本
  • 文件存在但还没有 alembic_version:直接报错,要求先完成 legacy 接管
  • 文件已被 Alembic 管理但 revision 不匹配:直接报错并拒绝启动

这是有意为之,用来避免应用在错误路径上静默创建新库,或带着错误数据库版本继续跑业务。

新数据库初始化

如果本地不存在 LOCATION_DATABASE_URL 指向的 DB 文件:

  • 脚本会先创建父目录
  • 然后执行 Alembic upgrade head
  • 最终建立 location 表与 alembic_version

手工执行时也等价于:

alembic upgrade head

这会创建与 legacy 相同的 location 表结构,并在库中建立 Alembic revision 记录。

旧数据库接管

对于已经存在的 legacy SQLite 数据库:

  1. 先确认 DB 文件存在
  2. 如果已经存在 alembic_version 表,则先读取当前 revision
  3. 如果 revision 等于 20260419_01_location_baseline,则视为该库已经被 Alembic 正确接管
  4. 如果 revision 不匹配,立即报错并停止,不做任何自动修复
  5. 如果还没有 alembic_version 表,则读取当前 DB 中 location 表的实际 schema
  6. 与 baseline schema 做严格比对
  7. 再检查 PRAGMA user_version
  8. 只有 schema 匹配且 user_version = 2 时,才执行 Alembic stamp
  9. 接管完成后,后续 migration 才交给 Alembic 管理

示例:

LOCATION_DATABASE_URL=sqlite:///./data/locationRecorder.db alembic stamp 20260419_01_location_baseline

或直接执行脚本:

LOCATION_DATABASE_URL=sqlite:///./data/locationRecorder.db python scripts/location_db_adopt.py

这样做的含义是:

  • 告诉 Alembic:这个数据库已经处于 baseline 结构
  • 不修改已有 location 表数据
  • 后续 migration 由 Alembic 接管

Fail Closed 原则

当前策略是保守接管,不做未知 legacy 状态的自动修复。

如果出现以下任一情况,脚本会直接报错并停止:

  • 找不到 location
  • location 表 schema 与 baseline 不一致
  • PRAGMA user_version 不等于 2
  • 已有 alembic_version,但 revision 与预期 baseline 不一致
  • 目标 DB 不是 SQLite URL

当前不会尝试:

  • 自动修表
  • 自动调整 user_version
  • 自动推断未知 legacy 状态

如果发生这些情况,应先人工确认数据库状态,再决定是否需要单独迁移或修复。

关于 data/locationRecorder.db

你本地放在 data/locationRecorder.db 的 legacy 样本库,可以用于:

  • 人工核对 schema
  • 手动验证 stamp 接管流程
  • 做开发时的兼容性确认

但当前代码不应硬依赖这个文件存在。

测试样本的安全使用方式

如果要用 legacy SQLite 样本做测试或验证,应遵守:

  1. 不直接在原始样本文件上跑测试
  2. 先复制到临时路径
  3. 所有 stamp、写入、实验性 migration 都只针对副本执行

自动化测试里当前采用的方式是:

  • 构造一个“legacy 风格”的临时 SQLite 文件
  • 建出同样的 location
  • 设置 PRAGMA user_version = 2
  • 再执行接管脚本中的 adopt 逻辑

同时也覆盖:

  • DB 文件不存在时的新库初始化路径
  • schema 不匹配时的失败路径
  • user_version 不匹配时的失败路径

这样可以验证接管路径,同时不污染真实样本库。