"""add note_usage_events — did anyone actually open what we surfaced?

Revision ID: 0071
Revises: 0070
Create Date: 2026-07-28

`retrieval_logs` records what the ranker returned and with what scores, which is
the right substrate for tuning a similarity threshold. It cannot answer the
different question the snippet corpus needs: was a surfaced snippet ever pulled
in full? A snippet nobody opens still competes for the injection budget on every
turn, so the surfaced:pulled ratio is what makes dead weight visible.

Two reasons this is its own table rather than columns on `notes` or rows in
`retrieval_logs`:

  - Counters on `notes` would answer "how many" but not "when, from where, and
    by which arm" — and the place arm vs semantic arm comparison is precisely
    what was missing (the write-path place arm surfaced snippets while leaving
    no trace anywhere).
  - Folding un-scored surfacing into `retrieval_logs` would corrupt the score
    distribution that table exists to capture. Location hits have no score.

Grain is one row per note per event, which is what the per-snippet readout needs
and what `retrieval_logs.result_ids` (a JSONB array, one row per *call*) cannot
be indexed at.

FK-free on note_id and user_id, matching retrieval_logs and app_logs: telemetry
should outlive what it describes. Deleting a note must not erase the evidence
that it was surfaced forty times and opened none.

Downgrade drops the table outright. The data is purely observational — nothing
reads it for correctness, so losing it costs history and no behavior.
"""
from alembic import op
import sqlalchemy as sa


revision = "0071"
down_revision = "0070"
branch_labels = None
depends_on = None


def upgrade() -> None:
    op.create_table(
        "note_usage_events",
        sa.Column("id", sa.Integer(), primary_key=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            nullable=False,
            server_default=sa.text("now()"),
        ),
        sa.Column("user_id", sa.Integer(), nullable=True),
        sa.Column("note_id", sa.Integer(), nullable=False),
        sa.Column("event", sa.Text(), nullable=False),
        sa.Column("source", sa.Text(), nullable=False),
    )
    # Every readout is "these note ids, split by event", so the composite is the
    # one that actually gets used; the others serve pruning and per-user views.
    op.create_index(
        "ix_note_usage_note_event", "note_usage_events", ["note_id", "event"]
    )
    op.create_index("ix_note_usage_created_at", "note_usage_events", ["created_at"])
    op.create_index("ix_note_usage_user_id", "note_usage_events", ["user_id"])


def downgrade() -> None:
    op.drop_index("ix_note_usage_user_id", table_name="note_usage_events")
    op.drop_index("ix_note_usage_created_at", table_name="note_usage_events")
    op.drop_index("ix_note_usage_note_event", table_name="note_usage_events")
    op.drop_table("note_usage_events")
