"""Add ``sub_profile`` to cmdb_bedrock_profile_facts (per-key × UsedBy cross). The per-API-Key attribution axis (``profile_tag_key = iamPrincipal/helix-request-number``, migration 435) stored one row per (date × key × service) and gave a shared-Owner sibling (#1776) its own headline number — but its ``UsedBy`` workload breakdown was still the person-level table (``profile_tag_key='UsedBy'``, owner-filtered), so two keys held by the same person showed the SAME UsedBy rows (xuhuaiyu reported it: B5F7 and B9C1 rendered an identical workload table). Cost Explorer's ``UsedBy`` resource tag and the ``helix-request-number`` IAM-principal tag CAN be crossed at the CUR line-item level (verified in prod 2026-08-04: ``Filter=account+key, GroupBy=(UsedBy × SERVICE)`` returns each key's own workload split, and the per-key totals sum exactly to the owner total). To carry that cross the per-key axis rows gain a second dimension: ``sub_profile`` = the ``UsedBy`` value, so a per-key row is now (date × key × UsedBy × service). ``sub_profile`` is NULLable and joins the ``fact_key`` natural key (see ``_profile_fact_key``). Existing rows (both the UsedBy workload axis and the pre-cross per-key axis) carry ``sub_profile = NULL``; the ingest's per-(scope, date) window DELETE reconverges them on the next sync, and every read path is ``SUM ... GROUP BY`` so a finer grain never perturbs an existing total. Revision ID: 452 Revises: 451 """ import sqlalchemy as sa from alembic import op revision = "452" down_revision = "451" branch_labels = None depends_on = None _TABLE = "cmdb_bedrock_profile_facts" _COLUMN = "sub_profile" def upgrade() -> None: op.add_column( _TABLE, sa.Column(_COLUMN, sa.String(length=256), nullable=True), ) def downgrade() -> None: op.drop_column(_TABLE, _COLUMN)