From efd77e1be71950d065bdafa54e36d20d08d758e6 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Fri, 15 Jul 2022 14:41:36 +0200 Subject: [PATCH 01/34] Add replay button to sensor page Signed-off-by: F.N. Claessen --- flexmeasures/api/dev/sensors.py | 1 + flexmeasures/ui/static/css/flexmeasures.css | 47 +++++++++- flexmeasures/ui/templates/views/sensors.html | 90 +++++++++++++++++++- 3 files changed, 134 insertions(+), 4 deletions(-) diff --git a/flexmeasures/api/dev/sensors.py b/flexmeasures/api/dev/sensors.py index 0a48c20ef..805e9222a 100644 --- a/flexmeasures/api/dev/sensors.py +++ b/flexmeasures/api/dev/sensors.py @@ -69,6 +69,7 @@ def get_chart(self, id: int, sensor: Sensor, **kwargs): "beliefs_after": AwareDateTimeField(format="iso", required=False), "beliefs_before": AwareDateTimeField(format="iso", required=False), "resolution": DurationField(required=False), + "most_recent_beliefs_only": fields.Boolean(required=False), }, location="query", ) diff --git a/flexmeasures/ui/static/css/flexmeasures.css b/flexmeasures/ui/static/css/flexmeasures.css index 4f9238068..e66c94df5 100644 --- a/flexmeasures/ui/static/css/flexmeasures.css +++ b/flexmeasures/ui/static/css/flexmeasures.css @@ -1715,4 +1715,49 @@ div.heading-group { align-items: center; } -/* ---- End Unsure / potential Legacy ---- */ \ No newline at end of file +/* ---- End Unsure / potential Legacy ---- */ + + +/* ---- Play/Pause button ---- */ +.replay-container { + display: flex; + align-items: center; + justify-content: center; +} +#replay { + margin-top: 190px; + width: 50px; + height: 50px; + position: relative; +} +#replay:before, #replay:after { + content: ""; + position: absolute; + width: 0; + border-style: solid; + border-color: transparent; + border-left-color: var(--secondary-color); + transition: 0.3s; +} +#replay:not(.playing):before { + height: 50px; + border-width: 25px 0px 25px 50px; +} +#replay:not(.playing):after { + left: 25px; + top: 12.5px; + height: 0; + border-width: 12.5px 0 12.5px 25px; +} +#replay:before { + left: 0; + top: 0; + height: 50px; + border-width: 0 0 0 16.6666666667px; +} +#replay:after { + left: 33.3333333333px; + top: 0; + height: 50px; + border-width: 0 0 0 16.6666666667px; +} \ No newline at end of file diff --git a/flexmeasures/ui/templates/views/sensors.html b/flexmeasures/ui/templates/views/sensors.html index 38a2ca449..5886f02c9 100644 --- a/flexmeasures/ui/templates/views/sensors.html +++ b/flexmeasures/ui/templates/views/sensors.html @@ -24,7 +24,11 @@ Loading... -
+
+
+
+
+

@@ -41,6 +45,10 @@ let vegaView; let previousResult; + let queryStartDate; + let queryEndDate; + let storeStartDate; + let storeEndDate; async function embedAndLoad(chartSpecsPath, elementId, datasetName, previousResult, startDate, endDate) { @@ -98,11 +106,20 @@ format: 'YYYY-MM-DD\\T00:00:00', }); picker.on('selected', (startDate, endDate) => { + + // Stop replay + let toggle = document.querySelector('#replay') + toggle.classList.remove('playing'); + toggle.classList.remove('paused'); + toggle.classList.add('stopped'); + startDate = startDate.toJSDate() endDate = endDate.toJSDate() endDate.setDate(endDate.getDate() + 1); - var queryStartDate = (startDate != null) ? (startDate.toISOString()) : (null) - var queryEndDate = (endDate != null) ? (endDate.toISOString()) : (null) + storeStartDate = startDate + storeEndDate = endDate + queryStartDate = (startDate != null) ? (startDate.toISOString()) : (null) + queryEndDate = (endDate != null) ? (endDate.toISOString()) : (null) // Abort previous request and create abort controller for new request controller.abort() @@ -189,6 +206,73 @@ } }); + // Set up play/pause button for replay + let toggle = document.querySelector('#replay'); + toggle.addEventListener('click', function(e) { + e.preventDefault(); + // toggle.classList.toggle('paused'); + if (toggle.classList.contains('playing')) { + // Pause replay + console.log("pause replay") + toggle.classList.remove('playing'); + toggle.classList.add('paused'); + } else if (toggle.classList.contains('stopped')) { + // Start replay + console.log("start replay") + // toggle.classList.remove('paused'); + toggle.classList.remove('stopped'); + toggle.classList.add('playing'); + var beliefTime = new Date(storeStartDate) + // var beliefTimedelta = 900000 + var beliefTimedelta = 3600000 + var numReplaySteps = Math.ceil((storeEndDate - storeStartDate) / beliefTimedelta) + queryStartDate = (storeStartDate != null) ? (storeStartDate.toISOString()) : (null) + queryEndDate = (storeEndDate != null) ? (storeEndDate.toISOString()) : (null) + + $("#spinner").show(); + Promise.all([ + // Fetch time series data + fetch(sensorPath + '/chart_data/?event_starts_after=' + queryStartDate + '&event_ends_before=' + queryEndDate + '&most_recent_beliefs_only=true', { + method: "GET", + headers: {"Content-Type": "application/json"}, + }) + .then(function(response) { return response.json(); }), + ]).then(function(result) { + $("#spinner").hide(); + load(result); + }).catch(console.error); + + const timer = ms => new Promise(res => setTimeout(res, ms)) + + async function load (result) { + for (var i = 0; i < numReplaySteps + 1; i++) { + while (document.getElementById('replay').classList.contains('paused') ) { + await timer(1000); + } + if (document.getElementById('replay').classList.contains('stopped') ) { + break; + } + var queryBeliefTime = (beliefTime != null) ? (beliefTime.toISOString()) : (null) + beliefTime = new Date(beliefTime.getTime() + beliefTimedelta) + var slicedResult = result[0].filter(item => { + return item.belief_time <= beliefTime.getTime() + }) + vegaView.change(datasetName, vega.changeset().remove(vega.truthy).insert(slicedResult)).resize().run(); + await timer(25); + } + // Stop replay when finished + console.log("stop replay") + toggle.classList.remove('playing'); + toggle.classList.add('stopped'); + } + } else { + // Resume replay + console.log("resume replay") + toggle.classList.remove('paused'); + toggle.classList.add('playing'); + } + }); + {% block leftsidepanel %} {{ super() }} {% endblock %} From 11f241d618aea10ce480612f4e91e07eaf5812a3 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Sat, 16 Jul 2022 14:48:28 +0200 Subject: [PATCH 02/34] Add replay time below replay button Signed-off-by: F.N. Claessen --- flexmeasures/ui/static/css/flexmeasures.css | 8 ++++++-- flexmeasures/ui/templates/views/sensors.html | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/flexmeasures/ui/static/css/flexmeasures.css b/flexmeasures/ui/static/css/flexmeasures.css index e66c94df5..b1a8a041d 100644 --- a/flexmeasures/ui/static/css/flexmeasures.css +++ b/flexmeasures/ui/static/css/flexmeasures.css @@ -1721,10 +1721,15 @@ div.heading-group { /* ---- Play/Pause button ---- */ .replay-container { display: flex; + flex-direction: column; align-items: center; justify-content: center; } +#replay-time { + margin-top: 15px; +} #replay { + cursor: pointer !important; margin-top: 190px; width: 50px; height: 50px; @@ -1751,13 +1756,12 @@ div.heading-group { } #replay:before { left: 0; - top: 0; height: 50px; border-width: 0 0 0 16.6666666667px; } #replay:after { left: 33.3333333333px; - top: 0; + top: 0px; height: 50px; border-width: 0 0 0 16.6666666667px; } \ No newline at end of file diff --git a/flexmeasures/ui/templates/views/sensors.html b/flexmeasures/ui/templates/views/sensors.html index 5886f02c9..ae30375c4 100644 --- a/flexmeasures/ui/templates/views/sensors.html +++ b/flexmeasures/ui/templates/views/sensors.html @@ -27,6 +27,7 @@
+
@@ -258,12 +259,14 @@ return item.belief_time <= beliefTime.getTime() }) vegaView.change(datasetName, vega.changeset().remove(vega.truthy).insert(slicedResult)).resize().run(); + document.getElementById('replay-time').innerHTML = beliefTime await timer(25); } // Stop replay when finished console.log("stop replay") toggle.classList.remove('playing'); toggle.classList.add('stopped'); + document.getElementById('replay-time').innerHTML = '' } } else { // Resume replay From cd8ce8ed4d8177ffe9f650d487a19d1d9dc0eb72 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 18 Jul 2022 12:37:03 +0200 Subject: [PATCH 03/34] Include older beliefs in replay Signed-off-by: F.N. Claessen --- flexmeasures/ui/templates/views/sensors.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexmeasures/ui/templates/views/sensors.html b/flexmeasures/ui/templates/views/sensors.html index ae30375c4..f14c18b11 100644 --- a/flexmeasures/ui/templates/views/sensors.html +++ b/flexmeasures/ui/templates/views/sensors.html @@ -233,7 +233,7 @@ $("#spinner").show(); Promise.all([ // Fetch time series data - fetch(sensorPath + '/chart_data/?event_starts_after=' + queryStartDate + '&event_ends_before=' + queryEndDate + '&most_recent_beliefs_only=true', { + fetch(sensorPath + '/chart_data/?event_starts_after=' + queryStartDate + '&event_ends_before=' + queryEndDate + '&most_recent_beliefs_only=false', { method: "GET", headers: {"Content-Type": "application/json"}, }) From 33b67a291205ad05e825f7d5681b1dae5b2cc039 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 21 Jul 2022 12:46:53 +0200 Subject: [PATCH 04/34] Show only most recent beliefs in replay at any given time, and add new slice to simulatedData instead of reslicing result all over (also time function performance) Signed-off-by: F.N. Claessen --- flexmeasures/ui/templates/views/sensors.html | 87 +++++++++++++++++++- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/flexmeasures/ui/templates/views/sensors.html b/flexmeasures/ui/templates/views/sensors.html index f14c18b11..44d1d0591 100644 --- a/flexmeasures/ui/templates/views/sensors.html +++ b/flexmeasures/ui/templates/views/sensors.html @@ -51,6 +51,61 @@ let storeStartDate; let storeEndDate; + function updateBeliefs(oldBeliefs, newBeliefs) { + // Update oldBeliefs with the most recent newBeliefs about the same event by the same source + + // Group by event start and source + var oldBeliefsByEventBySource = Object.fromEntries(new Map(oldBeliefs.map(belief => [belief['event_start'] + '_' + belief['source'].id, belief]))); // array -> dict (already had one belief per event) + + // Group by event start and source and select only the most recent new beliefs + var mostRecentNewBeliefsByEventBySource = Object.fromEntries(new Map(newBeliefs.map(belief => [belief['event_start'] + '_' + belief['source'].id, belief]))); // array -> dict (assumes beliefs are ordered by ascending belief time, with the last belief used as dict value) + + /** + // Group by event start and source + const groupByEventStartAndSource = (beliefs) => { + // Group beliefs by event_start and source, accumulates a dictionary using event_start and source.id as keys, and arrays of beliefs as values + + return beliefs.reduce((beliefsByEvent, currentBelief) => { + let groupKey = currentBelief['event_start'] + '_' + currentBelief['source'].id; + + // Initialize new dict entry if the event_start is new + if (!beliefsByEvent[groupKey]) { + beliefsByEvent[groupKey] = []; + } + + // Push the current belief into the group + beliefsByEvent[groupKey].push(currentBelief); + + return beliefsByEvent; + }, {}); + }; + var newBeliefsByEventBySource = groupByEventStartAndSource(newBeliefs); // array -> dict (possibly multiple beliefs per event) + + // Select only the most recent new beliefs + var mostRecentNewBeliefsByEventBySource = {}; + Object.entries(newBeliefsByEventBySource).forEach( + ([event_start_and_source, newBeliefs]) => { + let mostRecentNewBelief = newBeliefs.reduce((l, e) => e.belief_time > l.belief_time ? e : l); + mostRecentNewBeliefsByEventBySource[event_start_and_source] = mostRecentNewBelief; + } + ) + **/ + + // Return old beliefs updated with most recent new beliefs + return Object.values({...oldBeliefsByEventBySource, ...mostRecentNewBeliefsByEventBySource}) // dict -> list + } + + function partition(array, callback){ + return array.reduce(function(result, element, i) { + callback(element, i, array) + ? result[0].push(element) + : result[1].push(element); + + return result; + }, [[],[]] + ); + }; + async function embedAndLoad(chartSpecsPath, elementId, datasetName, previousResult, startDate, endDate) { await vegaEmbed('#'+elementId, chartSpecsPath + 'dataset_name=' + datasetName + '&width=container&include_sensor_annotations=true&include_asset_annotations=true', {{ chart_options | safe }}) @@ -246,6 +301,8 @@ const timer = ms => new Promise(res => setTimeout(res, ms)) async function load (result) { + var remainingData = result[0] + var simulatedData = [] for (var i = 0; i < numReplaySteps + 1; i++) { while (document.getElementById('replay').classList.contains('paused') ) { await timer(1000); @@ -255,10 +312,32 @@ } var queryBeliefTime = (beliefTime != null) ? (beliefTime.toISOString()) : (null) beliefTime = new Date(beliefTime.getTime() + beliefTimedelta) - var slicedResult = result[0].filter(item => { - return item.belief_time <= beliefTime.getTime() - }) - vegaView.change(datasetName, vega.changeset().remove(vega.truthy).insert(slicedResult)).resize().run(); + + let t0= performance.now(); + + // Split of one simulation step of new data from the remaining data + var newData + [newData, remainingData] = partition( + remainingData, + (item) => item.belief_time <= beliefTime.getTime(), + ); + + // Update beliefs in the simulated data given the new data + const oldLength = simulatedData.length + newData.length; + simulatedData = updateBeliefs(simulatedData, newData) + const newLength = simulatedData.length; + console.log("Removed " + (oldLength - newLength) + " obsolete beliefs") + + let t1= performance.now(); + + console.log('Time taken to update data:'+ (t1-t0) +' milliseconds'); + + // todo: remove obsolete beliefs and insert most recent new beliefs + vegaView.change(datasetName, vega.changeset().remove(vega.truthy).insert(simulatedData)).resize().run().finalize(); + + let t2= performance.now(); + + console.log('Time taken to embed data:'+ (t2-t1) +' milliseconds'); document.getElementById('replay-time').innerHTML = beliefTime await timer(25); } From 7d346a9b119cc1ccb8cd556c9a692077fb496da8 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 21 Jul 2022 15:45:56 +0200 Subject: [PATCH 05/34] Speed up: no need to resize Signed-off-by: F.N. Claessen --- flexmeasures/ui/templates/views/sensors.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexmeasures/ui/templates/views/sensors.html b/flexmeasures/ui/templates/views/sensors.html index 44d1d0591..51eb8f24a 100644 --- a/flexmeasures/ui/templates/views/sensors.html +++ b/flexmeasures/ui/templates/views/sensors.html @@ -333,7 +333,7 @@ console.log('Time taken to update data:'+ (t1-t0) +' milliseconds'); // todo: remove obsolete beliefs and insert most recent new beliefs - vegaView.change(datasetName, vega.changeset().remove(vega.truthy).insert(simulatedData)).resize().run().finalize(); + vegaView.change(datasetName, vega.changeset().remove(vega.truthy).insert(simulatedData)).run().finalize(); let t2= performance.now(); From 15cab01527999fe7c063eac29a84b20d8181f227 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 8 Aug 2022 15:23:03 +0200 Subject: [PATCH 06/34] Make sure vega view is embedded in page before attempting to change its dataset Signed-off-by: F.N. Claessen --- flexmeasures/ui/templates/crud/asset.html | 5 +++-- flexmeasures/ui/templates/views/sensors.html | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/flexmeasures/ui/templates/crud/asset.html b/flexmeasures/ui/templates/crud/asset.html index 66d6904dd..8a7490bb2 100644 --- a/flexmeasures/ui/templates/crud/asset.html +++ b/flexmeasures/ui/templates/crud/asset.html @@ -229,7 +229,6 @@

All sensors for {{ asset.name }}

signal = controller.signal $("#spinner").show(); - embedAndLoad(chartSpecsPath + 'event_starts_after=' + queryStartDate + '&event_ends_before=' + queryEndDate + '&', elementId, datasetName, previousResult, startDate, endDate); Promise.all([ // Fetch time series data fetch(assetPath + '/chart_data/?event_starts_after=' + queryStartDate + '&event_ends_before=' + queryEndDate, { @@ -246,8 +245,10 @@

All sensors for {{ asset.name }}

headers: {"Content-Type": "application/json"}, signal: signal, }) - .then(function(response) { return response.json(); }) + .then(function(response) { return response.json(); }), */ + + embedAndLoad(chartSpecsPath + 'event_starts_after=' + queryStartDate + '&event_ends_before=' + queryEndDate + '&', elementId, datasetName, previousResult, startDate, endDate), ]).then(function(result) { $("#spinner").hide(); vegaView.change(datasetName, vega.changeset().remove(vega.truthy).insert(result[0])).resize().run(); diff --git a/flexmeasures/ui/templates/views/sensors.html b/flexmeasures/ui/templates/views/sensors.html index 51eb8f24a..b4349c4e3 100644 --- a/flexmeasures/ui/templates/views/sensors.html +++ b/flexmeasures/ui/templates/views/sensors.html @@ -183,7 +183,6 @@ signal = controller.signal $("#spinner").show(); - embedAndLoad(chartSpecsPath + 'event_starts_after=' + queryStartDate + '&event_ends_before=' + queryEndDate + '&', elementId, datasetName, previousResult, startDate, endDate); Promise.all([ // Fetch time series data fetch(sensorPath + '/chart_data/?event_starts_after=' + queryStartDate + '&event_ends_before=' + queryEndDate, { @@ -197,7 +196,10 @@ method: "GET", headers: {"Content-Type": "application/json"}, }) - .then(function(response) { return response.json(); }) + .then(function(response) { return response.json(); }), + + // Embed chart + embedAndLoad(chartSpecsPath + 'event_starts_after=' + queryStartDate + '&event_ends_before=' + queryEndDate + '&', elementId, datasetName, previousResult, startDate, endDate), ]).then(function(result) { $("#spinner").hide(); vegaView.change(datasetName, vega.changeset().remove(vega.truthy).insert(result[0])).resize().run(); From 3ed684778e716cbc90bdd3417a288c48d00af6c8 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 11 Aug 2022 16:19:32 +0200 Subject: [PATCH 07/34] Typo Signed-off-by: F.N. Claessen --- flexmeasures/ui/templates/views/sensors.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexmeasures/ui/templates/views/sensors.html b/flexmeasures/ui/templates/views/sensors.html index b4349c4e3..fa47a413f 100644 --- a/flexmeasures/ui/templates/views/sensors.html +++ b/flexmeasures/ui/templates/views/sensors.html @@ -317,7 +317,7 @@ let t0= performance.now(); - // Split of one simulation step of new data from the remaining data + // Split off one simulation step of new data from the remaining data var newData [newData, remainingData] = partition( remainingData, From 4697771dfb64b8289a0a1b1fd98e65b5d5fc95df Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 11 Aug 2022 17:04:24 +0200 Subject: [PATCH 08/34] Add replay button to asset page Instead of having multiple columns with event_values, named by sensor_id, we switched to one column containing the sensor_id, in addition to the column holding the event_values. Signed-off-by: F.N. Claessen --- flexmeasures/api/v3_0/assets.py | 1 + .../data/models/charts/belief_charts.py | 6 +- flexmeasures/data/models/generic_assets.py | 19 +- flexmeasures/ui/templates/crud/asset.html | 169 +++++++++++++++++- 4 files changed, 181 insertions(+), 14 deletions(-) diff --git a/flexmeasures/api/v3_0/assets.py b/flexmeasures/api/v3_0/assets.py index 6bed518b8..081be138e 100644 --- a/flexmeasures/api/v3_0/assets.py +++ b/flexmeasures/api/v3_0/assets.py @@ -273,6 +273,7 @@ def get_chart(self, id: int, asset: GenericAsset, **kwargs): "event_ends_before": AwareDateTimeField(format="iso", required=False), "beliefs_after": AwareDateTimeField(format="iso", required=False), "beliefs_before": AwareDateTimeField(format="iso", required=False), + "most_recent_beliefs_only": fields.Boolean(required=False), }, location="query", ) diff --git a/flexmeasures/data/models/charts/belief_charts.py b/flexmeasures/data/models/charts/belief_charts.py index 6f1570666..f02b255c8 100644 --- a/flexmeasures/data/models/charts/belief_charts.py +++ b/flexmeasures/data/models/charts/belief_charts.py @@ -82,10 +82,7 @@ def chart_for_multiple_sensors( format=[".3s", unit], formatType="quantityWithUnitFormat", stack=None, - **{ - **FIELD_DEFINITIONS["event_value"], - **dict(field=sensor.id), - }, + **FIELD_DEFINITIONS["event_value"], ) event_start_field_definition = FIELD_DEFINITIONS["event_start"] if event_starts_after and event_ends_before: @@ -108,6 +105,7 @@ def chart_for_multiple_sensors( "title": capitalize(sensor.name) if sensor.name != sensor.sensor_type else None, + "transform": [{"filter": f"datum.sensor_id == {sensor.id}"}], "layer": [ { "mark": { diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index 9a609bb64..c1648bcd2 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -342,6 +342,7 @@ def search_beliefs( source: Optional[ Union[DataSource, List[DataSource], int, List[int], str, List[str]] ] = None, + most_recent_beliefs_only: bool = True, most_recent_events_only: bool = False, as_json: bool = False, ) -> Union[BeliefsDataFrame, str]: @@ -373,7 +374,7 @@ def search_beliefs( horizons_at_least=horizons_at_least, horizons_at_most=horizons_at_most, source=source, - most_recent_beliefs_only=True, + most_recent_beliefs_only=most_recent_beliefs_only, most_recent_events_only=most_recent_events_only, one_deterministic_belief_per_event_per_source=True, ) @@ -386,16 +387,16 @@ def search_beliefs( for sensor, bdf in bdf_dict.items(): bdf = bdf.resample_events(min_resolution) df = simplify_index( - bdf, index_levels_to_columns=["source"] - ).set_index(["source"], append=True) - df_dict[sensor.id] = df.rename(columns=dict(event_value=sensor.id)) - df = list(df_dict.values())[0].join( - list(df_dict.values())[1:], how="outer" - ) + bdf, index_levels_to_columns=["source"] if most_recent_beliefs_only else ["belief_time", "source"] + ).set_index(["source"] if most_recent_beliefs_only else ["belief_time", "source"], append=True) + df["sensor_id"] = sensor.id # or some JSONfiable representation + df = df.set_index(["sensor_id"], append=True) + df_dict[sensor.id] = df + df = pd.concat(df_dict.values()) else: df = simplify_index( - BeliefsDataFrame(), index_levels_to_columns=["source"] - ).set_index(["source"], append=True) + BeliefsDataFrame(), index_levels_to_columns=["source"] if most_recent_beliefs_only else ["belief_time", "source"] + ).set_index(["source"] if most_recent_beliefs_only else ["belief_time", "source"], append=True) df = df.reset_index() df["source"] = df["source"].apply(lambda x: x.to_dict()) return df.to_json(orient="records") diff --git a/flexmeasures/ui/templates/crud/asset.html b/flexmeasures/ui/templates/crud/asset.html index 8a7490bb2..e0621d7d1 100644 --- a/flexmeasures/ui/templates/crud/asset.html +++ b/flexmeasures/ui/templates/crud/asset.html @@ -146,7 +146,12 @@

All sensors for {{ asset.name }}

-
+
+
+
+
+
+
@@ -164,6 +169,65 @@

All sensors for {{ asset.name }}

let vegaView; let previousResult; + let queryStartDate; + let queryEndDate; + let storeStartDate; + let storeEndDate; + + function updateBeliefs(oldBeliefs, newBeliefs) { + // Update oldBeliefs with the most recent newBeliefs about the same event for the same sensor by the same source + + // Group by sensor, event start and source + var oldBeliefsByEventBySource = Object.fromEntries(new Map(oldBeliefs.map(belief => [belief['sensor_id'] + '_' + belief['event_start'] + '_' + belief['source'].id, belief]))); // array -> dict (already had one belief per event) + + // Group by sensor, event start and source, and select only the most recent new beliefs + var mostRecentNewBeliefsByEventBySource = Object.fromEntries(new Map(newBeliefs.map(belief => [belief['sensor_id'] + '_' + belief['event_start'] + '_' + belief['source'].id, belief]))); // array -> dict (assumes beliefs are ordered by ascending belief time, with the last belief used as dict value) + + /** + // Group by sensor, event start and source + const groupByEventStartAndSource = (beliefs) => { + // Group beliefs by event_start and source, accumulates a dictionary using event_start and source.id as keys, and arrays of beliefs as values + + return beliefs.reduce((beliefsByEvent, currentBelief) => { + let groupKey = currentBelief['sensor_id'] + '_' + currentBelief['event_start'] + '_' + currentBelief['source'].id; + + // Initialize new dict entry if the event_start is new + if (!beliefsByEvent[groupKey]) { + beliefsByEvent[groupKey] = []; + } + + // Push the current belief into the group + beliefsByEvent[groupKey].push(currentBelief); + + return beliefsByEvent; + }, {}); + }; + var newBeliefsByEventBySource = groupByEventStartAndSource(newBeliefs); // array -> dict (possibly multiple beliefs per event) + + // Select only the most recent new beliefs + var mostRecentNewBeliefsByEventBySource = {}; + Object.entries(newBeliefsByEventBySource).forEach( + ([event_start_and_source, newBeliefs]) => { + let mostRecentNewBelief = newBeliefs.reduce((l, e) => e.belief_time > l.belief_time ? e : l); + mostRecentNewBeliefsByEventBySource[event_start_and_source] = mostRecentNewBelief; + } + ) + **/ + + // Return old beliefs updated with most recent new beliefs + return Object.values({...oldBeliefsByEventBySource, ...mostRecentNewBeliefsByEventBySource}) // dict -> list + } + + function partition(array, callback){ + return array.reduce(function(result, element, i) { + callback(element, i, array) + ? result[0].push(element) + : result[1].push(element); + + return result; + }, [[],[]] + ); + }; async function embedAndLoad(chartSpecsPath, elementId, datasetName, previousResult, startDate, endDate) { @@ -217,9 +281,18 @@

All sensors for {{ asset.name }}

format: 'YYYY-MM-DD\\T00:00:00', }); picker.on('selected', (startDate, endDate) => { + + // Stop replay + let toggle = document.querySelector('#replay') + toggle.classList.remove('playing'); + toggle.classList.remove('paused'); + toggle.classList.add('stopped'); + startDate = startDate.toJSDate() endDate = endDate.toJSDate() endDate.setDate(endDate.getDate() + 1); + storeStartDate = startDate + storeEndDate = endDate var queryStartDate = (startDate != null) ? (startDate.toISOString()) : (null) var queryEndDate = (endDate != null) ? (endDate.toISOString()) : (null) @@ -248,6 +321,7 @@

All sensors for {{ asset.name }}

.then(function(response) { return response.json(); }), */ + // Embed chart embedAndLoad(chartSpecsPath + 'event_starts_after=' + queryStartDate + '&event_ends_before=' + queryEndDate + '&', elementId, datasetName, previousResult, startDate, endDate), ]).then(function(result) { $("#spinner").hide(); @@ -299,6 +373,99 @@

All sensors for {{ asset.name }}

}); } }; + + // Set up play/pause button for replay + let toggle = document.querySelector('#replay'); + toggle.addEventListener('click', function(e) { + e.preventDefault(); + // toggle.classList.toggle('paused'); + if (toggle.classList.contains('playing')) { + // Pause replay + console.log("pause replay") + toggle.classList.remove('playing'); + toggle.classList.add('paused'); + } else if (toggle.classList.contains('stopped')) { + // Start replay + console.log("start replay") + // toggle.classList.remove('paused'); + toggle.classList.remove('stopped'); + toggle.classList.add('playing'); + var beliefTime = new Date(storeStartDate) + // var beliefTimedelta = 900000 + var beliefTimedelta = 3600000 + var numReplaySteps = Math.ceil((storeEndDate - storeStartDate) / beliefTimedelta) + queryStartDate = (storeStartDate != null) ? (storeStartDate.toISOString()) : (null) + queryEndDate = (storeEndDate != null) ? (storeEndDate.toISOString()) : (null) + + $("#spinner").show(); + Promise.all([ + // Fetch time series data + fetch(assetPath + '/chart_data/?event_starts_after=' + queryStartDate + '&event_ends_before=' + queryEndDate + '&most_recent_beliefs_only=false', { + method: "GET", + headers: {"Content-Type": "application/json"}, + }) + .then(function(response) { return response.json(); }), + ]).then(function(result) { + $("#spinner").hide(); + load(result); + }).catch(console.error); + + const timer = ms => new Promise(res => setTimeout(res, ms)) + + async function load (result) { + var remainingData = result[0] + var simulatedData = [] + for (var i = 0; i < numReplaySteps + 1; i++) { + while (document.getElementById('replay').classList.contains('paused') ) { + await timer(1000); + } + if (document.getElementById('replay').classList.contains('stopped') ) { + break; + } + var queryBeliefTime = (beliefTime != null) ? (beliefTime.toISOString()) : (null) + beliefTime = new Date(beliefTime.getTime() + beliefTimedelta) + + let t0= performance.now(); + + // Split off one simulation step of new data from the remaining data + var newData + [newData, remainingData] = partition( + remainingData, + (item) => item.belief_time <= beliefTime.getTime(), + ); + + // Update beliefs in the simulated data given the new data + const oldLength = simulatedData.length + newData.length; + simulatedData = updateBeliefs(simulatedData, newData) + const newLength = simulatedData.length; + console.log("Removed " + (oldLength - newLength) + " obsolete beliefs") + + let t1= performance.now(); + + console.log('Time taken to update data:'+ (t1-t0) +' milliseconds'); + + // todo: remove obsolete beliefs and insert most recent new beliefs + vegaView.change(datasetName, vega.changeset().remove(vega.truthy).insert(simulatedData)).run().finalize(); + + let t2= performance.now(); + + console.log('Time taken to embed data:'+ (t2-t1) +' milliseconds'); + document.getElementById('replay-time').innerHTML = beliefTime + await timer(25); + } + // Stop replay when finished + console.log("stop replay") + toggle.classList.remove('playing'); + toggle.classList.add('stopped'); + document.getElementById('replay-time').innerHTML = '' + } + } else { + // Resume replay + console.log("resume replay") + toggle.classList.remove('paused'); + toggle.classList.add('playing'); + } + }); {% block leftsidepanel %} {{ super() }} {% endblock %} From e9cbcf42b7169e0e3dd1870fac4faee29eb41ea7 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 11 Aug 2022 17:35:14 +0200 Subject: [PATCH 09/34] Pass along sensor information as an object Signed-off-by: F.N. Claessen --- flexmeasures/data/models/charts/belief_charts.py | 2 +- flexmeasures/data/models/generic_assets.py | 5 +++-- flexmeasures/data/models/time_series.py | 9 +++++++++ flexmeasures/ui/templates/crud/asset.html | 6 +++--- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/flexmeasures/data/models/charts/belief_charts.py b/flexmeasures/data/models/charts/belief_charts.py index f02b255c8..e7089a269 100644 --- a/flexmeasures/data/models/charts/belief_charts.py +++ b/flexmeasures/data/models/charts/belief_charts.py @@ -105,7 +105,7 @@ def chart_for_multiple_sensors( "title": capitalize(sensor.name) if sensor.name != sensor.sensor_type else None, - "transform": [{"filter": f"datum.sensor_id == {sensor.id}"}], + "transform": [{"filter": f"datum.sensor.id == {sensor.id}"}], "layer": [ { "mark": { diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index c1648bcd2..57a62b227 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -389,8 +389,8 @@ def search_beliefs( df = simplify_index( bdf, index_levels_to_columns=["source"] if most_recent_beliefs_only else ["belief_time", "source"] ).set_index(["source"] if most_recent_beliefs_only else ["belief_time", "source"], append=True) - df["sensor_id"] = sensor.id # or some JSONfiable representation - df = df.set_index(["sensor_id"], append=True) + df["sensor"] = sensor # or some JSONfiable representation + df = df.set_index(["sensor"], append=True) df_dict[sensor.id] = df df = pd.concat(df_dict.values()) else: @@ -399,6 +399,7 @@ def search_beliefs( ).set_index(["source"] if most_recent_beliefs_only else ["belief_time", "source"], append=True) df = df.reset_index() df["source"] = df["source"].apply(lambda x: x.to_dict()) + df["sensor"] = df["sensor"].apply(lambda x: x.to_dict()) return df.to_json(orient="records") return bdf_dict diff --git a/flexmeasures/data/models/time_series.py b/flexmeasures/data/models/time_series.py index b8fe02daa..3e264c4fb 100644 --- a/flexmeasures/data/models/time_series.py +++ b/flexmeasures/data/models/time_series.py @@ -480,6 +480,15 @@ def timerange(self) -> Dict[str, datetime_type]: def __repr__(self) -> str: return f"" + def __str__(self) -> str: + return self.name + + def to_dict(self) -> dict: + return dict( + id=self.id, + name=self.name, + ) + @classmethod def find_closest( cls, generic_asset_type_name: str, sensor_name: str, n: int = 1, **kwargs diff --git a/flexmeasures/ui/templates/crud/asset.html b/flexmeasures/ui/templates/crud/asset.html index e0621d7d1..429b4455b 100644 --- a/flexmeasures/ui/templates/crud/asset.html +++ b/flexmeasures/ui/templates/crud/asset.html @@ -178,10 +178,10 @@

All sensors for {{ asset.name }}

// Update oldBeliefs with the most recent newBeliefs about the same event for the same sensor by the same source // Group by sensor, event start and source - var oldBeliefsByEventBySource = Object.fromEntries(new Map(oldBeliefs.map(belief => [belief['sensor_id'] + '_' + belief['event_start'] + '_' + belief['source'].id, belief]))); // array -> dict (already had one belief per event) + var oldBeliefsByEventBySource = Object.fromEntries(new Map(oldBeliefs.map(belief => [belief['sensor'].id + '_' + belief['event_start'] + '_' + belief['source'].id, belief]))); // array -> dict (already had one belief per event) // Group by sensor, event start and source, and select only the most recent new beliefs - var mostRecentNewBeliefsByEventBySource = Object.fromEntries(new Map(newBeliefs.map(belief => [belief['sensor_id'] + '_' + belief['event_start'] + '_' + belief['source'].id, belief]))); // array -> dict (assumes beliefs are ordered by ascending belief time, with the last belief used as dict value) + var mostRecentNewBeliefsByEventBySource = Object.fromEntries(new Map(newBeliefs.map(belief => [belief['sensor'].id + '_' + belief['event_start'] + '_' + belief['source'].id, belief]))); // array -> dict (assumes beliefs are ordered by ascending belief time, with the last belief used as dict value) /** // Group by sensor, event start and source @@ -189,7 +189,7 @@

All sensors for {{ asset.name }}

// Group beliefs by event_start and source, accumulates a dictionary using event_start and source.id as keys, and arrays of beliefs as values return beliefs.reduce((beliefsByEvent, currentBelief) => { - let groupKey = currentBelief['sensor_id'] + '_' + currentBelief['event_start'] + '_' + currentBelief['source'].id; + let groupKey = currentBelief['sensor'].id + '_' + currentBelief['event_start'] + '_' + currentBelief['source'].id; // Initialize new dict entry if the event_start is new if (!beliefsByEvent[groupKey]) { From 0cd4c2ab1a754315e22f5200e827d2556c9856fc Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 11 Aug 2022 17:35:37 +0200 Subject: [PATCH 10/34] black Signed-off-by: F.N. Claessen --- flexmeasures/data/models/generic_assets.py | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index 57a62b227..c280984ec 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -387,16 +387,32 @@ def search_beliefs( for sensor, bdf in bdf_dict.items(): bdf = bdf.resample_events(min_resolution) df = simplify_index( - bdf, index_levels_to_columns=["source"] if most_recent_beliefs_only else ["belief_time", "source"] - ).set_index(["source"] if most_recent_beliefs_only else ["belief_time", "source"], append=True) + bdf, + index_levels_to_columns=["source"] + if most_recent_beliefs_only + else ["belief_time", "source"], + ).set_index( + ["source"] + if most_recent_beliefs_only + else ["belief_time", "source"], + append=True, + ) df["sensor"] = sensor # or some JSONfiable representation df = df.set_index(["sensor"], append=True) df_dict[sensor.id] = df df = pd.concat(df_dict.values()) else: df = simplify_index( - BeliefsDataFrame(), index_levels_to_columns=["source"] if most_recent_beliefs_only else ["belief_time", "source"] - ).set_index(["source"] if most_recent_beliefs_only else ["belief_time", "source"], append=True) + BeliefsDataFrame(), + index_levels_to_columns=["source"] + if most_recent_beliefs_only + else ["belief_time", "source"], + ).set_index( + ["source"] + if most_recent_beliefs_only + else ["belief_time", "source"], + append=True, + ) df = df.reset_index() df["source"] = df["source"].apply(lambda x: x.to_dict()) df["sensor"] = df["sensor"].apply(lambda x: x.to_dict()) From 0333e21f70d63d9c62094cb78192bc36780a0f32 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 11 Aug 2022 17:35:47 +0200 Subject: [PATCH 11/34] missing type annotations Signed-off-by: F.N. Claessen --- flexmeasures/data/models/data_sources.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flexmeasures/data/models/data_sources.py b/flexmeasures/data/models/data_sources.py index f2b693a12..f17d07575 100644 --- a/flexmeasures/data/models/data_sources.py +++ b/flexmeasures/data/models/data_sources.py @@ -81,10 +81,10 @@ def description(self): descr += f" v{self.version}" return descr - def __repr__(self): + def __repr__(self) -> str: return "" % (self.id, self.description) - def __str__(self): + def __str__(self) -> str: return self.description def to_dict(self) -> dict: From 366cd4fe8cc7dfc87dae7091991db2f74165caed Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 11 Aug 2022 17:37:39 +0200 Subject: [PATCH 12/34] Nicer js formatting Signed-off-by: F.N. Claessen --- flexmeasures/ui/templates/crud/asset.html | 6 +++--- flexmeasures/ui/templates/views/sensors.html | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/flexmeasures/ui/templates/crud/asset.html b/flexmeasures/ui/templates/crud/asset.html index 429b4455b..0dbf65c30 100644 --- a/flexmeasures/ui/templates/crud/asset.html +++ b/flexmeasures/ui/templates/crud/asset.html @@ -178,10 +178,10 @@

All sensors for {{ asset.name }}

// Update oldBeliefs with the most recent newBeliefs about the same event for the same sensor by the same source // Group by sensor, event start and source - var oldBeliefsByEventBySource = Object.fromEntries(new Map(oldBeliefs.map(belief => [belief['sensor'].id + '_' + belief['event_start'] + '_' + belief['source'].id, belief]))); // array -> dict (already had one belief per event) + var oldBeliefsByEventBySource = Object.fromEntries(new Map(oldBeliefs.map(belief => [belief.sensor.id + '_' + belief.event_start + '_' + belief.source.id, belief]))); // array -> dict (already had one belief per event) // Group by sensor, event start and source, and select only the most recent new beliefs - var mostRecentNewBeliefsByEventBySource = Object.fromEntries(new Map(newBeliefs.map(belief => [belief['sensor'].id + '_' + belief['event_start'] + '_' + belief['source'].id, belief]))); // array -> dict (assumes beliefs are ordered by ascending belief time, with the last belief used as dict value) + var mostRecentNewBeliefsByEventBySource = Object.fromEntries(new Map(newBeliefs.map(belief => [belief.sensor.id + '_' + belief.event_start + '_' + belief.source.id, belief]))); // array -> dict (assumes beliefs are ordered by ascending belief time, with the last belief used as dict value) /** // Group by sensor, event start and source @@ -189,7 +189,7 @@

All sensors for {{ asset.name }}

// Group beliefs by event_start and source, accumulates a dictionary using event_start and source.id as keys, and arrays of beliefs as values return beliefs.reduce((beliefsByEvent, currentBelief) => { - let groupKey = currentBelief['sensor'].id + '_' + currentBelief['event_start'] + '_' + currentBelief['source'].id; + let groupKey = currentBelief.sensor.id + '_' + currentBelief.event_start + '_' + currentBelief.source.id; // Initialize new dict entry if the event_start is new if (!beliefsByEvent[groupKey]) { diff --git a/flexmeasures/ui/templates/views/sensors.html b/flexmeasures/ui/templates/views/sensors.html index fa47a413f..d4dc6139f 100644 --- a/flexmeasures/ui/templates/views/sensors.html +++ b/flexmeasures/ui/templates/views/sensors.html @@ -55,10 +55,10 @@ // Update oldBeliefs with the most recent newBeliefs about the same event by the same source // Group by event start and source - var oldBeliefsByEventBySource = Object.fromEntries(new Map(oldBeliefs.map(belief => [belief['event_start'] + '_' + belief['source'].id, belief]))); // array -> dict (already had one belief per event) + var oldBeliefsByEventBySource = Object.fromEntries(new Map(oldBeliefs.map(belief => [belief.event_start + '_' + belief.source.id, belief]))); // array -> dict (already had one belief per event) // Group by event start and source and select only the most recent new beliefs - var mostRecentNewBeliefsByEventBySource = Object.fromEntries(new Map(newBeliefs.map(belief => [belief['event_start'] + '_' + belief['source'].id, belief]))); // array -> dict (assumes beliefs are ordered by ascending belief time, with the last belief used as dict value) + var mostRecentNewBeliefsByEventBySource = Object.fromEntries(new Map(newBeliefs.map(belief => [belief.event_start + '_' + belief.source.id, belief]))); // array -> dict (assumes beliefs are ordered by ascending belief time, with the last belief used as dict value) /** // Group by event start and source @@ -66,7 +66,7 @@ // Group beliefs by event_start and source, accumulates a dictionary using event_start and source.id as keys, and arrays of beliefs as values return beliefs.reduce((beliefsByEvent, currentBelief) => { - let groupKey = currentBelief['event_start'] + '_' + currentBelief['source'].id; + let groupKey = currentBelief.event_start + '_' + currentBelief.source.id; // Initialize new dict entry if the event_start is new if (!beliefsByEvent[groupKey]) { From ce6d5b17030dcce7f3b361ed38943221a37cbb99 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 11 Aug 2022 17:41:18 +0200 Subject: [PATCH 13/34] Remove obsolete code Signed-off-by: F.N. Claessen --- flexmeasures/ui/templates/crud/asset.html | 31 -------------------- flexmeasures/ui/templates/views/sensors.html | 31 -------------------- 2 files changed, 62 deletions(-) diff --git a/flexmeasures/ui/templates/crud/asset.html b/flexmeasures/ui/templates/crud/asset.html index 0dbf65c30..27f77ce2b 100644 --- a/flexmeasures/ui/templates/crud/asset.html +++ b/flexmeasures/ui/templates/crud/asset.html @@ -183,37 +183,6 @@

All sensors for {{ asset.name }}

// Group by sensor, event start and source, and select only the most recent new beliefs var mostRecentNewBeliefsByEventBySource = Object.fromEntries(new Map(newBeliefs.map(belief => [belief.sensor.id + '_' + belief.event_start + '_' + belief.source.id, belief]))); // array -> dict (assumes beliefs are ordered by ascending belief time, with the last belief used as dict value) - /** - // Group by sensor, event start and source - const groupByEventStartAndSource = (beliefs) => { - // Group beliefs by event_start and source, accumulates a dictionary using event_start and source.id as keys, and arrays of beliefs as values - - return beliefs.reduce((beliefsByEvent, currentBelief) => { - let groupKey = currentBelief.sensor.id + '_' + currentBelief.event_start + '_' + currentBelief.source.id; - - // Initialize new dict entry if the event_start is new - if (!beliefsByEvent[groupKey]) { - beliefsByEvent[groupKey] = []; - } - - // Push the current belief into the group - beliefsByEvent[groupKey].push(currentBelief); - - return beliefsByEvent; - }, {}); - }; - var newBeliefsByEventBySource = groupByEventStartAndSource(newBeliefs); // array -> dict (possibly multiple beliefs per event) - - // Select only the most recent new beliefs - var mostRecentNewBeliefsByEventBySource = {}; - Object.entries(newBeliefsByEventBySource).forEach( - ([event_start_and_source, newBeliefs]) => { - let mostRecentNewBelief = newBeliefs.reduce((l, e) => e.belief_time > l.belief_time ? e : l); - mostRecentNewBeliefsByEventBySource[event_start_and_source] = mostRecentNewBelief; - } - ) - **/ - // Return old beliefs updated with most recent new beliefs return Object.values({...oldBeliefsByEventBySource, ...mostRecentNewBeliefsByEventBySource}) // dict -> list } diff --git a/flexmeasures/ui/templates/views/sensors.html b/flexmeasures/ui/templates/views/sensors.html index d4dc6139f..e6ed75d8c 100644 --- a/flexmeasures/ui/templates/views/sensors.html +++ b/flexmeasures/ui/templates/views/sensors.html @@ -60,37 +60,6 @@ // Group by event start and source and select only the most recent new beliefs var mostRecentNewBeliefsByEventBySource = Object.fromEntries(new Map(newBeliefs.map(belief => [belief.event_start + '_' + belief.source.id, belief]))); // array -> dict (assumes beliefs are ordered by ascending belief time, with the last belief used as dict value) - /** - // Group by event start and source - const groupByEventStartAndSource = (beliefs) => { - // Group beliefs by event_start and source, accumulates a dictionary using event_start and source.id as keys, and arrays of beliefs as values - - return beliefs.reduce((beliefsByEvent, currentBelief) => { - let groupKey = currentBelief.event_start + '_' + currentBelief.source.id; - - // Initialize new dict entry if the event_start is new - if (!beliefsByEvent[groupKey]) { - beliefsByEvent[groupKey] = []; - } - - // Push the current belief into the group - beliefsByEvent[groupKey].push(currentBelief); - - return beliefsByEvent; - }, {}); - }; - var newBeliefsByEventBySource = groupByEventStartAndSource(newBeliefs); // array -> dict (possibly multiple beliefs per event) - - // Select only the most recent new beliefs - var mostRecentNewBeliefsByEventBySource = {}; - Object.entries(newBeliefsByEventBySource).forEach( - ([event_start_and_source, newBeliefs]) => { - let mostRecentNewBelief = newBeliefs.reduce((l, e) => e.belief_time > l.belief_time ? e : l); - mostRecentNewBeliefsByEventBySource[event_start_and_source] = mostRecentNewBelief; - } - ) - **/ - // Return old beliefs updated with most recent new beliefs return Object.values({...oldBeliefsByEventBySource, ...mostRecentNewBeliefsByEventBySource}) // dict -> list } From 806b29460fbffc2f2d4d655c00cedd733220b4c1 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 11 Aug 2022 17:46:08 +0200 Subject: [PATCH 14/34] Refactor: move partition function into replay-utils.js Signed-off-by: F.N. Claessen --- flexmeasures/ui/static/js/replay-utils.js | 10 ++++++++++ flexmeasures/ui/templates/crud/asset.html | 12 +----------- flexmeasures/ui/templates/views/sensors.html | 12 +----------- 3 files changed, 12 insertions(+), 22 deletions(-) create mode 100644 flexmeasures/ui/static/js/replay-utils.js diff --git a/flexmeasures/ui/static/js/replay-utils.js b/flexmeasures/ui/static/js/replay-utils.js new file mode 100644 index 000000000..bfc7314dc --- /dev/null +++ b/flexmeasures/ui/static/js/replay-utils.js @@ -0,0 +1,10 @@ +// Replay utils +export function partition(array, callback){ + return array.reduce(function(result, element, i) { + callback(element, i, array) + ? result[0].push(element) + : result[1].push(element); + return result; + }, [[],[]] + ); +}; diff --git a/flexmeasures/ui/templates/crud/asset.html b/flexmeasures/ui/templates/crud/asset.html index 27f77ce2b..998626793 100644 --- a/flexmeasures/ui/templates/crud/asset.html +++ b/flexmeasures/ui/templates/crud/asset.html @@ -166,6 +166,7 @@

All sensors for {{ asset.name }}

+ + {% endblock sensorChartSetup %} + {% block attributions %}