Files
ENI-projet-piscine/apps/backend/alembic/versions/e6d2026091501_create_data_schema.py
T
Johan LEROY e3e0e843d0 fix(backend): rebranche la revision data sur la tete d'authentification
Le merge de dev apporte trois revisions d'authentification qui partent de la
meme racine 5353c0e4f094 que la revision data. Git ne signale rien, mais
alembic upgrade head refuse de choisir entre deux tetes.

La revision data se greffe desormais sur 821f71be74c0, ce qui rend la chaine
lineaire.
2026-09-15 16:46:37 +02:00

217 lines
9.5 KiB
Python

"""Création des six tables Data et de l'hypertable readings.
Revision ID: e6d2026091501
Revises: 821f71be74c0
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
revision = "e6d2026091501"
down_revision = "821f71be74c0"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"datasets",
sa.Column("dataset_id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("dataset_name", sa.Text(), nullable=False),
sa.Column("archive_sha256", sa.String(length=64), nullable=False),
sa.Column("storage_uri", sa.Text(), nullable=False),
sa.Column("source_timezone", sa.Text(), nullable=True),
sa.Column(
"metadata", postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), nullable=False
),
sa.CheckConstraint("dataset_id > 0", name="ck_datasets_positive_id"),
sa.PrimaryKeyConstraint("dataset_id"),
sa.UniqueConstraint("archive_sha256", name="uq_datasets_archive_sha256"),
)
op.create_table(
"sites",
sa.Column("site_id", sa.Text(), nullable=False),
sa.Column("site_name", sa.Text(), nullable=False),
sa.Column("site_type", sa.Text(), nullable=False),
sa.Column("location", sa.Text(), nullable=True),
sa.Column("capacity_kw", sa.Double(), nullable=True),
sa.Column("status", sa.Text(), nullable=True),
sa.PrimaryKeyConstraint("site_id"),
)
op.create_table(
"predictions",
sa.Column("prediction_id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("site_id", sa.Text(), nullable=False),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.Column("target_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("target_metric", sa.Text(), nullable=False),
sa.Column("period_minutes", sa.Integer(), nullable=True),
sa.Column("predicted_value", sa.Double(), nullable=True),
sa.Column("model_reference", sa.Text(), nullable=False),
sa.Column("status", sa.Text(), nullable=False),
sa.Column("failure_reason", sa.Text(), nullable=True),
sa.CheckConstraint(
"(status = 'available' AND predicted_value IS NOT NULL AND failure_reason IS NULL) OR (status IN ('insufficient_data', 'error') AND predicted_value IS NULL AND failure_reason IS NOT NULL)",
name="ck_predictions_status",
),
sa.CheckConstraint(
"target_metric <> 'consumption_kwh' OR period_minutes IS NOT NULL",
name="ck_predictions_energy_period",
),
sa.CheckConstraint(
"target_metric IN ('consumption_kwh', 'consumption_kw')", name="ck_predictions_metric"
),
sa.CheckConstraint(
"period_minutes IS NULL OR period_minutes > 0", name="ck_predictions_period"
),
sa.ForeignKeyConstraint(
["site_id"], ["sites.site_id"], name="fk_predictions_site", ondelete="RESTRICT"
),
sa.PrimaryKeyConstraint("prediction_id"),
sa.UniqueConstraint("prediction_id", "site_id", name="uq_predictions_id_site"),
)
op.create_index(
"ix_predictions_site_target", "predictions", ["site_id", "target_at"], unique=False
)
op.create_table(
"readings",
sa.Column("reading_id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("site_id", sa.Text(), nullable=False),
sa.Column("timestamp", sa.DateTime(timezone=True), nullable=False),
sa.Column("source", sa.Text(), nullable=False),
sa.Column("dataset_id", sa.BigInteger(), nullable=True),
sa.Column("consumption_kw", sa.Double(), nullable=True),
sa.Column("consumption_kwh", sa.Double(), nullable=True),
sa.Column("consumption_euros", sa.Numeric(precision=14, scale=2), nullable=True),
sa.Column("voltage_v", sa.Double(), nullable=True),
sa.Column("current_a", sa.Double(), nullable=True),
sa.Column("power_factor", sa.Double(), nullable=True),
sa.Column("temperature_celsius", sa.Double(), nullable=True),
sa.Column("humidity_percent", sa.Double(), nullable=True),
sa.Column("solar_irradiance_wm2", sa.Double(), nullable=True),
sa.Column("is_working_hours", sa.Boolean(), nullable=True),
sa.Column("data_quality", sa.Text(), nullable=True),
sa.Column("null_reasons", postgresql.ARRAY(sa.Text()), nullable=True),
sa.Column(
"imputed_values", postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), nullable=True
),
sa.Column("imputation_method", sa.Text(), nullable=True),
sa.Column(
"ingested_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.Column(
"raw_data", postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), nullable=False
),
sa.CheckConstraint(
"(source = 'csv' AND dataset_id IS NOT NULL) OR (source IN ('api_current', 'api_history') AND dataset_id IS NULL)",
name="ck_readings_dataset_source",
),
sa.CheckConstraint(
"data_quality IS NULL OR data_quality IN ('good', 'partial', 'degraded', 'critical')",
name="ck_readings_quality",
),
sa.CheckConstraint(
"source IN ('csv', 'api_current', 'api_history')", name="ck_readings_source"
),
sa.CheckConstraint(
"(imputed_values IS NULL AND imputation_method IS NULL) OR (imputed_values IS NOT NULL AND imputation_method IS NOT NULL)",
name="ck_readings_imputation",
),
sa.ForeignKeyConstraint(
["dataset_id"], ["datasets.dataset_id"], name="fk_readings_dataset", ondelete="RESTRICT"
),
sa.ForeignKeyConstraint(
["site_id"], ["sites.site_id"], name="fk_readings_site", ondelete="RESTRICT"
),
sa.PrimaryKeyConstraint("reading_id", "timestamp"),
)
op.create_index("ix_readings_dataset_id", "readings", ["dataset_id"], unique=False)
op.create_index(
"ix_readings_site_timestamp", "readings", ["site_id", "timestamp"], unique=False
)
op.create_index(
"uq_readings_source",
"readings",
["site_id", "timestamp", "source", sa.literal_column("coalesce(dataset_id, 0)")],
unique=True,
)
op.execute(
"SELECT create_hypertable('readings', by_range('timestamp'), create_default_indexes => FALSE)"
)
op.create_table(
"alerts",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("alert_id", sa.Text(), nullable=False),
sa.Column("site_id", sa.Text(), nullable=False),
sa.Column("source", sa.Text(), nullable=False),
sa.Column("timestamp", sa.DateTime(timezone=True), nullable=False),
sa.Column("type", sa.Text(), nullable=False),
sa.Column("severity", sa.Text(), nullable=False),
sa.Column("message", sa.Text(), nullable=False),
sa.Column("value", sa.Double(), nullable=True),
sa.Column("threshold", sa.Double(), nullable=True),
sa.Column("metric", sa.Text(), nullable=True),
sa.Column("prediction_id", sa.BigInteger(), nullable=True),
sa.Column(
"raw_data", postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), nullable=False
),
sa.CheckConstraint(
"severity IN ('low', 'medium', 'high', 'critical')", name="ck_alerts_severity"
),
sa.CheckConstraint("source IN ('api_mock', 'enervision')", name="ck_alerts_source"),
sa.CheckConstraint(
"type IN ('spike', 'threshold', 'anomaly', 'outage', 'sensor')", name="ck_alerts_type"
),
sa.ForeignKeyConstraint(
["prediction_id", "site_id"],
["predictions.prediction_id", "predictions.site_id"],
name="fk_alerts_prediction_site",
ondelete="RESTRICT",
),
sa.ForeignKeyConstraint(
["site_id"], ["sites.site_id"], name="fk_alerts_site", ondelete="RESTRICT"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("source", "site_id", "alert_id", name="uq_alerts_source_site_id"),
)
op.create_index("ix_alerts_site_timestamp", "alerts", ["site_id", "timestamp"], unique=False)
op.create_table(
"recommendations",
sa.Column("recommendation_id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("alert_id", sa.BigInteger(), nullable=False),
sa.Column("action", sa.Text(), nullable=False),
sa.Column("explanation", sa.Text(), nullable=False),
sa.Column("rule_reference", sa.Text(), nullable=False),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.ForeignKeyConstraint(
["alert_id"], ["alerts.id"], name="fk_recommendations_alert", ondelete="RESTRICT"
),
sa.PrimaryKeyConstraint("recommendation_id"),
sa.UniqueConstraint("alert_id", "rule_reference", name="uq_recommendations_alert_rule"),
)
# ### end Alembic commands ###
def downgrade() -> None:
op.drop_table("recommendations")
op.drop_table("alerts")
op.drop_table("readings")
op.drop_table("predictions")
op.drop_table("sites")
op.drop_table("datasets")