Add Forecast.actual column

This commit is contained in:
Alexander Hess 2021-01-31 18:29:53 +01:00
parent 6429165aaf
commit a5b590b24c
Signed by: alexander
GPG key ID: 344EA5AB10D868E0
3 changed files with 58 additions and 0 deletions

View file

@ -0,0 +1,41 @@
"""Store actuals with forecast.
Revision: #c2af85bada01 at 2021-01-29 11:13:15
Revises: #e86290e7305e
"""
import os
import sqlalchemy as sa
from alembic import op
from urban_meal_delivery import configuration
revision = 'c2af85bada01'
down_revision = 'e86290e7305e'
branch_labels = None
depends_on = None
config = configuration.make_config('testing' if os.getenv('TESTING') else 'production')
def upgrade():
"""Upgrade to revision c2af85bada01."""
op.add_column(
'forecasts',
sa.Column('actual', sa.SmallInteger(), nullable=False),
schema=config.CLEAN_SCHEMA,
)
op.create_check_constraint(
op.f('ck_forecasts_on_actuals_must_be_non_negative'),
'forecasts',
'actual >= 0',
schema=config.CLEAN_SCHEMA,
)
def downgrade():
"""Downgrade to revision e86290e7305e."""
op.drop_column('forecasts', 'actual', schema=config.CLEAN_SCHEMA)

View file

@ -22,6 +22,10 @@ class Forecast(meta.Base):
time_step = sa.Column(sa.SmallInteger, nullable=False) time_step = sa.Column(sa.SmallInteger, nullable=False)
training_horizon = sa.Column(sa.SmallInteger, nullable=False) training_horizon = sa.Column(sa.SmallInteger, nullable=False)
model = sa.Column(sa.Unicode(length=20), nullable=False) model = sa.Column(sa.Unicode(length=20), nullable=False)
# We also store the actual order counts for convenient retrieval.
# A `UniqueConstraint` below ensures that redundant values that
# are to be expected are consistend across rows.
actual = sa.Column(sa.SmallInteger, nullable=False)
# Raw `.prediction`s are stored as `float`s (possibly negative). # Raw `.prediction`s are stored as `float`s (possibly negative).
# The rounding is then done on the fly if required. # The rounding is then done on the fly if required.
prediction = sa.Column(postgresql.DOUBLE_PRECISION, nullable=False) prediction = sa.Column(postgresql.DOUBLE_PRECISION, nullable=False)
@ -62,6 +66,7 @@ class Forecast(meta.Base):
sa.CheckConstraint( sa.CheckConstraint(
'training_horizon > 0', name='training_horizon_must_be_positive', 'training_horizon > 0', name='training_horizon_must_be_positive',
), ),
sa.CheckConstraint('actual >= 0', name='actuals_must_be_non_negative'),
sa.CheckConstraint( sa.CheckConstraint(
""" """
NOT ( NOT (

View file

@ -18,6 +18,7 @@ def forecast(pixel):
time_step=60, time_step=60,
training_horizon=8, training_horizon=8,
model='hets', model='hets',
actual=12,
prediction=12.3, prediction=12.3,
low80=1.23, low80=1.23,
high80=123.4, high80=123.4,
@ -131,6 +132,16 @@ class TestConstraints:
): ):
db_session.commit() db_session.commit()
def test_non_negative_actuals(self, db_session, forecast):
"""Insert an instance with invalid data."""
forecast.actual = -1
db_session.add(forecast)
with pytest.raises(
sa_exc.IntegrityError, match='actuals_must_be_non_negative',
):
db_session.commit()
def test_set_prediction_without_ci(self, db_session, forecast): def test_set_prediction_without_ci(self, db_session, forecast):
"""Sanity check to see that the check constraint ... """Sanity check to see that the check constraint ...
@ -388,6 +399,7 @@ class TestConstraints:
time_step=forecast.time_step, time_step=forecast.time_step,
training_horizon=forecast.training_horizon, training_horizon=forecast.training_horizon,
model=forecast.model, model=forecast.model,
actual=forecast.actual,
prediction=2, prediction=2,
low80=1, low80=1,
high80=3, high80=3,