"""scenes.max_quality_p: zdenormalizowana najwyzsza znana jakosc zrodel sceny Po co, skoro filtr dalo sie napisac zapytaniem: NIE dalo sie go napisac szybko. Zmierzone na produkcji 2026-08-18 (EXPLAIN ANALYZE, prog 1080p): EXISTS na playback_sources 32 s (3 556 612 odwiedzonych scen) + indeks funkcyjny per-scena 24 s JOIN z DISTINCT po jakosci 1,1 s W IZOLACJI, ale ten sam JOIN + reszta filtrow 52 s (planer wraca do przechodzenia scen) Ostatni wiersz jest sednem: w polaczeniu z bramka wertykalow, heurystyka szkieletow i progiem dlugosci planer i tak wybiera przejscie `scenes` po `created_at` i testowanie warunku scena po scenie. Kaztaltem zapytania sie tego nie ominie, bo koszt to LICZBA odwiedzonych scen, a nie pojedynczy test. Skalarna kolumna zdejmuje problem: staje sie zwyklym filtrem, takim jak `duration_sec`, ktory jest szybki. NULL = nie znamy jakosci zadnego zywego zrodla (93,5% katalogu: `quality` zapisujemy oportunistycznie, tylko gdy listing zrodla ja podaje). Filtr celowo takie sceny ODSIEWA, patrz komentarz przy QUALITY_OPTIONS w mobile/src/screens/ScenesFilterModal.tsx. Revision ID: 0029_scenes_max_quality Revises: 0028_phash_backfill_queue Create Date: 2026-08-18 """ from __future__ import annotations import sqlalchemy as sa from alembic import op revision: str = "0029_scenes_max_quality" down_revision: str | None = "0028_phash_backfill_queue" branch_labels = None depends_on = None def upgrade() -> None: op.add_column("scenes", sa.Column("max_quality_p", sa.SmallInteger(), nullable=True)) # (max_quality_p, created_at DESC): filtr to zakres po pierwszej kolumnie, a sort # feedu idzie po drugiej. Partial, bo 93,5% scen ma NULL i nie ma po co ich indeksowac. op.create_index( "ix_scenes_max_quality_created", "scenes", ["max_quality_p", sa.text("created_at DESC")], postgresql_where=sa.text("max_quality_p IS NOT NULL"), ) def downgrade() -> None: op.drop_index("ix_scenes_max_quality_created", table_name="scenes") op.drop_column("scenes", "max_quality_p")