Pricing — operator runbook
Операционное руководство для P2 PriceRule aggregate + auto-derivation (см. ADR-0036). Описывает feature flags, env vars, метрики и readiness-пробы. Инцидентные runbooks (kafka lag, outbox stall и т.п.) — в
40-operations/runbooks/.
Feature flags
Engine + admin HTTP + readiness probe — always on. P1 stub retired после достижения production parity. Единственный feature flag — auto-derivation pipeline.
| Flag | Default | Что включает |
|---|---|---|
PRICING_DERIVATION_ENABLED | false | Kafka consumer (offers.events.v1), weekly cron, manual /derivation-runs/trigger |
Порядок включения в проде:
- Прогнать миграции 0018–0021 (price_rules / drafts / derivation_runs / qty_observed).
- Запустить api-server. PollingLoader.Bootstrap считает rules;
/readyzкрасный до окончания первой загрузки. - Засеять operator-curated
PriceRuleчерез admin HTTP (JWTrole: pricing_admin). Snapshot догонит черезPRICING_SNAPSHOT_POLL_INTERVAL(default 10s). - Через несколько часов поднять
PRICING_DERIVATION_ENABLED=true. Сначала появятся drafts; CF3 ≥ 0.9 → auto-applied derived rules.
Откат derivation: flip PRICING_DERIVATION_ENABLED=false + restart. Engine продолжает работать на existing rules; новые drafts не появляются.
Env vars
Полный набор — в deploy/.env.example секция Pricing engine. Применимые к
api-server:
| Var | Default | Tuning guidance |
|---|---|---|
PRICING_SNAPSHOT_POLL_INTERVAL | 10s | Incremental fetch cadence. Поднять до 30s если PG нагружен write-heavy. |
PRICING_SNAPSHOT_FULL_RELOAD_INTERVAL | 5m | Полный rebuild snapshot. Самостраховка от drift; не трогать без причины. |
PRICING_CF3_AUTO_APPLY_THRESHOLD | 0.9 | Порог авто-применения derived rules. Понижать → больше automation, выше риск. |
PRICING_CF3_HALF_LIFE_DAYS | 14 | Скорость затухания старых наблюдений. Меньше → сильнее recency-bias. |
PRICING_CF3_CV_MULTIPLIER | 2.0 | Penalty за вариативность. Поднять, если drafts шумят. |
PRICING_CF3_SAMPLE_SATURATION | 10 | Bonus saturation. Меньше = быстрее насыщение confidence. |
PRICING_CF3_MIN_SAMPLES | 3 | Минимум обсёрвейшенов для derivation. Опускать ниже 3 нельзя (см. spec §4.3.2). |
Readiness
Pricing регистрирует один health.Checker — pricing.snapshot:
- Не готов до завершения
PollingLoader.Bootstrap./readyzвозвращает 503 сcomponent=pricing.snapshot. - Готов после первой загрузки snapshot. Дальнейшие incremental/full reload failures не flip
pricing.snapshotобратно — это шум который должен ловиться через метрику, не через probe.
Метрики (counters)
Все counters пишутся в slog (level INFO/WARN). Pull-through OTel/Prometheus
exporter планируется отдельной задачей; до тех пор alerting — через
event=pricing.metrics.* в log aggregator.
| Metric | Labels | Когда увеличивается |
|---|---|---|
pricing_derivation_runs_total | status ∈ {ok, skipped, failed} | Каждое завершение Deriver.Execute. |
pricing_snapshot_reload_total | mode × result | После tick / fullReload в PollingLoader. |
pricing_outbox_emit_total | event_kind × result | После каждой Create/Update/Deprecate/UpsertDerived в CrudSvc. |
pricing_overlap_conflicts_total | — | Когда OverlapValidator отклоняет mutation. |
Alerts (пример):
rate(pricing_derivation_runs_total{status="failed"}[5m]) > 0— derivation failures, P2.rate(pricing_snapshot_reload_total{result="failed"}[5m]) > 0— DB unreachable, P1.rate(pricing_overlap_conflicts_total[1h]) > N(тюнинг по нагрузке) — operators ставят пересекающиеся правила, P3.
Что делать при инциденте
| Симптом | Первый шаг |
|---|---|
/readyz красный после deploy, причина pricing.snapshot | Проверить PG доступность (postgres checker). Если PG жив но Bootstrap зависает — глянуть pricing.snapshot.bootstrap log + длинные locks/queries в pg_stat_activity. |
Шквал pricing.metrics.derivation_run со status=failed | Глянуть последние commit’ы pricing/app/derivation; временно PRICING_DERIVATION_ENABLED=false чтобы остановить шум. |
| Operator жалуется что rule “не применился” | Проверить snapshot lag (pricing.snapshot.reload events). Snapshot — eventual; до 10s+5m задержки — норма. |
| Drafts ноль за неделю | CF3 threshold слишком высокий (или нет наблюдений ≥ 3 supplier credentials). Глянуть PRICING_CF3_MIN_SAMPLES + samples. |
Связано
- ADR-0036 — pricing rule aggregate + auto-derivation
- Pricing service boundary
- OpenAPI: pricing-admin.yaml
CustomPricingHandler registry (P3 closure)
P3 plugin extension point. См. ADR-0039.
Built-in Go handlers compile-time registered. WASM/DSL runtime — Phase 4+.
Env vars
| Var | Default | Tuning |
|---|---|---|
PRICING_HANDLER_POLL_INTERVAL | 10s | Incremental fetch cadence |
PRICING_HANDLER_FULL_RELOAD_INTERVAL | 5m | Full rebuild safety net |
Built-in handlers
| HandlerID | Что делает |
|---|---|
volume_discount_v1 | Tier-based percentage discount: qty≥10→-5%, ≥50→-8%, ≥100→-12% |
contract_markup_v1 | Fixed +5% markup (для customers с offline contract) |
GET /api/v1/admin/pricing/handlers — список из api-server runtime.
Trigger predicate (v1)
JSON equality predicate. Allowed keys: supplier, customer_type, customer_id, canonical_id, observation.has_volume_discount, observation.on_request. Multiple keys = AND. Array value = IN-clause.
Example:
{"supplier": "etm", "observation.has_volume_discount": false}Метрики (counters)
| Metric | Labels | Source |
|---|---|---|
pricing_handler_invocation_total | handler_ref × result ∈ {ok, timeout, error, panic, unknown_handler} | Registry.InvokeMatching |
pricing_handler_snapshot_reload_total | mode × result | handler PollingLoader |
Triage
| Симптом | Шаг |
|---|---|
Шквал pricing.metrics.handler_invocation{result=timeout} | Handler превышает 50ms. Проверить handler latency profile. Может быть infinite loop в built-in. |
result=unknown_handler | Operator зарегистрировал handler_ref которого нет в инвентаре (handler retired или typo). |
| Operator ожидает custom logic, но не applied | Glance trigger predicate vs HandlerInvocationContext. Use GET /handlers для список валидных IDs. |
result=panic | Built-in handler bug. Найти по pricing.handler.invocation_panic slog event. |
Развёртывание новых built-in handlers
P3 — handlers compile-time. Требует rebuild api-server binary с новым handler в core/pricing/handlers/builtin/. Steps:
- Реализовать
Handlerinterface (Descriptor()+Apply()) - Регистрация через fx group
pricing.handlersвpricing/fx.go - Deploy api-server
- Operator активирует handler через
POST /handler-registrationsс handler_ref новой ID
Связанное
- ADR-0039 — CustomPricingHandler Plugin Registry
- Spec:
docs/superpowers/specs/2026-04-25-p3-custom-pricing-handler-design.md - OpenAPI:
pricing-admin.yaml§ handler-registrations endpoints