идентичность эпизода — интервал: блокер снят измерением
- находка 47: ключ (date,start,end) даёт 174 координаты сна против 170 по метке и ноль столкновений внутри доставки против 33; value в ключе лишний - UUID в выгрузку Apple не попадает, другой идентичности у эпизода нет - поэпизодный sleep_analysis вернулся в scope razbor-metrik-v-obekty
This commit is contained in:
@@ -1459,6 +1459,59 @@ type="HKWorkoutEventTypeMotionPaused|Resumed" ← совпадение по
|
||||
следующего проверенного экспорта, иначе между концом ретеншена и датой
|
||||
снапшота в журнале образуется дыра.
|
||||
|
||||
## 47. Идентичность эпизода сна — `start` и `end`, и другой у Apple нет
|
||||
|
||||
Координата `метрика + слой + метка` для поэпизодного `sleep_analysis` неверна:
|
||||
под одной меткой лежит до трёх разных эпизодов. Замер по всем 94 доставкам
|
||||
(1880 эпизодных точек, суточные сводки исключены):
|
||||
|
||||
| Ключ | Координат | Схлопнуто точек | Дублей **внутри одной** доставки |
|
||||
|---|---|---|---|
|
||||
| `date` | 170 | 1710 | 33 |
|
||||
| `date + start + end` | 174 | 1706 | **0** |
|
||||
| `date + start + end + value` | 174 | 1706 | 0 |
|
||||
|
||||
`start`/`end` закрывают всё; `value` в ключе не добавляет ни одной координаты.
|
||||
Сильнее: из 174 координат **ни одна не несёт двух разных содержимых** — ни с
|
||||
`source`, ни без него. Правило разрешения столкновений на эпизодном сне за весь
|
||||
корпус не срабатывает ни разу.
|
||||
|
||||
Внутридоставочные дубли важнее междоставочных: там `received_at` общий, и любой
|
||||
тай-брейк по времени приёма неприменим в принципе — исход решал бы порядок
|
||||
элементов в JSON-массиве, а он нестабилен (находка 2).
|
||||
|
||||
**Это не дефект данных, а задуманное представление.** Записи с одним `startDate`
|
||||
и разными `endDate` — вложенность «в кровати» и фазы сна внутри неё; HealthKit
|
||||
намеренно допускает перекрывающиеся сэмплы, чтобы выразить «в кровати» и
|
||||
«спит» одновременно.
|
||||
|
||||
**Другой идентичности Apple не даёт.** Атрибуты записи сна в экспорте:
|
||||
|
||||
```
|
||||
type sourceName sourceVersion creationDate startDate endDate value
|
||||
```
|
||||
|
||||
`HKObject.uuid` существует в API, но **в выгрузку не попадает**. Значит модель
|
||||
идентичности обязана выражаться через `start`/`end`: иначе `import(экспорт)` не
|
||||
сойдётся с `replay(HAE)` и журнал перестанет быть журналом.
|
||||
|
||||
### Как это решают другие
|
||||
|
||||
- **Health CSV Importer** дедуплицирует по `Start Date + End Date + Data Type +
|
||||
Type Identifier`. Совпадает с нашим замером, включая отсутствие источника в
|
||||
ключе.
|
||||
- **Ингесторы поверх InfluxDB** (`irvinlim/apple-health-ingester`,
|
||||
`joeecarter/health-import-server`) ключуют по `measurement + tags + timestamp`
|
||||
— то есть имеют ровно эту коллизию и разрешают её молчаливым last-write-wins
|
||||
движка. Один обходит её тем, что кладёт сон плоской точкой
|
||||
(`inBed`/`inBedStart`/`inBedEnd`), то есть поддерживает только суточную схему.
|
||||
- **Пайплайн HAE → FastAPI → Postgres** (ladvien) даёт каждой записи
|
||||
суррогатный UUID-PK без уникального ограничения: обещанная идемпотентность не
|
||||
реализована, повторная доставка задваивает строки.
|
||||
|
||||
То есть обе распространённые схемы хранения теряют данные ровно там, где мы это
|
||||
измерили, а единственная принятая схема дедупликации — это `start`+`end`.
|
||||
|
||||
## Инструмент
|
||||
|
||||
Разбор ведётся скриптом `tmp/research/hl.py` (Python 3, только стандартная
|
||||
|
||||
Reference in New Issue
Block a user