plugin_metrics_hourly gains value_p95 beside value_avg and value_max. A two-minute backup spike raises the hour's peak and leaves its p95 low; an hour spent at that level raises both. Learned normals (milestone 513) need that difference to call a recurring spike usual without calling the same level held at another hour usual too. Hours rolled up before this keep a NULL p95 and read as their average. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
27 lines
932 B
Python
27 lines
932 B
Python
"""Each hour of the metric rollup keeps the level it held, its 95th percentile,
|
|
beside its average and peak (#5738), so a learned normal can tell a brief
|
|
spike (a high peak, a low p95) from a level the metric stayed at.
|
|
|
|
Revision ID: 0044_hourly_p95
|
|
Revises: 0043_outbox_alert_words
|
|
Create Date: 2026-10-14
|
|
"""
|
|
from typing import Sequence, Union
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision: str = "0044_hourly_p95"
|
|
down_revision: Union[str, None] = "0043_outbox_alert_words"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# Hours rolled up before this have no p95 to recover (their raw samples
|
|
# are gone), so they stay NULL and read as their average.
|
|
op.add_column("plugin_metrics_hourly", sa.Column("value_p95", sa.Float, nullable=True))
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_column("plugin_metrics_hourly", "value_p95")
|