From 22c09a3e7fc9987ce84b242ef1d645b6b6da9605 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Sun, 28 Mar 2021 16:32:21 +0200 Subject: [PATCH 01/10] Fix docstring --- flexmeasures/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexmeasures/conftest.py b/flexmeasures/conftest.py index aee4f38c3..82b1b5046 100644 --- a/flexmeasures/conftest.py +++ b/flexmeasures/conftest.py @@ -177,7 +177,7 @@ def setup_assets(db, setup_roles_users, setup_markets): @pytest.fixture(scope="function", autouse=True) def add_market_prices(db: SQLAlchemy, setup_assets, setup_markets): - """Add one day of market prices for the EPEX day-ahead market.""" + """Add two days of market prices for the EPEX day-ahead market.""" epex_da = Market.query.filter(Market.name == "epex_da").one_or_none() data_source = DataSource.query.filter_by( name="Seita", type="demo script" From 63e0cdc5aff0723ace09943042ceed9cd99b0b26 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 29 Mar 2021 13:38:33 +0200 Subject: [PATCH 02/10] Added TimedBelief table with i/o functionality --- flexmeasures/conftest.py | 23 +++++ ...c5f519d7_create_table_for_timed_beliefs.py | 53 +++++++++++ flexmeasures/data/models/time_series.py | 92 +++++++++++++++++++ flexmeasures/data/tests/test_queries.py | 42 +++++++++ requirements/app.in | 2 +- 5 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 flexmeasures/data/migrations/versions/e62ac5f519d7_create_table_for_timed_beliefs.py diff --git a/flexmeasures/conftest.py b/flexmeasures/conftest.py index 82b1b5046..0e8dadf23 100644 --- a/flexmeasures/conftest.py +++ b/flexmeasures/conftest.py @@ -23,6 +23,7 @@ from flexmeasures.data.models.assets import AssetType, Asset, Power from flexmeasures.data.models.data_sources import DataSource from flexmeasures.data.models.markets import Market, Price +from flexmeasures.data.models.time_series import Sensor, TimedBelief """ @@ -175,6 +176,28 @@ def setup_assets(db, setup_roles_users, setup_markets): db.session.add(p) +@pytest.fixture(scope="function") +def setup_beliefs(db: SQLAlchemy, setup_markets) -> int: + """ + :returns: the number of beliefs set up + """ + sensor = Sensor.query.filter(Sensor.name == "epex_da").one_or_none() + data_source = DataSource.query.filter_by( + name="Seita", type="demo script" + ).one_or_none() + beliefs = [ + TimedBelief( + sensor=sensor, + source_id=data_source.id, + event_value=21, + event_start="2021-03-28 16:00", + belief_horizon=timedelta(0), + ) + ] + db.session.add_all(beliefs) + return len(beliefs) + + @pytest.fixture(scope="function", autouse=True) def add_market_prices(db: SQLAlchemy, setup_assets, setup_markets): """Add two days of market prices for the EPEX day-ahead market.""" diff --git a/flexmeasures/data/migrations/versions/e62ac5f519d7_create_table_for_timed_beliefs.py b/flexmeasures/data/migrations/versions/e62ac5f519d7_create_table_for_timed_beliefs.py new file mode 100644 index 000000000..95fb2ddb3 --- /dev/null +++ b/flexmeasures/data/migrations/versions/e62ac5f519d7_create_table_for_timed_beliefs.py @@ -0,0 +1,53 @@ +"""create table for timed beliefs + +Revision ID: e62ac5f519d7 +Revises: a528c3c81506 +Create Date: 2021-03-28 16:26:45.025994 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "e62ac5f519d7" +down_revision = "a528c3c81506" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "timed_belief", + sa.Column( + "event_start", sa.DateTime(timezone=True), nullable=False, index=True + ), + sa.Column("belief_horizon", sa.Interval(), nullable=False), + sa.Column("cumulative_probability", sa.Float(), nullable=False, default=0.5), + sa.Column("event_value", sa.Float(), nullable=False), + sa.Column("sensor_id", sa.Integer(), nullable=False, index=True), + sa.Column("source_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["sensor_id"], + ["sensor.id"], + name=op.f("timed_belief_sensor_id_sensor_fkey"), + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["source_id"], + ["data_source.id"], + name=op.f("timed_belief_source_id_source_fkey"), + ondelete="CASCADE", + ), + sa.PrimaryKeyConstraint( + "event_start", + "belief_horizon", + "cumulative_probability", + "sensor_id", + name=op.f("timed_belief_pkey"), + ), + ) + + +def downgrade(): + op.drop_table("timed_belief") diff --git a/flexmeasures/data/models/time_series.py b/flexmeasures/data/models/time_series.py index 08e721509..e1a706ea3 100644 --- a/flexmeasures/data/models/time_series.py +++ b/flexmeasures/data/models/time_series.py @@ -6,6 +6,7 @@ import timely_beliefs as tb from flexmeasures.data.config import db +from flexmeasures.data.models.data_sources import DataSource from flexmeasures.data.queries.utils import ( add_belief_timing_filter, add_user_source_filter, @@ -19,6 +20,97 @@ class Sensor(db.Model, tb.SensorDBMixin): """A sensor measures events. """ + def search_beliefs( + self, + event_time_window: Tuple[Optional[datetime_type], Optional[datetime_type]] = ( + None, + None, + ), + belief_time_window: Tuple[Optional[datetime_type], Optional[datetime_type]] = ( + None, + None, + ), + source: Optional[Union[int, List[int], str, List[str]]] = None, + ): + """Search all beliefs about events for this sensor. + + :param event_time_window: search only events within this time window + :param belief_time_window: search only beliefs within this time window + :param source: search only beliefs by this source (pass its name or id) or list of sources""" + return TimedBelief.search_all( + sensor=self, + event_time_window=event_time_window, + belief_time_window=belief_time_window, + source=source, + ) + + def __repr__(self) -> str: + return f"" + + +class TimedBelief(db.Model, tb.TimedBeliefDBMixin): + """A timed belief holds a precisely timed record of a belief about an event.""" + + @declared_attr + def source_id(cls): + return db.Column(db.Integer, db.ForeignKey("data_source.id"), primary_key=True) + + sensor = db.relationship("Sensor", backref=db.backref("beliefs", lazy=True)) + source = db.relationship("DataSource", backref=db.backref("beliefs", lazy=True)) + + @classmethod + def search_all( + cls, + sensor: Sensor, + event_time_window: Tuple[Optional[datetime_type], Optional[datetime_type]] = ( + None, + None, + ), + belief_time_window: Tuple[Optional[datetime_type], Optional[datetime_type]] = ( + None, + None, + ), + source: Optional[Union[int, List[int], str, List[str]]] = None, + ) -> tb.BeliefsDataFrame: + """Search all beliefs about events for a given sensor. + + :param sensor: search only this sensor + :param event_time_window: search only events within this time window + :param belief_time_window: search only beliefs within this time window + :param source: search only beliefs by this source (pass its name or id) or list of sources + """ + return cls.query_all( + db.session, + sensor, + event_before=event_time_window[1], + event_not_before=event_time_window[0], + belief_before=belief_time_window[1], + belief_not_before=belief_time_window[0], + source=source, + source_cls=DataSource, + ) + + @classmethod + def persist_all(cls, bdf: tb.BeliefsDataFrame, commit_transaction: bool = True): + """Persist a BeliefsDataFrame as timed beliefs in the database. + + :param bdf: the BeliefsDataFrame to be persisted + :param commit_transaction: set to False if you're interested in persisting other data as well within one atomic transaction + """ + belief_records = ( + bdf.convert_index_from_belief_time_to_horizon() + .reset_index() + .to_dict("records") + ) + beliefs = [cls(**dict(**d, sensor=bdf.sensor)) for d in belief_records] + db.session.add_all(beliefs) + if commit_transaction: + db.session.commit() + + def __repr__(self) -> str: + """timely-beliefs representation of timed beliefs.""" + return tb.TimedBelief.__repr__(self) + class TimedValue(object): """ diff --git a/flexmeasures/data/tests/test_queries.py b/flexmeasures/data/tests/test_queries.py index e432cd2d2..5674118aa 100644 --- a/flexmeasures/data/tests/test_queries.py +++ b/flexmeasures/data/tests/test_queries.py @@ -7,6 +7,8 @@ import timely_beliefs as tb from flexmeasures.data.models.assets import Asset, Power +from flexmeasures.data.models.data_sources import DataSource +from flexmeasures.data.models.time_series import Sensor, TimedBelief from flexmeasures.data.queries.utils import ( multiply_dataframe_with_deterministic_beliefs, simplify_index, @@ -215,3 +217,43 @@ def test_simplify_index(): ) df = simplify_index(bdf) assert df.event_resolution == timedelta(minutes=15) + + +def test_query_beliefs(setup_beliefs): + """Check various ways of querying for beliefs.""" + sensor = Sensor.query.filter_by(name="epex_da").one_or_none() + source = DataSource.query.filter_by(name="Seita").one_or_none() + bdfs = [ + TimedBelief.search_all(sensor, source=source.id), + sensor.search_beliefs(source=source.id), + tb.BeliefsDataFrame(sensor.beliefs), # doesn't allow filtering + ] + for bdf in bdfs: + assert sensor.event_resolution == timedelta( + hours=0 + ) # todo change to 1 after migrating Markets to Sensors + assert bdf.event_resolution == timedelta( + hours=0 + ) # todo change to 1 after migrating Markets to Sensors + assert len(bdf) == setup_beliefs + + +def test_persist_beliefs(setup_beliefs): + """Check whether persisting beliefs works. + + We load the already set up beliefs, and form new beliefs an hour later. + """ + sensor = Sensor.query.filter_by(name="epex_da").one_or_none() + bdf: tb.BeliefsDataFrame = TimedBelief.search_all(sensor) + + # Form new beliefs + df = bdf.reset_index() + df["belief_time"] = df["belief_time"] + timedelta(hours=1) + df["event_value"] = df["event_value"] * 10 + bdf = df.set_index( + ["event_start", "belief_time", "source", "cumulative_probability"] + ) + + TimedBelief.persist_all(bdf) + bdf: tb.BeliefsDataFrame = TimedBelief.search_all(sensor) + assert len(bdf) == setup_beliefs * 2 diff --git a/requirements/app.in b/requirements/app.in index 592ed01c5..3de8c870b 100644 --- a/requirements/app.in +++ b/requirements/app.in @@ -32,7 +32,7 @@ netCDF4 siphon tables timetomodel>=0.6.8 -timely-beliefs>=1.2.1 +timely-beliefs>=1.3.0.dev8856 python-dotenv # a backport, not needed in Python3.8 importlib_metadata From 2060e4631252ccd1a6cbfe816aca843890108ee8 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 29 Mar 2021 13:48:52 +0200 Subject: [PATCH 03/10] Move some functionality to timely-beliefs --- flexmeasures/data/models/time_series.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/flexmeasures/data/models/time_series.py b/flexmeasures/data/models/time_series.py index e1a706ea3..eec1763a7 100644 --- a/flexmeasures/data/models/time_series.py +++ b/flexmeasures/data/models/time_series.py @@ -80,8 +80,8 @@ def search_all( :param source: search only beliefs by this source (pass its name or id) or list of sources """ return cls.query_all( - db.session, - sensor, + session=db.session, + sensor=sensor, event_before=event_time_window[1], event_not_before=event_time_window[0], belief_before=belief_time_window[1], @@ -97,15 +97,11 @@ def persist_all(cls, bdf: tb.BeliefsDataFrame, commit_transaction: bool = True): :param bdf: the BeliefsDataFrame to be persisted :param commit_transaction: set to False if you're interested in persisting other data as well within one atomic transaction """ - belief_records = ( - bdf.convert_index_from_belief_time_to_horizon() - .reset_index() - .to_dict("records") + return cls.add_all( + session=db.session, + beliefs_data_frame=bdf, + commit_transaction=commit_transaction, ) - beliefs = [cls(**dict(**d, sensor=bdf.sensor)) for d in belief_records] - db.session.add_all(beliefs) - if commit_transaction: - db.session.commit() def __repr__(self) -> str: """timely-beliefs representation of timed beliefs.""" From 8d19b9df6e81c2bcc6335a48655267c138ceeeb6 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 1 Apr 2021 10:43:43 +0200 Subject: [PATCH 04/10] Rename class methods --- flexmeasures/data/models/time_series.py | 16 +++++++++------- flexmeasures/data/tests/test_queries.py | 8 ++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/flexmeasures/data/models/time_series.py b/flexmeasures/data/models/time_series.py index eec1763a7..03b9fcfd5 100644 --- a/flexmeasures/data/models/time_series.py +++ b/flexmeasures/data/models/time_series.py @@ -37,7 +37,7 @@ def search_beliefs( :param event_time_window: search only events within this time window :param belief_time_window: search only beliefs within this time window :param source: search only beliefs by this source (pass its name or id) or list of sources""" - return TimedBelief.search_all( + return TimedBelief.search( sensor=self, event_time_window=event_time_window, belief_time_window=belief_time_window, @@ -59,7 +59,7 @@ def source_id(cls): source = db.relationship("DataSource", backref=db.backref("beliefs", lazy=True)) @classmethod - def search_all( + def search( cls, sensor: Sensor, event_time_window: Tuple[Optional[datetime_type], Optional[datetime_type]] = ( @@ -79,7 +79,7 @@ def search_all( :param belief_time_window: search only beliefs within this time window :param source: search only beliefs by this source (pass its name or id) or list of sources """ - return cls.query_all( + return cls.search_session( session=db.session, sensor=sensor, event_before=event_time_window[1], @@ -91,13 +91,15 @@ def search_all( ) @classmethod - def persist_all(cls, bdf: tb.BeliefsDataFrame, commit_transaction: bool = True): - """Persist a BeliefsDataFrame as timed beliefs in the database. + def add(cls, bdf: tb.BeliefsDataFrame, commit_transaction: bool = True): + """Add a BeliefsDataFrame as timed beliefs in the database. :param bdf: the BeliefsDataFrame to be persisted - :param commit_transaction: set to False if you're interested in persisting other data as well within one atomic transaction + :param commit_transaction: if True, the session is committed + if False, you can still add other data to the session + and commit it all within an atomic transaction """ - return cls.add_all( + return cls.add_to_session( session=db.session, beliefs_data_frame=bdf, commit_transaction=commit_transaction, diff --git a/flexmeasures/data/tests/test_queries.py b/flexmeasures/data/tests/test_queries.py index 5674118aa..714553f9c 100644 --- a/flexmeasures/data/tests/test_queries.py +++ b/flexmeasures/data/tests/test_queries.py @@ -224,7 +224,7 @@ def test_query_beliefs(setup_beliefs): sensor = Sensor.query.filter_by(name="epex_da").one_or_none() source = DataSource.query.filter_by(name="Seita").one_or_none() bdfs = [ - TimedBelief.search_all(sensor, source=source.id), + TimedBelief.search(sensor, source=source.id), sensor.search_beliefs(source=source.id), tb.BeliefsDataFrame(sensor.beliefs), # doesn't allow filtering ] @@ -244,7 +244,7 @@ def test_persist_beliefs(setup_beliefs): We load the already set up beliefs, and form new beliefs an hour later. """ sensor = Sensor.query.filter_by(name="epex_da").one_or_none() - bdf: tb.BeliefsDataFrame = TimedBelief.search_all(sensor) + bdf: tb.BeliefsDataFrame = TimedBelief.search(sensor) # Form new beliefs df = bdf.reset_index() @@ -254,6 +254,6 @@ def test_persist_beliefs(setup_beliefs): ["event_start", "belief_time", "source", "cumulative_probability"] ) - TimedBelief.persist_all(bdf) - bdf: tb.BeliefsDataFrame = TimedBelief.search_all(sensor) + TimedBelief.add(bdf) + bdf: tb.BeliefsDataFrame = TimedBelief.search(sensor) assert len(bdf) == setup_beliefs * 2 From 2cb927586b0edc397e6e5ca26aed39dea64d5908 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 1 Apr 2021 11:36:38 +0200 Subject: [PATCH 05/10] Source db class now derived from instance --- flexmeasures/data/models/time_series.py | 2 -- flexmeasures/data/tests/test_queries.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/flexmeasures/data/models/time_series.py b/flexmeasures/data/models/time_series.py index 03b9fcfd5..8ff401e3f 100644 --- a/flexmeasures/data/models/time_series.py +++ b/flexmeasures/data/models/time_series.py @@ -6,7 +6,6 @@ import timely_beliefs as tb from flexmeasures.data.config import db -from flexmeasures.data.models.data_sources import DataSource from flexmeasures.data.queries.utils import ( add_belief_timing_filter, add_user_source_filter, @@ -87,7 +86,6 @@ def search( belief_before=belief_time_window[1], belief_not_before=belief_time_window[0], source=source, - source_cls=DataSource, ) @classmethod diff --git a/flexmeasures/data/tests/test_queries.py b/flexmeasures/data/tests/test_queries.py index 714553f9c..b510809ad 100644 --- a/flexmeasures/data/tests/test_queries.py +++ b/flexmeasures/data/tests/test_queries.py @@ -224,8 +224,8 @@ def test_query_beliefs(setup_beliefs): sensor = Sensor.query.filter_by(name="epex_da").one_or_none() source = DataSource.query.filter_by(name="Seita").one_or_none() bdfs = [ - TimedBelief.search(sensor, source=source.id), - sensor.search_beliefs(source=source.id), + TimedBelief.search(sensor, source=source), + sensor.search_beliefs(source=source), tb.BeliefsDataFrame(sensor.beliefs), # doesn't allow filtering ] for bdf in bdfs: From 1b5235f11db0cc6c305bb87c88d79a79779430e0 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 1 Apr 2021 16:35:14 +0200 Subject: [PATCH 06/10] Fix mixin usage by initializing superclasses in a specific order with specific args and kwargs --- flexmeasures/conftest.py | 4 ++-- flexmeasures/data/models/time_series.py | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/flexmeasures/conftest.py b/flexmeasures/conftest.py index 0e8dadf23..ebde5931c 100644 --- a/flexmeasures/conftest.py +++ b/flexmeasures/conftest.py @@ -188,9 +188,9 @@ def setup_beliefs(db: SQLAlchemy, setup_markets) -> int: beliefs = [ TimedBelief( sensor=sensor, - source_id=data_source.id, + source=data_source, event_value=21, - event_start="2021-03-28 16:00", + event_start="2021-03-28 16:00+01", belief_horizon=timedelta(0), ) ] diff --git a/flexmeasures/data/models/time_series.py b/flexmeasures/data/models/time_series.py index 8ff401e3f..5b8ce7aba 100644 --- a/flexmeasures/data/models/time_series.py +++ b/flexmeasures/data/models/time_series.py @@ -4,6 +4,7 @@ from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.orm import Query, Session import timely_beliefs as tb +import timely_beliefs.utils as tb_utils from flexmeasures.data.config import db from flexmeasures.data.queries.utils import ( @@ -19,6 +20,11 @@ class Sensor(db.Model, tb.SensorDBMixin): """A sensor measures events. """ + def __init__(self, name: str, **kwargs): + tb.SensorDBMixin.__init__(self, name, **kwargs) + tb_utils.remove_class_init_kwargs(tb.SensorDBMixin, kwargs) + db.Model.__init__(self, **kwargs) + def search_beliefs( self, event_time_window: Tuple[Optional[datetime_type], Optional[datetime_type]] = ( @@ -48,7 +54,10 @@ def __repr__(self) -> str: class TimedBelief(db.Model, tb.TimedBeliefDBMixin): - """A timed belief holds a precisely timed record of a belief about an event.""" + """A timed belief holds a precisely timed record of a belief about an event. + + It also records the source of the belief, and the sensor that the event pertains to. + """ @declared_attr def source_id(cls): @@ -57,6 +66,16 @@ def source_id(cls): sensor = db.relationship("Sensor", backref=db.backref("beliefs", lazy=True)) source = db.relationship("DataSource", backref=db.backref("beliefs", lazy=True)) + def __init__( + self, + sensor: tb.DBSensor, + source: tb.DBBeliefSource, + **kwargs, + ): + tb.TimedBeliefDBMixin.__init__(self, sensor, source, **kwargs) + tb_utils.remove_class_init_kwargs(tb.TimedBeliefDBMixin, kwargs) + db.Model.__init__(self, **kwargs) + @classmethod def search( cls, From b3641124d81eafd1a8a836f6b002a71fe65c6a66 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 1 Apr 2021 16:41:22 +0200 Subject: [PATCH 07/10] Set dependency dev version --- requirements/app.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/app.in b/requirements/app.in index 3de8c870b..d91d225b3 100644 --- a/requirements/app.in +++ b/requirements/app.in @@ -32,7 +32,7 @@ netCDF4 siphon tables timetomodel>=0.6.8 -timely-beliefs>=1.3.0.dev8856 +timely-beliefs==1.3.0.dev2664 python-dotenv # a backport, not needed in Python3.8 importlib_metadata From 0f1f92113ac8eb1cf09a98a827e2c9d3637b10d0 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Fri, 2 Apr 2021 16:12:48 +0200 Subject: [PATCH 08/10] Register TimedBelief as a data table for db-obs --- flexmeasures/data/scripts/data_gen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flexmeasures/data/scripts/data_gen.py b/flexmeasures/data/scripts/data_gen.py index 8f0c0eafc..89b13bc99 100644 --- a/flexmeasures/data/scripts/data_gen.py +++ b/flexmeasures/data/scripts/data_gen.py @@ -17,6 +17,7 @@ import inflect from flexmeasures.data.models.markets import MarketType, Market, Price +from flexmeasures.data.models.time_series import TimedBelief from flexmeasures.data.models.assets import AssetType, Asset, Power from flexmeasures.data.models.data_sources import DataSource from flexmeasures.data.models.weather import WeatherSensorType, WeatherSensor, Weather @@ -625,5 +626,5 @@ def get_affected_classes(structure: bool = True, data: bool = False): DataSource, ] if data: - affected_classes += [Power, Price, Weather] + affected_classes += [TimedBelief, Power, Price, Weather] return affected_classes From 45737d3eb9e5f8a36aa131aedf1996b16c0b2ed2 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Fri, 2 Apr 2021 19:29:03 +0200 Subject: [PATCH 09/10] Set minimum tb dependency to released version --- requirements/app.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/app.in b/requirements/app.in index d91d225b3..8eabd2405 100644 --- a/requirements/app.in +++ b/requirements/app.in @@ -32,7 +32,7 @@ netCDF4 siphon tables timetomodel>=0.6.8 -timely-beliefs==1.3.0.dev2664 +timely-beliefs>=1.3.0 python-dotenv # a backport, not needed in Python3.8 importlib_metadata From 48e6bb56ff8b1a49db2934a7265952d20b9d1e58 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Fri, 2 Apr 2021 20:13:54 +0200 Subject: [PATCH 10/10] Changelog entry --- documentation/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/changelog.rst b/documentation/changelog.rst index 49fb85af2..c3a4c39d1 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -9,6 +9,7 @@ v0.2.5 | April XX, 2021 Infrastructure / Support ---------------------- * Updated dependencies, including Flask-Security-Too [see `PR #82 `_] +* Integration with `timely beliefs `_ lib: Sensor data as TimedBeliefs [see `PR #79 `_]