<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Creates the creator_classification table.
*
* This is the authoritative, machine-written record of a creator's
* classification as produced by the EXTERNAL classifier service. It is the
* system of record and carries the full provenance, confidence and
* reversibility of every classification:
* - provenance: model_provider / model_id / prompt_version /
* classification_version / input_kind / source_images / cost_usd /
* classified_at, so every value can be traced back to the exact run that
* wrote it;
* - confidence: the per-attribute confidence map plus trust_score /
* trust_band / trust_breakdown / signals, so downstream consumers can gate
* decisions on certainty;
* - reversibility: the prior_* columns snapshot the previous creator.* values
* before write-back, so a classification can be cleanly restored/rolled back.
*
* It is deliberately SEPARATE from the legacy creator.* enum columns
* (creator.ethnicity, creator.age_group, creator.category_gender,
* creator.category_parent, creator.attractiveness_level, creator.tier). The
* classifier service writes its results BACK into those creator.* columns so
* the existing admin UI keeps working unchanged; those columns are a
* denormalised projection for the UI, whereas this table is the source of
* truth. The prior_* columns mirror the creator.* column types (BIGINT
* UNSIGNED) precisely so a clean restore of the legacy columns is possible.
*
* creator_id is the primary key and equals creator.id, but NO foreign key
* constraint is declared on purpose: the relation is enforced at the
* application layer so the external service can upsert rows independently of
* (and without locking against) the creator table. Because there is no FK,
* deleting a creator does NOT cascade-delete its classification row; orphan
* rows are cleaned up out-of-band by the external classifier service (or a
* periodic reconciliation job), which is the deliberate trade-off for keeping
* external-service upserts independent of the creator table.
*/
final class Version20260626120000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Create creator_classification table (authoritative record written by the external classifier service)';
}
public function up(Schema $schema): void
{
$this->addSql(<<<'SQL'
CREATE TABLE creator_classification (
creator_id BIGINT UNSIGNED NOT NULL COMMENT 'Primary key; equals creator.id. No FK constraint by design so the external classifier service can upsert independently; the relation is enforced at the application layer.',
ethnicity TINYINT UNSIGNED DEFAULT NULL,
age_group TINYINT UNSIGNED DEFAULT NULL,
category_gender TINYINT UNSIGNED DEFAULT NULL,
category_parent TINYINT UNSIGNED DEFAULT NULL,
attractiveness_level TINYINT UNSIGNED DEFAULT NULL,
tier TINYINT UNSIGNED DEFAULT NULL,
trust_score DECIMAL(5, 2) DEFAULT NULL,
trust_band TINYINT UNSIGNED DEFAULT NULL,
confidence JSON DEFAULT NULL,
signals JSON DEFAULT NULL,
trust_breakdown JSON DEFAULT NULL,
input_kind VARCHAR(24) NOT NULL DEFAULT 'avatar_plus_posts',
source_images JSON DEFAULT NULL,
model_provider VARCHAR(32) DEFAULT NULL,
model_id VARCHAR(64) DEFAULT NULL,
prompt_version VARCHAR(16) DEFAULT NULL,
classification_version INT NOT NULL DEFAULT 0,
cost_usd DECIMAL(8, 5) DEFAULT NULL,
had_human_label TINYINT(1) NOT NULL DEFAULT 0,
prior_ethnicity BIGINT UNSIGNED DEFAULT NULL,
prior_age_group BIGINT UNSIGNED DEFAULT NULL,
prior_category_gender BIGINT UNSIGNED DEFAULT NULL,
prior_category_parent BIGINT UNSIGNED DEFAULT NULL,
prior_attractiveness_level BIGINT UNSIGNED DEFAULT NULL,
prior_tier BIGINT UNSIGNED DEFAULT NULL,
error TEXT DEFAULT NULL,
classified_at DATETIME DEFAULT NULL,
updated_at DATETIME DEFAULT NULL,
INDEX idx_cc_ethnicity (ethnicity),
INDEX idx_cc_age_group (age_group),
INDEX idx_cc_tier (tier),
INDEX idx_cc_version (classification_version),
INDEX idx_cc_classified_at (classified_at),
INDEX idx_cc_target (category_gender, ethnicity, tier),
PRIMARY KEY(creator_id)
) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
SQL);
}
public function down(Schema $schema): void
{
$this->addSql('DROP TABLE creator_classification');
}
}