feat: product enrichment foundation (GTIN, KB evidence, providers, quota gateway, resolver, migration 0029)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
"""Per-installation functions (corrector / enrichment mode), KB field status, enrichment state + audit, provider state/usage/cache, official source refresh log.
|
||||
|
||||
Revision ID: 0029_product_enrichment
|
||||
Revises: 0028_rtc_write_modes
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "0029_product_enrichment"
|
||||
down_revision = "0028_rtc_write_modes"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def _idx(table, *cols):
|
||||
for col in cols:
|
||||
op.create_index(f"ix_{table}_{col}", table, [col])
|
||||
|
||||
|
||||
def upgrade():
|
||||
# existing installations keep behaving exactly as before: corrector on, functions already configured
|
||||
op.add_column("installations", sa.Column("corrector_enabled", sa.Boolean(), nullable=False, server_default=sa.text("true")))
|
||||
op.add_column("installations", sa.Column("enrichment_mode", sa.String(16), nullable=False, server_default="DISABLED"))
|
||||
op.add_column("installations", sa.Column("features_configured", sa.Boolean(), nullable=False, server_default=sa.text("true")))
|
||||
op.add_column("agent_enrollments", sa.Column("features_selected", sa.Boolean(), nullable=False, server_default=sa.text("false")))
|
||||
op.add_column("agent_enrollments", sa.Column("feature_corrector", sa.Boolean(), nullable=False, server_default=sa.text("false")))
|
||||
op.add_column("agent_enrollments", sa.Column("feature_enrichment_mode", sa.String(16), nullable=False, server_default="DISABLED"))
|
||||
op.add_column("kb_knowledge", sa.Column("status", sa.String(16), nullable=False, server_default="EVIDENCE_ONLY"))
|
||||
op.create_index("ix_kb_knowledge_status", "kb_knowledge", ["status"])
|
||||
|
||||
op.create_table(
|
||||
"product_enrichments",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("tenant_id", sa.Integer(), sa.ForeignKey("tenants.id"), nullable=False),
|
||||
sa.Column("installation_id", sa.Integer(), sa.ForeignKey("installations.id"), nullable=False),
|
||||
sa.Column("product_code", sa.String(64), nullable=False),
|
||||
sa.Column("gtin", sa.String(16), nullable=True),
|
||||
sa.Column("description", sa.String(255), nullable=True),
|
||||
sa.Column("status", sa.String(32), nullable=False, server_default="PENDING"),
|
||||
sa.Column("decisions", sa.JSON(), nullable=False),
|
||||
sa.Column("fields_applied", sa.JSON(), nullable=False),
|
||||
sa.Column("human_overridden", sa.JSON(), nullable=False),
|
||||
sa.Column("attempts", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("next_attempt_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("latency_ms", sa.Integer(), nullable=True),
|
||||
sa.Column("detected_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.UniqueConstraint("installation_id", "product_code", name="uq_product_enrichment"),
|
||||
)
|
||||
_idx("product_enrichments", "tenant_id", "installation_id", "gtin", "status", "next_attempt_at")
|
||||
|
||||
op.create_table(
|
||||
"product_field_audits",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("tenant_id", sa.Integer(), sa.ForeignKey("tenants.id"), nullable=False),
|
||||
sa.Column("installation_id", sa.Integer(), sa.ForeignKey("installations.id"), nullable=False),
|
||||
sa.Column("agent_id", sa.Integer(), sa.ForeignKey("agents.id"), nullable=False),
|
||||
sa.Column("product_code", sa.String(64), nullable=False),
|
||||
sa.Column("gtin", sa.String(16), nullable=True),
|
||||
sa.Column("description_snapshot", sa.String(255), nullable=True),
|
||||
sa.Column("field", sa.String(48), nullable=False),
|
||||
sa.Column("old_value", sa.String(64), nullable=True),
|
||||
sa.Column("new_value", sa.String(64), nullable=True),
|
||||
sa.Column("rule", sa.String(64), nullable=True),
|
||||
sa.Column("kb_status", sa.String(16), nullable=True),
|
||||
sa.Column("evidence", sa.JSON(), nullable=False),
|
||||
sa.Column("dataset_versions", sa.JSON(), nullable=False),
|
||||
sa.Column("authorization_origin", sa.String(48), nullable=True),
|
||||
sa.Column("job_id", sa.Integer(), nullable=True),
|
||||
sa.Column("outcome", sa.String(24), nullable=False, server_default="APPLIED"),
|
||||
sa.Column("reason", sa.String(96), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
_idx("product_field_audits", "tenant_id", "installation_id", "agent_id", "product_code")
|
||||
|
||||
op.create_table(
|
||||
"provider_states",
|
||||
sa.Column("provider", sa.String(32), primary_key=True),
|
||||
sa.Column("configured", sa.Boolean(), nullable=False, server_default=sa.text("false")),
|
||||
sa.Column("status", sa.String(16), nullable=False, server_default="NOT_CONFIGURED"),
|
||||
sa.Column("quota_kind", sa.String(16), nullable=False, server_default="DOCUMENTED_RATE"),
|
||||
sa.Column("daily_limit", sa.Integer(), nullable=True),
|
||||
sa.Column("remaining", sa.Integer(), nullable=True),
|
||||
sa.Column("reset_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("rate_per_minute", sa.Integer(), nullable=True),
|
||||
sa.Column("circuit_open_until", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("consecutive_failures", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("last_request_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("last_success_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("last_error", sa.String(160), nullable=True),
|
||||
sa.Column("last_error_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("last_latency_ms", sa.Integer(), nullable=True),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
op.create_table(
|
||||
"provider_usage_daily",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("provider", sa.String(32), nullable=False),
|
||||
sa.Column("day", sa.String(10), nullable=False),
|
||||
*[sa.Column(name, sa.Integer(), nullable=False, server_default="0") for name in ("requests", "success", "not_found", "errors", "rate_limited", "timeouts", "cache_hits", "cache_misses")],
|
||||
sa.UniqueConstraint("provider", "day", name="uq_provider_usage_day"),
|
||||
)
|
||||
_idx("provider_usage_daily", "provider", "day")
|
||||
op.create_table(
|
||||
"gtin_lookup_cache",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("gtin", sa.String(16), nullable=False),
|
||||
sa.Column("provider", sa.String(32), nullable=False),
|
||||
sa.Column("found", sa.Boolean(), nullable=False),
|
||||
sa.Column("data", sa.JSON(), nullable=False),
|
||||
sa.Column("fetched_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.UniqueConstraint("gtin", "provider", name="uq_gtin_lookup_cache"),
|
||||
)
|
||||
_idx("gtin_lookup_cache", "gtin", "expires_at")
|
||||
op.create_table(
|
||||
"source_refresh_runs",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("source", sa.String(32), nullable=False),
|
||||
sa.Column("source_url", sa.Text(), nullable=False),
|
||||
sa.Column("started_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("status", sa.String(16), nullable=False, server_default="RUNNING"),
|
||||
sa.Column("sha256", sa.String(64), nullable=True),
|
||||
sa.Column("version_label", sa.String(64), nullable=True),
|
||||
sa.Column("record_count", sa.Integer(), nullable=True),
|
||||
sa.Column("error", sa.String(300), nullable=True),
|
||||
)
|
||||
_idx("source_refresh_runs", "source")
|
||||
|
||||
|
||||
def downgrade():
|
||||
for table in ("source_refresh_runs", "gtin_lookup_cache", "provider_usage_daily", "provider_states", "product_field_audits", "product_enrichments"):
|
||||
op.drop_table(table)
|
||||
op.drop_index("ix_kb_knowledge_status", table_name="kb_knowledge")
|
||||
op.drop_column("kb_knowledge", "status")
|
||||
for name in ("feature_enrichment_mode", "feature_corrector", "features_selected"):
|
||||
op.drop_column("agent_enrollments", name)
|
||||
for name in ("features_configured", "enrichment_mode", "corrector_enabled"):
|
||||
op.drop_column("installations", name)
|
||||
Reference in New Issue
Block a user