Каталог разрезов и измеренный род агрегации
- род метрики выводится сверкой минутного слоя с часовым: часовое значение сходится с суммой минутных — накопительная, со средним — мгновенная, иначе `unknown` и свёртка не предлагается вовсе. На живом архиве (123 доставки, 31 метрика) 7 накопительных, 9 мгновенных, противоречащих часов ноль - `GET /api/v1/metrics` под токеном чтения отдаёт единицы, слои с границами и род вместе с основанием измерения; род нигде не хранится — он функция витрины, а витрина функция журнала, устаревать в нём нечему - миграция 00009: покрывающий индекс, чтобы каталог отвечал по учётным колонкам, не разжимая содержимое объектов
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
package hae
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// PointValue достаёт число точки: `qty`, а при его отсутствии — `Avg`.
|
||||
//
|
||||
// Единственное место в проекте, знающее, какое поле точки HAE несёт число.
|
||||
// Порядок именно такой: `qty` несут все метрики, `Avg` — только `heart_rate`
|
||||
// (находка 40), и без второго кандидата самая частая метрика потока не
|
||||
// измерялась бы вовсе.
|
||||
//
|
||||
// Это чтение, а не интерпретация: значение никуда не пишется и ничего не
|
||||
// подменяет. Форма точки при этом рода метрики не выдаёт — род измеряется
|
||||
// сверкой слоёв, а не выводится отсюда.
|
||||
//
|
||||
// **Ноль — значение.** Словарь пустоты из `canon` сюда не применяется и
|
||||
// применяться не должен: там ноль объявлен пустотой, чтобы точка без измерений
|
||||
// не вытесняла настоящее измерение при столкновении координат, — вопрос совсем
|
||||
// другой. Взяв его, измерение не увидело бы точки `{"qty":0}`, час выпал бы из
|
||||
// счётчиков ещё до правила различимости, и проверка «нулевой час свидетельством
|
||||
// не является» зеленела бы по неверной причине.
|
||||
//
|
||||
// **Значением считается только JSON-число.** Строка `"72.5"` — не число, хотя
|
||||
// `json.Number` её принимает (проверено): такой формы поток не приносил, и
|
||||
// прочитать её как измерение значило бы угадать за источник. Отсутствие ключа и
|
||||
// `null` — тоже «нет значения»; при этом `qty: null` не мешает прочитать `Avg`,
|
||||
// а `qty` не того типа мешает: форма точки поменялась, и догадываться не о чем.
|
||||
func PointValue(raw json.RawMessage) (float64, bool) {
|
||||
var p struct {
|
||||
Qty *json.RawMessage `json:"qty"`
|
||||
Avg *json.RawMessage `json:"Avg"`
|
||||
}
|
||||
// Указатели, а не значения: `null` обязан отличаться от нуля, и в этой форме
|
||||
// он приходит нулевым указателем.
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
if p.Qty != nil {
|
||||
return jsonNumber(*p.Qty)
|
||||
}
|
||||
if p.Avg != nil {
|
||||
return jsonNumber(*p.Avg)
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// jsonNumber разбирает значение поля, требуя, чтобы это было JSON-число.
|
||||
//
|
||||
// Проверка первого байта нужна потому, что `json.Unmarshal` в `float64`
|
||||
// отвергает строку, но `json.Number` — принимает; полагаться на тип-приёмник
|
||||
// значило бы получить разное поведение от невидимой детали.
|
||||
//
|
||||
// Бесконечность значением не считается: `1e400` разбирается с ошибкой
|
||||
// диапазона, и проглоти её — `+Inf` отравил бы и сумму, и среднее всего часа, а
|
||||
// видно это было бы лишь тем, что род перестал определяться. Отдельной проверки
|
||||
// на `Inf`/`NaN` после разбора нет намеренно: их литералов в JSON не бывает, а
|
||||
// число вне диапазона `float64` даёт ошибку, а не бесконечность.
|
||||
func jsonNumber(raw json.RawMessage) (float64, bool) {
|
||||
b := bytes.TrimSpace(raw)
|
||||
if len(b) == 0 || (b[0] != '-' && (b[0] < '0' || b[0] > '9')) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
var v float64
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return v, true
|
||||
}
|
||||
Reference in New Issue
Block a user