# ADR：配送事件的时间与空间基线

- 状态：proposed
- 发布候选：`1.4-proposal`
- fixture：`ch16-spatiotemporal-v1`
- 本地验证：PostgreSQL 18.4、PostGIS 3.6.4、`btree_gist` 1.8
- Pigsty 参考：4.4；本地未执行 L1

## 背景

配送系统既要回答“事件什么时候发生”，也要回答“当时位于哪个区域”。到达
数据库的时间不等于业务发生时间；地理围栏还会随时间换版。若只留一个时间戳
和两个浮点数，迟到、乱序、夏令时、边界归属、距离单位和历史重算都会产生
无法审计的歧义。

## 决策

1. 业务事实以 `occurred_at timestamptz` 作为事件时间和分区键，
   `received_at timestamptz` 单独记录接收时间。写入尝试先落到
   `ingest_attempt`，由 `event_id` 注册表选出唯一规范事件。
2. 分区边界按 UTC 日历日定义，统一使用半开区间 `[from, to)`。基线采用
   PostgreSQL 原生 `RANGE` 分区；是否引入 TimescaleDB 留到有压缩、连续聚合、
   自动保留或运维收益的量化证据后再决定。
3. 地理围栏以 `tstzrange` 记录有效时间。同一 `zone_id` 的版本禁止重叠，
   由 `btree_gist` 排他约束在写入时执行，而不是靠查询约定。
4. 规范坐标是 EPSG:4326。`geometry` 用于拓扑谓词和空间索引；
   从它生成的 `geography` 用于以米为单位的距离判断。`ST_SetSRID` 只赋予
   坐标标签，真正换投影必须使用 `ST_Transform`。
5. 围栏业务口径采用 `ST_Covers`，所以落在边界上的点算作命中。若业务要求
   唯一区域归属，必须再增加优先级或消歧规则；本基线允许相邻区域共享边界，
   因而 `e003` 同时命中 `central` 与 `east`。
6. 查询先写可裁剪的事件时间范围，再写空间谓词。计划证据分别保存：
   直接时间谓词、包裹分区键的反例、GiST、SP-GiST 和时空联合计划。

## 被否决或推迟的方案

- 只保存服务器接收时间：无法恢复事件发生顺序，也会把网络延迟误当成业务
  时间。
- `timestamp without time zone` 保存绝对时刻：调用方时区约定无法由类型系统
  强制，夏令时回放容易产生歧义。
- 经度、纬度分别建普通 B-tree：不能表达空间相交、包含或邻近的二维搜索。
- 所有距离都在 EPSG:4326 `geometry` 上计算：结果单位是角度，不是米。
- 一开始就引入时序扩展：在没有容量与生命周期证据时扩大了包、preload、
  升级和备份边界。

## 后果

- 正面：历史事实、迟到、重复、围栏换版和边界口径都可重放；时间裁剪与空间
  索引能够分别验证；扩展生命周期与数据 schema 分开。
- 代价：每个分区要维护本地索引；围栏版本需要治理；`geometry` 与
  `geography` 会增加写入、WAL 和存储成本；PostGIS 必须进入备份恢复与大版本
  升级验证。
- 仍未解决：地图匹配、路网最短路、轨迹压缩、全球跨日期变更线、多 SRID
  数据接入，以及生产规模下的选择率和容量。

## 复核与重开条件

出现以下任一条件时重开本 ADR：分区数量或保留操作成为主要运维成本；连续
聚合/压缩的收益可量化；写入吞吐或 WAL 超预算；全球范围的 geography 查询
无法满足延迟；业务要求边界唯一归属；权威外部地理数据引入新的许可与版本
责任。

## 证据

执行 `task.sh all` 后，以 `baseline-v1.4-proposal.json`、冻结 CSV、两个周期的
目录快照、计划、预期失败、完整校验和与 reset/rebuild 记录为准。小样本计划
只证明机制和路径存在，不构成性能基准。
