second-brain/alembic/versions/7b3e8a5c1f29_v2_pipeline_settings.py
Travis Herbranson 09efbb5965 settings: v2 migration + ORM + settings_store helper
Introduces second_brain.pipeline_settings — the single-row config row
the upcoming web dashboard edits and the workers read at the start of
each run. Pinned to id=1 by a CHECK constraint so upserts-by-PK keep
the table singleton, and the migration seeds the row with the table's
column defaults via INSERT ... ON CONFLICT DO NOTHING.

Two consumer groups carved out:
- transcription_* fields persist now; future tower-side worker reads them.
- extraction_* fields will be read by the existing scheduler in the next
  commit, which is the actual behavior change Travis cares about today.

The settings_store helper centralises get/update + form-parsing
(time-of-day, int-or-none) and active-window math so the routes and the
scheduler don't reimplement them.
2026-05-25 08:11:53 -04:00

71 lines
2.7 KiB
Python

"""v2 second_brain: pipeline_settings table
Revision ID: 7b3e8a5c1f29
Revises: 4a1e2f6c9d10
Create Date: 2026-05-25 00:00:00.000000
Adds a single-row `pipeline_settings` table that the web dashboard edits
and the workers (current dev-side scheduler + future tower-side transcribe
worker) read at the start of each run. The transcription_* fields persist
now but are only consumed once the transcribe worker exists — the
extraction_* fields are wired into the existing scheduler in this commit.
Single-row enforcement: PK is fixed at `id=1` with a CHECK constraint, so
INSERT-as-upsert-by-PK keeps the table free of stale rows.
Schema/permission notes mirror the v1 migration: lovebug lacks CREATE on
the petalbrain database, so env.py has already bootstrapped the schema —
this migration just SETs search_path and emits DDL inside it.
"""
from __future__ import annotations
from collections.abc import Sequence
from alembic import op
revision: str = "7b3e8a5c1f29"
down_revision: str | Sequence[str] | None = "4a1e2f6c9d10"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.execute("SET search_path TO second_brain")
op.execute(
"""
CREATE TABLE pipeline_settings (
id INTEGER PRIMARY KEY DEFAULT 1,
transcription_enabled BOOLEAN NOT NULL DEFAULT TRUE,
transcription_active_hours_start TIME,
transcription_active_hours_end TIME,
transcription_max_concurrent_gpu_jobs INTEGER NOT NULL DEFAULT 1,
transcription_max_video_length_seconds INTEGER,
transcription_max_items_per_run INTEGER NOT NULL DEFAULT 10,
extraction_enabled BOOLEAN NOT NULL DEFAULT TRUE,
extraction_active_hours_start TIME,
extraction_active_hours_end TIME,
extraction_max_items_per_run INTEGER NOT NULL DEFAULT 20,
updated_at TIMESTAMP NOT NULL
DEFAULT (now() AT TIME ZONE 'UTC'),
CONSTRAINT pipeline_settings_singleton CHECK (id = 1)
)
"""
)
# Seed the singleton row with the table's column defaults. Idempotent
# via ON CONFLICT so re-running this migration on a partly-applied DB
# (or a manual cherry-pick) is safe.
op.execute(
"""
INSERT INTO pipeline_settings (id) VALUES (1)
ON CONFLICT (id) DO NOTHING
"""
)
def downgrade() -> None:
op.execute("SET search_path TO second_brain")
op.execute("DROP TABLE IF EXISTS pipeline_settings")