разбор метрик HAE: фикстуры, канонизация, парсер

- tmp/research/fixtures.py собирает фикстуры из архива, вычищая измерения и
  сохраняя порядок ключей, форму литералов, выравнивание меток и невидимые
  символы; шесть фикстур в internal/hae/testdata
- internal/canon — общий дом канонической формы, полноты и хеша: числа читаются
  литералом через json.Number, округление до 12 значащих цифр
- internal/hae — разбор секции metrics, вывод слоя по метрике, разделение схем
  сна, координаты интервалом; recover внутри Parse, фаззинг
- миграция bucket и docs/database.md
This commit is contained in:
av
2026-08-01 17:31:41 +03:00
parent 3e93dd95b4
commit 01d0de59df
18 changed files with 3693 additions and 21 deletions
+78
View File
@@ -0,0 +1,78 @@
package hae
// assignLayers проставляет слой каждой группе точек.
//
// Правило проверено на всей истории (находка 33) и дважды ломалось на живых
// данных при более простых формулировках: классификация доставки целиком
// сложила минутные точки с посекундными и удвоила сумму за час, а
// классификация каждой метрики по отдельности растащила редкие метрики по трём
// слоям.
//
// Работающая формулировка в три шага:
//
// 1. плотная метрика (не меньше denseThreshold точек) классифицируется по
// собственному выравниванию;
// 2. редкая наследует самый мелкий слой среди плотных метрик этой доставки —
// её собственное выравнивание ничего не значит, одна метка на часе бывает
// и у минутного ряда;
// 3. доставка без плотных метрик вовсе наследует последний надёжно выведенный
// слой той же автоматизации, а если и его нет — берёт слой из НАДЁЖНОГО
// заголовка.
//
// Третий шаг не роскошь: измерено 2 такие доставки из 89, обе с заголовком
// `Default`. Заменить его заголовком нельзя — `Default` не означает режима.
func assignLayers(groups []group, meta Meta, res *Result) error {
delivery := deliveryLayer(groups)
if delivery == "" {
// Плотных метрик нет: наследуем от прошлых доставок этой автоматизации,
// иначе — от надёжного заголовка.
delivery = meta.FallbackLayer
if delivery == "" {
delivery = res.HeaderLayer
}
if delivery == "" {
return ErrLayerUnknown
}
}
res.Layer = delivery
// Расхождение сверяется только с надёжным заголовком: `Default` не
// означает режима, и сравнение с ним давало бы WARN на каждой доставке
// потока в пять минут — сигнал утонул бы в собственном шуме.
res.LayerMismatch = res.HeaderLayer != "" && delivery != res.HeaderLayer
for i := range groups {
g := &groups[i]
switch {
case g.fixed:
// Слой назначен схемой, выводить нечего.
case g.dense:
g.layer = g.alignment
default:
g.layer = delivery
}
}
return nil
}
// deliveryLayer — самый мелкий слой среди плотных метрик доставки. Пустой,
// если плотных метрик нет.
//
// Группы с назначенным слоем в голосовании не участвуют: суточных сводок сна
// бывает больше порога плотности, и их полуночные метки иначе назначили бы
// всей доставке слой hour.
func deliveryLayer(groups []group) Layer {
var out Layer
for _, g := range groups {
if g.fixed || !g.dense {
continue
}
if out == "" {
out = g.alignment
continue
}
out = finer(out, g.alignment)
}
return out
}