feat: разбор и хранение тренировок и состояния разума
- секции `workouts` и `stateOfMind` покрыты разбором: тренировка лежит одной строкой вместе с маршрутом и внутренними рядами, запись — по ключу `род + id`; миграция 00007 заводит обе таблицы и возвращает в очередь `partial`-доставки с этими ключами - сущность заменяется целиком, но условно: приехавшая побеждает, если не теряет содержания сохранённой (множество ключей и длины верхнеуровневых массивов), а при равном содержании выигрывает версия из более поздней доставки ЖУРНАЛА — «побеждает приехавшая» было бы функцией порядка свёртки, и живая витрина расходилась бы с пересборкой молча - отпечаток витрины покрывает тренировки и записи и снимается одним снимком базы; отчёт `reindex` считает «было и стало» по каждой единице хранения
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
## 1. Фикстуры на реальных пакетах
|
||||
|
||||
- [x] 1.1 Расширить `tmp/research/fixtures.py`: вычистка UUID (`id`), меток
|
||||
RFC 3339, `route[].timestamp` и словарных значений `stateOfMind`
|
||||
(`kind`, `valenceClassification`, `labels`, `associations`) при
|
||||
сохранении формы литерала, структуры и порядка ключей
|
||||
- [x] 1.2 Собрать `internal/hae/testdata/workout_route.json` — уличная
|
||||
тренировка с маршрутом (проверяет дословность маршрута и внутренних рядов)
|
||||
- [x] 1.3 Собрать `internal/hae/testdata/workout_indoor.json` — тренировка без
|
||||
маршрута, с полями, которых нет у уличной (`temperature`, `humidity`,
|
||||
`intensity`)
|
||||
- [x] 1.4 Собрать `internal/hae/testdata/state_of_mind.json` — состояние разума
|
||||
(RFC 3339, отсутствие `source`)
|
||||
- [x] 1.5 Дописать в `handmade_edge.json` случаи, которых поток не даёт:
|
||||
сущность без `id`, с пустым и со слишком длинным `id`, с неразбираемой
|
||||
меткой, с неразбираемым `end`, с нечисловой длительностью, два элемента с
|
||||
одним `id` в одном теле
|
||||
- [x] 1.6 Обновить `internal/hae/testdata/README.md`: новые файлы и что именно
|
||||
вычищено
|
||||
|
||||
## 2. Разбор (`internal/hae`)
|
||||
|
||||
- [x] 2.1 Множество покрытых секций: `metrics`, `workouts`, `stateOfMind` —
|
||||
одно объявление на разбор и на перечисление непокрытых
|
||||
- [x] 2.2 Типы `Workout` и `Record` в `Result`; счётчики `SkippedNoID`,
|
||||
`SkippedNoTime` для сущностей
|
||||
- [x] 2.3 `parseEntityTime`: формат HAE, затем RFC 3339; офсет из разобранной
|
||||
зоны. Парсер точек остаётся строгим — причина записана в спеке
|
||||
- [x] 2.4 Разбор `workouts`: заголовок (`id`, `name`, `start`, `end`,
|
||||
`duration`), содержимое — исходные байты элемента; `end` нечитаем →
|
||||
равен началу; длительность отсутствует → не заполнена (не ноль)
|
||||
- [x] 2.5 Разбор `stateOfMind` в записи рода `stateOfMind` (имя секции дословно)
|
||||
- [x] 2.6 `Parse` отдаёт сущности и при отсутствии секции `metrics`; при
|
||||
ошибке (обрыв тела, невыводимый слой) не отдаёт ни точек, ни сущностей
|
||||
- [x] 2.7 Повтор ключа покрытой секции даёт объединение; повтор члена `data`
|
||||
**накапливает** результаты, а не присваивает последний (существующий
|
||||
дефект `decodeEnvelope`)
|
||||
- [x] 2.8 Предел длины `id`: сущность сверх него пропускается тем же счётчиком
|
||||
- [x] 2.9 Тесты на фикстурах: маршрут дословно, пульс тренировки не стал
|
||||
метрикой, оба формата времени, пропуски со счётчиками
|
||||
- [x] 2.10 Тест: доставка из одних тренировок и из одного `stateOfMind`
|
||||
непокрытых ключей не даёт; из одного `ecg` — даёт
|
||||
|
||||
## 3. Схема (`internal/store/migrations`)
|
||||
|
||||
- [x] 3.1 Миграция `00007_workout_record.sql`: таблицы `workout` и `record`
|
||||
(провенанс `delivery_id` + `delivery_received_at`, `content_hash`,
|
||||
nullable `duration_sec`), индексы по времени, перевод `partial`-доставок
|
||||
с ключами `workouts` и `stateOfMind` в `pending`
|
||||
- [x] 3.2 Проверить, что перевод в `pending` отбирает строки точно (по элементу
|
||||
JSON-массива, а не по подстроке тела)
|
||||
- [x] 3.3 Обновить `docs/database.md`: обе таблицы, смысл колонок, ключ
|
||||
`род + id`, провенанс, `NULL` у длительности, офсет `0` у `stateOfMind`
|
||||
|
||||
## 4. Хранение (`internal/store`, `internal/canon`)
|
||||
|
||||
- [x] 4.1 `canon`: сравнение множеств содержательных ключей **без** условия о
|
||||
совпадении значений (поверх существующего `relateKeys`) плюс сравнение
|
||||
длин верхнеуровневых массивов
|
||||
- [x] 4.2 `Incoming{Points, Workouts, Records}` и `Merge` вместо `MergePoints`:
|
||||
одна транзакция на доставку
|
||||
- [x] 4.3 Правило замены версии: хеш-детектор без чтения блоба, условие «не
|
||||
теряет содержания», тай-брейк по позиции журнала `(received_at, id)`,
|
||||
внутридоставочный тай-брейк по канонической форме, счётчик и координаты
|
||||
для `WARN`
|
||||
- [x] 4.4 Чтение и запись тренировки и записи; содержимое — сжатый блоб; хеш
|
||||
приехавшей считается один раз на доставку, до входа в транзакцию
|
||||
- [x] 4.5 `Fingerprint` покрывает тренировки и записи, читает одной read-only
|
||||
транзакцией, строки разделов различаются константным признаком
|
||||
- [x] 4.6 Тесты: замещение маршрутом; досчёт при том же наборе полей; обеднённая
|
||||
версия **с изменившимися значениями** не затирает; усечённый маршрут не
|
||||
затирает; несравнимые наборы; повтор не пишет; две версии в одном теле;
|
||||
перестановка **трёх** версий в двух порядках подачи (конвенция
|
||||
`docs/conventions.md`)
|
||||
- [x] 4.7 Тест: отпечаток расходится при расхождении одной тренировки
|
||||
|
||||
## 5. Свёртка и пересборка (`internal/fold`, `internal/replay`, `cmd`)
|
||||
|
||||
- [x] 5.1 `Stats` несёт счётчики сущностей; свёртка зовёт `Merge` один раз;
|
||||
счётчики доезжают до лога без ручного копирования (или это покрыто тестом)
|
||||
- [x] 5.2 Единственный логирующий чекпоинт: атрибуты сущностей, ветка `WARN`
|
||||
для удержанной обеднённой версии — координатами, без содержимого
|
||||
- [x] 5.3 Отчёт `reindex`: «до и после» по тренировкам и записям, «покрыта новая
|
||||
секция» в перечне ожидаемых классов расхождения
|
||||
- [x] 5.4 Тест: содержимое сущности в лог не попадает
|
||||
|
||||
## 6. Сходимость и проверка на живом архиве
|
||||
|
||||
- [x] 6.1 `task gate` — зелёный
|
||||
- [x] 6.2 `task verify:archive` — прогон всего `./data/raw`, повтор даёт то же
|
||||
состояние; он же оракул того, что живой приём и пересборка применяют к
|
||||
сущностям один порядок
|
||||
- [x] 6.3 Проверка на копии рабочей базы в отдельном каталоге данных: миграция
|
||||
накатывается, `partial`-доставки пересворачиваются, тренировки и записи
|
||||
появляются
|
||||
|
||||
## 7. Документация и беклог
|
||||
|
||||
- [x] 7.1 `docs/architecture.md` — раздел «Тренировки и прочие секции»: правило
|
||||
замены версии с обоснованием, отвергнутые варианты, предел `import`;
|
||||
**плюс блок схемы БД** (`record(kind, id)`, сжатый блоб, `content_hash`,
|
||||
провенанс)
|
||||
- [x] 7.2 `docs/conventions.md` — строка про ключ `record` устарела, поправить
|
||||
- [x] 7.3 `docs/local-research.md` — находка о поведении тренировки при
|
||||
переприсылке (числа замера) и пересчёт находки 50 на 118 доставок
|
||||
- [x] 7.4 Беклог: задача про идентичность тренировок при импорте родного
|
||||
экспорта (в `export.xml` `id` нет — `dogsheep` считает hash_id);
|
||||
`retenshen-syrogo-arhiva` — предусловие про `stateOfMind` снова открыто;
|
||||
отметить в `read-api-tochki`, что отдача тренировок и записей входит в
|
||||
неё; уточнить `proverka-novyh-sekcij` — модель заложена, остались пять
|
||||
секций
|
||||
- [x] 7.5 Удалить `docs/backlog/trenirovki-i-zapisi.md` и строку индекса
|
||||
|
||||
## 8. Приёмочные критерии (рубрика ревью)
|
||||
|
||||
- [x] 8.1 Тренировка с маршрутом переживает круг «разбор → хранение → чтение»
|
||||
побайтово
|
||||
- [x] 8.2 Повторная свёртка того же журнала не меняет отпечатка витрины
|
||||
- [x] 8.3 Ни один путь не пишет содержимое сущности в лог выше `DEBUG`
|
||||
- [x] 8.4 Доставка из одних тренировок получает `parsed`, а не `partial`
|
||||
- [x] 8.5 Доставка с непокрытой секцией по-прежнему `partial`, и её тело
|
||||
ретеншену трогать нельзя
|
||||
- [x] 8.6 Исход правила замены не зависит от порядка свёртки в пунктах 2–4
|
||||
правила; зависимость в пункте 5 наблюдаема счётчиком
|
||||
- [x] 8.7 У каждого класса пропуска при разборе сущности — свой счётчик, и
|
||||
пропуск одного элемента не уносит соседей
|
||||
Reference in New Issue
Block a user