идентичность эпизода — интервал: блокер снят измерением
- находка 47: ключ (date,start,end) даёт 174 координаты сна против 170 по метке и ноль столкновений внутри доставки против 33; value в ключе лишний - UUID в выгрузку Apple не попадает, другой идентичности у эпизода нет - поэпизодный sleep_analysis вернулся в scope razbor-metrik-v-obekty
This commit is contained in:
@@ -30,10 +30,6 @@
|
||||
- Словарь категориальных значений: строка пока хранится дословно и без кода.
|
||||
- Род агрегации и каталог разрезов.
|
||||
- Своя агрегация при записи: слои не сводятся друг к другу никогда.
|
||||
- **Хранение эпизодных схем** (поэпизодный `sleep_analysis`): модель их
|
||||
идентичности вынесена блокером `identichnost-epizodnyh-metrik`. Точки
|
||||
разбираются и считаются, но не сохраняются; тела в архиве, подберёт
|
||||
пересборка.
|
||||
|
||||
## Decisions
|
||||
|
||||
@@ -125,6 +121,41 @@ double, и хеш-детектор начнёт видеть изменения
|
||||
|
||||
Сжатие наблюдалось около 25 раз — ~2 МБ в сутки вместо ~50 МБ.
|
||||
|
||||
### Эпизод адресуется интервалом, а не меткой
|
||||
|
||||
Координата `метрика + слой + метка` верна для точки-измерения и неверна для
|
||||
точки-интервала: под одной меткой лежит до трёх разных эпизодов сна. Поэтому
|
||||
у точки, несущей `start` и `end`, координата — `метрика + слой + start + end`.
|
||||
|
||||
Замер по всем 94 доставкам (1880 эпизодных точек, находка 47): метка одна даёт
|
||||
170 координат и 33 столкновения **внутри одной доставки**, интервал — 174
|
||||
координаты и ноль столкновений; `value` в ключе не добавляет ни одной
|
||||
координаты. Из 174 координат ни одна не несёт двух разных содержимых, то есть
|
||||
на эпизодном сне правило разрешения столкновений не срабатывает ни разу.
|
||||
|
||||
**Эпизодность выводится из данных, а не курируется списком** — так же, как слой
|
||||
выводится из выравнивания меток, а не из заголовка. Признак: точка несёт `start`
|
||||
и `end`, отличные от `date`. Список имён метрик здесь был бы вторым способом
|
||||
описывать то, что уже сказано формой точки, и разошёлся бы с ней на первой же
|
||||
новой метрике HAE.
|
||||
|
||||
Почему не «принять потерю и писать `WARN`»: в дублях внутри одной доставки
|
||||
`received_at` общий, и тай-брейк по времени приёма неприменим в принципе —
|
||||
исход решал бы порядок элементов в JSON-массиве, а он нестабилен (находка 2).
|
||||
Свёртка перестала бы быть детерминированной: пересборка из архива давала бы не
|
||||
то состояние, что живой приём.
|
||||
|
||||
Почему не append-only по хешу содержимого: это вторая модель идентичности в
|
||||
`store` ради случая, которого можно избежать, и эпизоды не схлопывались бы
|
||||
никогда — даже когда повтор действительно повтор, а их здесь 1706 из 1880.
|
||||
|
||||
Проверено против чужих решений (находка 47): единственная принятая схема
|
||||
дедупликации Apple Health — `Start + End + тип`, без источника в ключе;
|
||||
популярные ингесторы поверх InfluxDB ключуют по метке и теряют эпизоды
|
||||
молчаливым last-write-wins движка. `HKObject.uuid` дал бы идентичность даром,
|
||||
но **в выгрузку Apple он не попадает** — значит модель обязана выражаться через
|
||||
`start`/`end`, иначе `import(экспорт)` не сойдётся с `replay(HAE)`.
|
||||
|
||||
### Слияние — по полноте, при равенстве — по `received_at`
|
||||
|
||||
Координатный ключ означает перезапись значения. Кто побеждает — решает
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
- **Идентичность точки — координаты** (`метрика + слой + метка`). `source` в
|
||||
ключ не входит: он нестабилен и меняется задним числом. При столкновении
|
||||
выигрывает **более полная** точка, а не последняя пришедшая.
|
||||
- **Точка-интервал адресуется интервалом**: `метрика + слой + начало + конец`.
|
||||
Под одной меткой лежит до трёх эпизодов сна, и 33 столкновения из измеренных
|
||||
происходят внутри одной доставки, где тай-брейк по времени приёма неприменим.
|
||||
Эпизодность выводится из формы точки, а не из списка имён метрик.
|
||||
- **Канонизация с округлением** чисел до ~12 значащих цифр; хеш канонической
|
||||
формы — детектор изменений, а не ключ.
|
||||
- `sleep_analysis` разводится на два имени: поэпизодное и суточную сводку —
|
||||
@@ -43,13 +47,7 @@
|
||||
Сходимость на них проверяется скриптом поверх архива, а не командой сервиса;
|
||||
- **словарь категориальных значений** (переведённые строки → коды HealthKit) —
|
||||
отдельная задача; пока строка хранится дословно и без кода рядом;
|
||||
- **род агрегации и каталог разрезов** — отдельная задача, она следующая;
|
||||
- **хранение поэпизодного `sleep_analysis`** — вынуто ревью дизайна блокером
|
||||
`identichnost-epizodnyh-metrik`: координатный ключ его схлопывает (измерено:
|
||||
31 координата в 21 доставке из 89 задвоена внутри одной доставки, где
|
||||
тай-брейк по времени приёма неприменим в принципе). Точки разбираются и
|
||||
считаются, но не сохраняются; тела в архиве, подберёт пересборка.
|
||||
Суточной сводки это не касается.
|
||||
- **род агрегации и каталог разрезов** — отдельная задача, она следующая.
|
||||
|
||||
## Capabilities
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
### Requirement: Идентичность точки по координатам
|
||||
|
||||
Система SHALL адресовать точку координатами `метрика + слой + метка времени`.
|
||||
Поле `source` в ключ входить MUST NOT: оно нестабильно — то же измерение с тем
|
||||
же значением приезжает то как `Apple Watch Ultra 3|iPad (Anton)`, то как
|
||||
`Apple Watch Ultra 3`, потому что Health переосмысливает атрибуцию задним
|
||||
числом.
|
||||
Система SHALL адресовать точку-измерение координатами
|
||||
`метрика + слой + метка времени`. Поле `source` в ключ входить MUST NOT: оно
|
||||
нестабильно — то же измерение с тем же значением приезжает то как
|
||||
`Apple Watch Ultra 3|iPad (Anton)`, то как `Apple Watch Ultra 3`, потому что
|
||||
Health переосмысливает атрибуцию задним числом.
|
||||
|
||||
Идентичность по хешу содержимого проверялась и отвергнута: она задваивала
|
||||
минутный слой целиком — 120 точек в часе вместо 60.
|
||||
@@ -21,27 +21,38 @@
|
||||
- **WHEN** точка с теми же координатами приезжает с другой строкой `source`
|
||||
- **THEN** она остаётся одной точкой, а не превращается в две
|
||||
|
||||
### Requirement: Эпизодные схемы не сохраняются до решения об идентичности
|
||||
### Requirement: Идентичность эпизода по интервалу
|
||||
|
||||
Система MUST NOT сохранять точки схем, у которых метка времени не уникальна, —
|
||||
пока модель их идентичности не определена. Такие точки разбираются и
|
||||
учитываются счётчиком, но в объекты не пишутся.
|
||||
Система SHALL адресовать точку-интервал координатами
|
||||
`метрика + слой + начало + конец`. Метки времени для неё недостаточно: под
|
||||
одной меткой лежит до трёх разных эпизодов сна.
|
||||
|
||||
Известная такая схема одна: поэпизодный `sleep_analysis`. Измерено: 31
|
||||
координата в 21 доставке из 89 задвоена **внутри одной доставки**, где
|
||||
`received_at` общий и тай-брейк по нему неприменим в принципе. Сохранять их по
|
||||
правилу, о котором известно, что оно теряет, хуже, чем не сохранять: тела
|
||||
остаются в архиве, и после решения их подберёт пересборка.
|
||||
Эпизодность SHALL выводиться из формы точки — точка несёт `start` и `end`,
|
||||
отличные от `date`, — а не назначаться списком имён метрик. Список был бы
|
||||
вторым способом описывать то, что уже сказано формой точки, и разошёлся бы с
|
||||
ней на первой же новой метрике HAE.
|
||||
|
||||
Развилка вынесена блокером `identichnost-epizodnyh-metrik`. Суточной сводки
|
||||
(`sleep_analysis_summary`) ограничение не касается — у неё метка на полуночи
|
||||
уникальна.
|
||||
Измерено на всех 94 доставках (1880 эпизодных точек, находка 47): ключ по метке
|
||||
даёт 170 координат и 33 столкновения **внутри одной доставки**, ключ по
|
||||
интервалу — 174 координаты и ноль столкновений. Внутридоставочные столкновения
|
||||
и делают ключ по метке неисправимым: `received_at` там общий, тай-брейк по нему
|
||||
неприменим в принципе, и исход решал бы порядок элементов в JSON-массиве — а он
|
||||
нестабилен.
|
||||
|
||||
#### Scenario: Поэпизодная запись сна не попадает в объект
|
||||
Час объекта для точки-интервала SHALL определяться по началу эпизода: эпизод
|
||||
пересекает границы часов, и любой другой выбор сделал бы принадлежность
|
||||
объекту зависящей от длительности.
|
||||
|
||||
- **WHEN** разобрана точка метрики `sleep_analysis` поэпизодной схемы
|
||||
- **THEN** она не сохраняется в часовой объект
|
||||
- **AND** факт учитывается счётчиком в итоге разбора доставки
|
||||
#### Scenario: Эпизоды с одной меткой и разными интервалами не схлопываются
|
||||
|
||||
- **WHEN** в доставке приходят точки `sleep_analysis` с одинаковым `date` и
|
||||
разными парами `start`/`end`
|
||||
- **THEN** каждая сохраняется отдельной точкой
|
||||
|
||||
#### Scenario: Повтор эпизода в следующей доставке не задваивает
|
||||
|
||||
- **WHEN** точка с тем же `start` и `end` приезжает следующей доставкой
|
||||
- **THEN** она остаётся одной точкой
|
||||
|
||||
### Requirement: Разрешение столкновений по полноте
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
- [ ] 1.1 Скрипт `tmp/research/fixtures.py`: собирает фикстуры из `data/raw`, вычищая измеренные значения и сохраняя порядок ключей, точность чисел, неразрывные пробелы и форматы времени
|
||||
- [ ] 1.2 Набор `internal/hae/testdata`: минутная доставка, посекундная, часовая, смешанная (перенастройка автоматизации), обе схемы `sleep_analysis`, точка с `heartbeatSeries`, доставка без плотных метрик с заголовком `Default`
|
||||
- [ ] 1.3a Фикстура с эпизодами сна: три точки с одной меткой `date` и разными парами `start`/`end`, включая эпизод, пересекающий границу часа (в архиве такие есть — вычистить значения, интервалы сохранить)
|
||||
- [ ] 1.3 Рукотворная фикстура с **выдуманным полем точки** — скрипт из архива её не породит, а без неё требование «незнакомое поле сохраняется» останется без теста
|
||||
- [ ] 1.4 Миграция `internal/store/migrations/00003_bucket.sql`: таблица `bucket` (`metric`, `layer`, `hour_utc`, `units`, `payload` BLOB, `content_hash`, `points`, `first_ts`, `last_ts`, `first_delivery_id`, `sealed`, `created_at`, `updated_at`), уникальность по `(metric, layer, hour_utc)`
|
||||
- [ ] 1.5 `docs/database.md` — ER-схема с `delivery` и `bucket` (шаг гейта `er-schema` требует её при изменении миграций)
|
||||
@@ -32,7 +33,8 @@
|
||||
- [ ] 4.1 Модель объекта в `internal/store`; содержимое — исходные байты точек, gzip-BLOB, точки упорядочены по времени
|
||||
- [ ] 4.2 Слияние: координатный ключ, победа более полной точки, при равной полноте — детерминированный исход по порядку канонических форм
|
||||
- [ ] 4.3 Столкновение с различием канонической формы — `WARN` без значений и счётчик перезаписей
|
||||
- [ ] 4.4 Поэпизодный `sleep_analysis` **не сохраняется** (блокер `identichnost-epizodnyh-metrik`): точки считаются, в объекты не пишутся
|
||||
- [ ] 4.4 Ключ точки-интервала — `метрика + слой + start + end`; эпизодность выводится из формы точки (`start`/`end`, отличные от `date`), а не из списка имён. Час объекта — по началу эпизода
|
||||
- [ ] 4.4a Тест на фикстуре 1.3a: три эпизода с одной меткой и разными интервалами дают три точки, а не одну; повтор той же тройки следующей доставкой не задваивает
|
||||
- [ ] 4.5 Хеш объекта как детектор изменений: совпал — записи нет
|
||||
- [ ] 4.6 `_txlock=immediate` в DSN; повтор оборачивает **всю тройку** чтение-слияние-запись; путь «хеш совпал» — под `TxOptions{ReadOnly: true}`
|
||||
- [ ] 4.7 Распознавание занятости — `errors.As` на `*sqlite.Error`, коды 5 и 517, обёрнуто в `store`
|
||||
@@ -71,3 +73,4 @@
|
||||
- [ ] 7.10 Время нормализовано без потери зоны
|
||||
- [ ] 7.11 Слой выводится из данных, а не из заголовка; неопределимый слой имеет явную судьбу
|
||||
- [ ] 7.12 Парсер детерминирован и чист: ни `time.Now`, ни генерации id; два вызова на одном входе равны
|
||||
- [ ] 7.13 (добавлено после снятия блокера) Точка-интервал не схлопывается по метке: прогон архива даёт 174 координаты сна, а не 170
|
||||
|
||||
Reference in New Issue
Block a user