"""hybrid retrieval: generated FTS column on chunks + query_log.fts_hits Revision ID: 0002 Revises: 0001 Create Date: 2026-08-21 A7/A8 (revised 2026-08-21, owner permission): retrieval becomes hybrid (cosine top-N + Postgres full-text top-N, RRF-fused). This adds: * ``chunks.tsv`` — generated ``TSVECTOR`` (``to_tsvector('english', content) STORED``) + GIN index for the lexical candidate list. * ``query_log.fts_hits`` — INT, nullable (pre-existing rows stay NULL: the column only carries meaning from hybrid retrieval onward). """ from __future__ import annotations import sqlalchemy as sa from alembic import op revision = "0002" down_revision = "0001" branch_labels = None depends_on = None def upgrade() -> None: op.execute( "ALTER TABLE chunks " "ADD COLUMN tsv tsvector " "GENERATED ALWAYS AS (to_tsvector('english', content)) STORED" ) op.execute("CREATE INDEX ix_chunks_tsv ON chunks USING gin (tsv)") op.add_column("query_log", sa.Column("fts_hits", sa.Integer(), nullable=True)) def downgrade() -> None: op.drop_column("query_log", "fts_hits") op.execute("DROP INDEX IF EXISTS ix_chunks_tsv") op.execute("ALTER TABLE chunks DROP COLUMN IF EXISTS tsv")