906804140c
Renames image_record.wd14_* -> tagger_* (we're on Camie now, not WD14). Adds tag_allowlist (auto-apply opt-in, per-tag confidence), tag_suggestion_rejection (per-image dismissals), tag_alias (composite (string, category) -> canonical tag, resolved at read time), tag_reference_embedding (per-tag SigLIP centroids), and the ml_settings singleton (per-category + centroid thresholds, model version pins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
173 lines
5.9 KiB
Python
173 lines
5.9 KiB
Python
"""fc2b: ML pipeline — allowlist, aliases, centroids, ml_settings
|
|
|
|
Revision ID: 0003
|
|
Revises: 0002
|
|
Create Date: 2026-05-15
|
|
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
from pgvector.sqlalchemy import Vector
|
|
|
|
revision: str = "0003"
|
|
down_revision: Union[str, None] = "0002"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# 3.1 rename wd14_* -> tagger_*
|
|
op.alter_column("image_record", "wd14_predictions", new_column_name="tagger_predictions")
|
|
op.alter_column(
|
|
"image_record", "wd14_model_version", new_column_name="tagger_model_version"
|
|
)
|
|
|
|
# 3.2 tag_allowlist
|
|
op.create_table(
|
|
"tag_allowlist",
|
|
sa.Column("tag_id", sa.Integer(), nullable=False),
|
|
sa.Column(
|
|
"min_confidence", sa.Float(), nullable=False, server_default="0.95"
|
|
),
|
|
sa.Column(
|
|
"added_at", sa.DateTime(timezone=True), nullable=False,
|
|
server_default=sa.func.now(),
|
|
),
|
|
sa.ForeignKeyConstraint(
|
|
["tag_id"], ["tag.id"], name="fk_tag_allowlist_tag_id_tag",
|
|
ondelete="CASCADE",
|
|
),
|
|
sa.PrimaryKeyConstraint("tag_id", name="pk_tag_allowlist"),
|
|
sa.CheckConstraint(
|
|
"min_confidence > 0 AND min_confidence <= 1",
|
|
name="ck_tag_allowlist_confidence_range",
|
|
),
|
|
)
|
|
|
|
# 3.3 tag_suggestion_rejection
|
|
op.create_table(
|
|
"tag_suggestion_rejection",
|
|
sa.Column("image_record_id", sa.Integer(), nullable=False),
|
|
sa.Column("tag_id", sa.Integer(), nullable=False),
|
|
sa.Column(
|
|
"rejected_at", sa.DateTime(timezone=True), nullable=False,
|
|
server_default=sa.func.now(),
|
|
),
|
|
sa.ForeignKeyConstraint(
|
|
["image_record_id"], ["image_record.id"],
|
|
name="fk_tsr_image_record_id_image_record", ondelete="CASCADE",
|
|
),
|
|
sa.ForeignKeyConstraint(
|
|
["tag_id"], ["tag.id"], name="fk_tsr_tag_id_tag", ondelete="CASCADE",
|
|
),
|
|
sa.PrimaryKeyConstraint(
|
|
"image_record_id", "tag_id", name="pk_tag_suggestion_rejection"
|
|
),
|
|
)
|
|
op.create_index(
|
|
"ix_tag_suggestion_rejection_tag", "tag_suggestion_rejection", ["tag_id"]
|
|
)
|
|
|
|
# 3.4 tag_alias
|
|
op.create_table(
|
|
"tag_alias",
|
|
sa.Column("alias_string", sa.String(length=255), nullable=False),
|
|
sa.Column("alias_category", sa.String(length=32), nullable=False),
|
|
sa.Column("canonical_tag_id", sa.Integer(), nullable=False),
|
|
sa.Column(
|
|
"created_at", sa.DateTime(timezone=True), nullable=False,
|
|
server_default=sa.func.now(),
|
|
),
|
|
sa.ForeignKeyConstraint(
|
|
["canonical_tag_id"], ["tag.id"],
|
|
name="fk_tag_alias_canonical_tag_id_tag", ondelete="CASCADE",
|
|
),
|
|
sa.PrimaryKeyConstraint(
|
|
"alias_string", "alias_category", name="pk_tag_alias"
|
|
),
|
|
)
|
|
op.create_index("ix_tag_alias_canonical", "tag_alias", ["canonical_tag_id"])
|
|
|
|
# 3.5 tag_reference_embedding (centroids)
|
|
op.create_table(
|
|
"tag_reference_embedding",
|
|
sa.Column("tag_id", sa.Integer(), nullable=False),
|
|
sa.Column("embedding", Vector(1152), nullable=False),
|
|
sa.Column("reference_count", sa.Integer(), nullable=False),
|
|
sa.Column("model_version", sa.String(length=128), nullable=False),
|
|
sa.Column(
|
|
"updated_at", sa.DateTime(timezone=True), nullable=False,
|
|
server_default=sa.func.now(),
|
|
),
|
|
sa.ForeignKeyConstraint(
|
|
["tag_id"], ["tag.id"],
|
|
name="fk_tag_reference_embedding_tag_id_tag", ondelete="CASCADE",
|
|
),
|
|
sa.PrimaryKeyConstraint("tag_id", name="pk_tag_reference_embedding"),
|
|
)
|
|
|
|
# 3.6 ml_settings singleton
|
|
op.create_table(
|
|
"ml_settings",
|
|
sa.Column("id", sa.Integer(), nullable=False),
|
|
sa.Column(
|
|
"suggestion_threshold_artist", sa.Float(), nullable=False,
|
|
server_default="0.30",
|
|
),
|
|
sa.Column(
|
|
"suggestion_threshold_character", sa.Float(), nullable=False,
|
|
server_default="0.50",
|
|
),
|
|
sa.Column(
|
|
"suggestion_threshold_copyright", sa.Float(), nullable=False,
|
|
server_default="0.50",
|
|
),
|
|
sa.Column(
|
|
"suggestion_threshold_general", sa.Float(), nullable=False,
|
|
server_default="0.95",
|
|
),
|
|
sa.Column(
|
|
"centroid_similarity_threshold", sa.Float(), nullable=False,
|
|
server_default="0.55",
|
|
),
|
|
sa.Column(
|
|
"min_reference_images", sa.Integer(), nullable=False,
|
|
server_default="5",
|
|
),
|
|
sa.Column(
|
|
"tagger_model_version", sa.String(length=128), nullable=False,
|
|
server_default="camie-tagger-v2",
|
|
),
|
|
sa.Column(
|
|
"embedder_model_version", sa.String(length=128), nullable=False,
|
|
server_default="siglip-so400m-patch14-384",
|
|
),
|
|
sa.Column(
|
|
"updated_at", sa.DateTime(timezone=True), nullable=False,
|
|
server_default=sa.func.now(),
|
|
),
|
|
sa.PrimaryKeyConstraint("id", name="pk_ml_settings"),
|
|
sa.CheckConstraint("id = 1", name="ck_ml_settings_singleton"),
|
|
)
|
|
op.execute("INSERT INTO ml_settings (id) VALUES (1)")
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_table("ml_settings")
|
|
op.drop_table("tag_reference_embedding")
|
|
op.drop_index("ix_tag_alias_canonical", table_name="tag_alias")
|
|
op.drop_table("tag_alias")
|
|
op.drop_index(
|
|
"ix_tag_suggestion_rejection_tag", table_name="tag_suggestion_rejection"
|
|
)
|
|
op.drop_table("tag_suggestion_rejection")
|
|
op.drop_table("tag_allowlist")
|
|
op.alter_column(
|
|
"image_record", "tagger_model_version", new_column_name="wd14_model_version"
|
|
)
|
|
op.alter_column(
|
|
"image_record", "tagger_predictions", new_column_name="wd14_predictions"
|
|
)
|