2025-05-14 10:08:34 +02:00
/ * *
* @ file Measurement . js
*
* Permission is hereby granted to any person obtaining a copy of this software
* and associated documentation files ( the "Software" ) , to use it for personal
* or non - commercial purposes , with the following restrictions :
*
* 1. * * No Copying or Redistribution * * : The Software or any of its parts may not
* be copied , merged , distributed , sublicensed , or sold without explicit
* prior written permission from the author .
*
* 2. * * Commercial Use * * : Any use of the Software for commercial purposes requires
* a valid license , obtainable only with the explicit consent of the author .
*
* THE SOFTWARE IS PROVIDED "AS IS" , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE , AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES , OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT , OR OTHERWISE , ARISING FROM ,
* OUT OF , OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE .
*
* Ownership of this code remains solely with the original author . Unauthorized
* use of this Software is strictly prohibited .
*
* Author :
* - Rene De Ren / Sjoerd Fijnje
* Email :
2025-08-08 09:51:57 +02:00
* - r . de . ren @ brabantsedelta . nl
2025-05-14 10:08:34 +02:00
*
* /
const EventEmitter = require ( 'events' ) ;
const Logger = require ( '../../../generalFunctions/helper/logger' ) ;
const defaultConfig = require ( './monsterConfig.json' ) ;
const ConfigUtils = require ( '../../../generalFunctions/helper/configUtils' ) ;
const tf = require ( '@tensorflow/tfjs' ) ;
const tfLoader = require ( './modelLoader' ) ;
class Monster {
/*------------------- Construct and set vars -------------------*/
constructor ( config = { } ) {
//init
this . init = false ; // keep track of init
this . emitter = new EventEmitter ( ) ; // Own EventEmitter
this . configUtils = new ConfigUtils ( defaultConfig , config . general . logging . enabled , config . general . logging . logLevel ) ; // ConfigUtils
this . config = this . configUtils . initConfig ( config ) ;
// Init after config is set
this . logger = new Logger ( this . config . general . logging . enabled , this . config . general . logging . logLevel , this . config . general . name ) ;
this . modelLoader = new tfLoader ( this . logger ) ; // ModelLoader
// -------------------------------------- fetch dependencies --------------------------
this . convert = require ( '../../../convert/dependencies/index' ) ;
this . math = require ( 'mathjs' ) ;
this . model = null ; // TensorFlow model placeholder
//place holders for output data
this . output = { } ; // object to place all relevant outputs in and preform event change check on
this . child = { } ; // register childs
//Specific object info
this . aquonSampleName = "112100" ; // aquon sample name to start automatic sampling on the basis of the document
this . monsternametijden = { } ; // json monsternametijden file?
this . rain _data = { } ; // precipitation data
this . aggregatedOutput = { } ; // object that does not contain momentary values but a combination of all kinds of data over a fixed period of time
this . sumRain = 0 ; // total sum of rain over time window + n hours and - n hours
this . avgRain = 0 ; // total divided by number of locations to get average over total time
this . daysPerYear = 0 ; // how many days remaining for this year
// outputs
this . pulse = false ; // output pulse to sampling machine
this . bucketVol = 0 ; // how full is the sample?
this . sumPuls = 0 ; // number of pulses so far
this . predFlow = 0 ; // predicted flow over sampling time in hours, expressed in m3
this . bucketWeight = 0 ; // actual weight of bucket
//inputs
this . q = 0 ; // influent flow in m3/h
this . i _start = false // when true, the program gets kicked off calculating what it needs to take samples
this . sampling _time = config . constraints . samplingtime ; // time expressed in hours over which the sampling will run (currently 24)
this . emptyWeightBucket = config . asset . emptyWeightBucket ; // empty weight of the bucket
// internal vars
this . temp _pulse = 0 ; // each interval pulses send out 1 and then reset
this . volume _pulse = 0.05 ; // define volume pulse expressed in L
this . minVolume = config . constraints . minVolume ; // define min volume in a sampling cabinet before a sample is declared valid expressed in L
this . maxVolume = 0 ; // calculated maxvolume depending on own weight
this . maxWeight = config . constraints . maxWeight ; // define max volume in a sampling cabinet before a sample is declared invalid expressed in L
this . cap _volume = 55 ; // abs max capacity of bucket (volume) in liters
this . targetVolume = 0 ; // volume of sampling cabinet that model aims for
this . minPuls = 0 ; // calculates the min pulses depending on min_vol and max_vol
this . maxPuls = 0 ; // calculates the max pulses depending on min_vol and max_vol
this . absMaxPuls = 0 ; // capacity of sampling cabinet (number of pulses)
this . targetPuls = 0 ; // keeps track of the desired amount of pulses (+- 50% tolerance), based on aimed volume
this . m3PerPuls = 0 ; // each pulse is equal to a number of m3
this . predM3PerSec = 0 ; // predicted flow in m3 per second
this . m3PerTick = 0 ; // actual measured flow in m3 per second
this . m3Total = 0 ; // total measured flow over sampling time in m3
this . running = false ; // define if sampling is running or not
this . qLineRaw = { } ; // see example
this . minSeen = { } ; // keeps track of minimum ever seen so far in a time period for each hour (over totals not every value)
this . maxSeen = { } ; // keeps track of maximum ever seen so far in a time period for each hour (over totals not every value)
this . qLineRefined = { } ; // this should be the ( quantiles? ) classified in the datasets
this . calcTimeShiftDry = 0 ; // What is the delay after a dry period of minimum n hours
this . calcTimeShiftWet = 0 ;
this . calcCapacitySewer = 0 ;
// how much rain goes to the sewage ? -> calculate surface area of hardend / sewage.
this . minDryHours = 0 ; // what is the minimum of dry hours before we can calculate timeshift? spot this with moving average?
this . minWetHours = 0 ; // how long does it take to remove all the rain?
this . resolution = 0 ; // Number of chunks in qLineRaw / define how big the window is to sum all values ( for now we need to take 1 hour or bigger resolutions but in the future smaller is better to see more accurate correlations)
this . tmpTotQ = 0 ; // keep track of sum of q within resolution window
//old prediction factor
this . predFactor = 0.7 ; // define factor as multiplier for prediction
//track program start and stop
this . start _time = Date . now ( ) ; // default start time
this . stop _time = Date . now ( ) ; // default stop time
this . flowTime = 0 ; //keep track in detail how much time between 2 ticks for more accurate flow measurement
this . timePassed = 0 ; // time in seconds
this . timeLeft = 0 ; // time in seconds
this . currHour = new Date ( ) . getHours ( ) ; // on init define in which hour we are 0 - 23
this . init = true ; // end of constructor
//set boundries and targets after init based on above settings
this . set _boundries _and _targets ( ) ;
}
/*------------------- GETTER/SETTERS Dynamics -------------------*/
set monsternametijden ( value ) {
if ( this . init ) {
if ( Object . keys ( value ) . length > 0 ) {
//check if push is in valid format and not null
if (
typeof value [ 0 ] . SAMPLE _NAME !== 'undefined'
&&
typeof value [ 0 ] . DESCRIPTION !== 'undefined'
&&
typeof value [ 0 ] . SAMPLED _DATE !== 'undefined'
&&
typeof value [ 0 ] . START _DATE !== 'undefined'
&&
typeof value [ 0 ] . END _DATE !== 'undefined'
) {
//each time this changes we load the next date applicable for this function
this . _monsternametijden = value ;
//fetch dates
this . regNextDate ( value ) ;
}
else {
// Monsternametijden object Wrong format contact AQUON
}
}
else {
// Monsternametijden object Wrong format contact AQUON
}
}
}
get monsternametijden ( ) {
return this . _monsternametijden ;
}
set rain _data ( value ) {
//retrieve precipitation expected during the coming day and precipitation of yesterday
this . _rain _data = value ;
//only update after init and is not running.
if ( this . init && ! this . running ) {
this . updatePredRain ( value ) ;
}
}
get rain _data ( ) {
return this . _rain _data ;
}
set bucketVol ( val ) {
//Put val in local var
this . _bucketVol = val ;
//Place into output object
this . output . bucketVol = val ;
// update bucket weight
this . bucketWeight = val + this . emptyWeightBucket ;
}
get bucketVol ( ) {
return this . _bucketVol ;
}
set minVolume ( val ) {
//Protect against 0
val == 0 ? val = 1 : val = val ;
this . _minVolume = val ;
//Place into output object
this . output . minVolume = val ;
}
get minVolume ( ) {
return this . _minVolume ;
}
set q ( val ) {
//Put val in local var
this . _q = val ;
//Place into output object
this . output . q = val ;
this . output . qm3sec = this . convert ( val ) . from ( 'm3/h' ) . to ( 'm3/s' ) ;
}
get q ( ) {
return this . _q ;
}
/*------------------- FUNCTIONS -------------------*/
set _boundries _and _targets ( ) {
// define boundries for algorithm
this . maxVolume = this . maxWeight - this . emptyWeightBucket ; // substract bucket weight of max volume assuming they are both on a 1 to 1 ratio
this . minPuls = Math . round ( this . minVolume / this . volume _pulse ) ; // minimum pulses we want before we have a valid sample
this . maxPuls = Math . round ( this . maxVolume / this . volume _pulse ) ; // maximum pulses we can handle (otherwise sample is too heavy)
this . absMaxPuls = Math . round ( this . cap _volume / this . volume _pulse ) ; // number of pulses a sample can contain before overflowing
// define target values
this . targetVolume = this . minVolume * Math . sqrt ( this . maxVolume / this . minVolume ) ;
//old way
//this.targetVolume = Math.round( ( ( (this.maxVolume - this.minVolume) / 2 ) + this.minVolume ) * 100) / 100; // calculate middle between min and max
// correct target values
this . targetPuls = Math . round ( this . targetVolume / this . volume _pulse ) ; // define desired amount of pulses (in this case our prediction can deviate 50% up and 50% down without a problem)
}
updateArchiveRain ( val ) {
}
updatePredRain ( value ) {
//make date objects to define relative time window
let now = new Date ( Date . now ( ) ) ;
let past = new Date ( Date . now ( ) ) ;
let future = new Date ( Date . now ( ) ) ;
let totalRaw = { } ;
let totalProb = { } ;
let totalAvg = { } ;
//refine object with different values
let rain = { } ;
rain . hourly = { } ; // an object with timestamps and aggreated over all locations summed precipation in mm
rain . hourly . time = [ ] ;
rain . hourly . precipationRaw = [ ] ;
rain . hourly . precipationProb = [ ] ;
let numberOfLocations = 0 ;
//Make timestamp + 24 hours
future . setHours ( now . getHours ( ) + 24 ) ;
//Make timestamp - 24hours
past . setHours ( now . getHours ( ) - 24 ) ;
//go through all locations and sum up the average precipation of each location so we have summed precipation over every hour
Object . entries ( value ) . forEach ( ( [ locationKey , location ] , locationindex ) => {
//number of locations
numberOfLocations ++ ;
// make an object to keep track of the dataset we load
this . aggregatedOutput [ locationKey ] = { } ;
this . aggregatedOutput [ locationKey ] . tag = { } ;
this . aggregatedOutput [ locationKey ] . tag . latitude = location . latitude ;
this . aggregatedOutput [ locationKey ] . tag . longitude = location . longitude ;
this . aggregatedOutput [ locationKey ] . precipationRaw = { } ;
this . aggregatedOutput [ locationKey ] . precipationProb = { } ;
//loop through object for each location over all hourlys
Object . entries ( location . hourly . time ) . forEach ( ( [ key , time ] , index ) => {
this . aggregatedOutput [ locationKey ] . precipationRaw [ key ] = { } ;
this . aggregatedOutput [ locationKey ] . precipationProb [ key ] = { } ;
//convert string output to a date object
let checkdate = new Date ( time ) ;
//convert date to milliseconds timestamps
let currTimestamp = checkdate . getTime ( ) ;
let probability = 100 ; //default probility unless otherwise defined
if ( typeof location . hourly . precipitation _probability !== 'undefined' ) {
probability = location . hourly . precipitation _probability [ key ] ;
}
if ( probability > 0 ) {
probability /= 100 ;
}
// only interested in dates before timeframe and after to make use of
2025-08-08 09:51:57 +02:00
// ( currTimestamp >= now && currTimestamp < future) || ( currTimestamp < now && currTimestamp > past )
if ( true ) {
2025-05-14 10:08:34 +02:00
typeof totalRaw [ currTimestamp ] === 'undefined' ? totalRaw [ currTimestamp ] = 0 : null ;
typeof totalProb [ currTimestamp ] === 'undefined' ? totalProb [ currTimestamp ] = 0 : null ;
//placed probability into the equation
totalRaw [ currTimestamp ] += location . hourly . precipitation [ key ] ;
totalProb [ currTimestamp ] += ( location . hourly . precipitation [ key ] * probability ) ;
//keep track of all requested data
this . aggregatedOutput [ locationKey ] . precipationRaw [ key ] [ "val" ] = location . hourly . precipitation [ key ] ; // raw data from open weather data
this . aggregatedOutput [ locationKey ] . precipationRaw [ key ] [ "time" ] = currTimestamp ;
this . aggregatedOutput [ locationKey ] . precipationProb [ key ] [ "val" ] = probability ; // probability of open weather
this . aggregatedOutput [ locationKey ] . precipationProb [ key ] [ "time" ] = currTimestamp ;
}
//remove dead info
if ( Object . keys ( this . aggregatedOutput [ locationKey ] . precipationRaw [ key ] ) . length == 0 ) {
delete this . aggregatedOutput [ locationKey ] . precipationRaw [ key ] ;
} ;
if ( Object . keys ( this . aggregatedOutput [ locationKey ] . precipationProb [ key ] ) . length == 0 ) {
delete this . aggregatedOutput [ locationKey ] . precipationProb [ key ] ;
} ;
} ) ;
} ) ;
//total sum expected over time window (just for ref now not so important anymore)
this . sumRain = Object . values ( totalProb ) . reduce ( ( sum , value ) => sum + value , 0 ) ;
this . avgRain = this . sumRain / numberOfLocations ;
//make average over prob
Object . entries ( totalProb ) . forEach ( ( [ key , sum ] , index ) => {
typeof totalAvg [ key ] === 'undefined' ? totalAvg [ key ] = 0 : null ;
totalAvg [ key ] = sum / numberOfLocations ;
} ) ;
//make new prediction
2025-08-08 09:51:57 +02:00
//this.get_model_prediction();
return this . aggregatedOutput ;
2025-05-14 10:08:34 +02:00
}
get _model _prediction ( ) {
2025-08-08 09:51:57 +02:00
// combine 24 hourly predictions to make one daily prediction (for the next 24 hours including the current hour)
let inputs = [ ] ;
for ( let predHour = 0 ; predHour <= 23 ; predHour ++ ) {
// select 24 timestamps based on hour te be predicted
let now = new Date ( ) ;
const lastHour = new Date ( now . setHours ( now . getHours ( ) + predHour ) ) ;
let timestamps = this . rain _data [ 0 ] . hourly . time . map ( ts => new Date ( ts ) ) ;
let timestamps _24 = timestamps . filter ( ts => ts <= lastHour ) . slice ( - 24 )
// for each relevant hour calculate the mean precipitation across all areas
let precipitation = [ ] ;
for ( let i = 0 ; i < timestamps . length ; i ++ ) {
if ( timestamps _24 . includes ( timestamps [ i ] ) ) {
let values = [ ] ;
for ( let j = 0 ; j < this . rain _data . length ; j ++ ) {
values . push ( this . rain _data [ j ] . hourly . precipitation [ i ] ) ;
}
let mean = values . reduce ( ( sum , value ) => sum + value , 0 ) / this . rain _data . length ;
precipitation . push ( mean ) ;
}
2025-05-14 10:08:34 +02:00
}
2025-08-08 09:51:57 +02:00
// standardize variables for prediction and 'zip' them
let hours = timestamps _24 . map ( ts => ts . getHours ( ) ) ;
hours = hours . map ( hour => ( hour - 11.50430734 ) / 6.92241142 ) ;
precipitation = precipitation . map ( value => ( value - 0.09011861 ) / 0.43853627 ) ;
let zipped = hours . map ( ( value , i ) => [ value , precipitation [ i ] ] ) ;
// collect inputdata for model
inputs . push ( zipped ) ;
}
const output = this . model _loader ( inputs ) ;
console . log ( 'Final output: ' + output ) ;
}
async model _loader ( inputs ) {
let dailyPred = 0 ;
try {
const localURL = "http://127.0.0.1:1880/generalFunctions/datasets/lstmData/tfjs_model/model.json" ;
// Could you log the original model JSON to help determine the correct input shape?
const response = await fetch ( localURL ) ;
const modelJSON = await response . json ( ) ;
console . log ( 'Original model config:' , JSON . stringify ( modelJSON . modelTopology . model _config . config . layers [ 0 ] , null , 24 , 2 ) ) ;
// Try loading with default input shape
const model = await this . modelLoader . loadModel ( localURL ) ;
console . log ( 'Model loaded successfully!' ) ;
// make predictions
for ( const input of inputs ) {
const inputTensor = tf . tensor3d ( [ input ] ) ;
const predict = model . predict ( inputTensor ) ;
let predictValue = await predict . data ( ) ;
// back-transformation because of standardization of the response variable
predictValue = predictValue [ 0 ] * 1024.1940942 + 1188.0105115 ;
dailyPred += predictValue ;
}
console . log ( 'Daily prediction: ' + dailyPred ) ;
} catch ( error ) {
console . error ( 'Failed to load model:' , error ) ;
}
return dailyPred ;
}
2025-05-14 10:08:34 +02:00
sampling _program ( ) {
// ------------------ Run once on conditions and start sampling
if ( ( ( this . i _start ) || ( Date . now ( ) >= this . nextDate ) ) && ! this . running ) {
this . running = true ;
// reset persistent vars
this . temp _pulse = 0 ;
this . pulse = false ;
this . bucketVol = 0 ;
this . sumPuls = 0 ;
this . m3Total = 0 ;
this . timePassed = 0 ; // time in seconds
this . timeLeft = 0 ; // time in seconds
this . predM3PerSec = 0 ;
//run prediction to ensure its value is filled
this . get _model _prediction ( ) ;
// define m3 per pulse for this run and round to int !
this . m3PerPuls = Math . round ( this . predFlow / this . targetPuls ) ;
this . predM3PerSec = this . predFlow / this . sampling _time / 60 / 60 ; // predicted m3 per time
// define start and stop time based on calender data
this . start _time = Date . now ( ) ;
this . stop _time = Date . now ( ) + ( this . sampling _time * 60 * 60 * 1000 ) ; // convert to milliseconds
//reset parameters and look for next date
this . regNextDate ( this . monsternametijden ) ;
// reset start
this . i _start = false ;
}
// ------------------ Run for as long as sampling time is not greater than stop time
if ( this . stop _time > Date . now ( ) ) {
// define time vars
this . timePassed = Math . round ( ( Date . now ( ) - this . start _time ) / 1000 ) ;
this . timeLeft = Math . round ( ( this . stop _time - Date . now ( ) ) / 1000 ) ;
// calc temp pulse rate
let update = this . m3PerTick / this . m3PerPuls ;
// update values
this . temp _pulse += update ;
this . m3Total += this . m3PerTick ;
// check if we need to send out a pulse (stop sending pulses if capacity is reached)
if ( this . temp _pulse >= 1 && this . sumPuls < this . absMaxPuls ) {
// reset
this . temp _pulse += - 1 ;
// send out a pulse and add to count
this . pulse = true ;
// count pulses
this . sumPuls ++ ;
// update bucket volume each puls
this . bucketVol = Math . round ( this . sumPuls * this . volume _pulse * 100 ) / 100 ;
}
else {
if ( this . sumPuls > this . absMaxPuls ) {
// find out how to reschedule sample automatically?
}
//update pulse when its true
if ( this . pulse ) {
this . pulse = false ; // continue but don't send out a pulse
}
}
}
else
{
//after setting once dont do it again
if ( this . running ) {
// Vars can only be 0 if this is not running
this . m3PerPuls = 0 ;
this . temp _pulse = 0 ;
this . pulse = false ;
this . bucketVol = 0 ;
this . sumPuls = 0 ;
this . timePassed = 0 ; // time in seconds
this . timeLeft = 0 ; // time in seconds
this . predFlow = 0 ;
this . predM3PerSec = 0 ;
this . m3Total = 0 ;
this . running = false ; // end of sampling program (stop_time reached)
}
}
}
flowCalc ( ) {
//reset timePassed
let timePassed = 0 ;
// each tick calc flowtimepassed
this . flowTime > 0 ? timePassed = ( Date . now ( ) - this . flowTime ) / 1000 : timePassed = 0 ;
//conver to m3 per tick
this . m3PerTick = this . q / 60 / 60 * timePassed ;
// put new timestamp
this . flowTime = Date . now ( ) ;
}
//goes through time related functions
tick ( ) {
//calculate flow based on input
this . flowCalc ( ) ;
//run sampling program
this . sampling _program ( ) ;
//logQ for predictions / forecasts
this . logQoverTime ( ) ;
}
regNextDate ( monsternametijden ) {
let next _date = new Date ( new Date ( ) . setFullYear ( new Date ( ) . getFullYear ( ) + 1 ) ) ;
let n _days _remaining = 0 ;
if ( typeof monsternametijden !== 'undefined' ) {
// loop through lines
Object . entries ( monsternametijden ) . forEach ( ( [ key , line ] , index ) => {
//console.log(line.START_DATE);
//check if date is not null
if ( line . START _DATE != "NULL" ) {
let curr _date _conv = new Date ( line . START _DATE ) ;
let curr _date = curr _date _conv . getTime ( ) ;
//check if sample name is this sample and if date is bigger than now.
if ( line . SAMPLE _NAME == this . aquonSampleName && curr _date > Date . now ( ) ) {
//only keep date that is bigger than current but smaller than the ones that follow after it.
if ( curr _date < next _date ) { next _date = curr _date ; }
// check if its within this year only show those days as days remaining
if ( new Date ( ) . getFullYear ( ) == curr _date _conv . getFullYear ( ) ) { n _days _remaining ++ ; }
}
}
} ) ;
}
else {
//this.warning.push(3);
}
//store vars remaining
this . daysPerYear = n _days _remaining ;
this . nextDate = next _date ;
}
logQoverTime ( ) {
//store currHour in temp obj for easy ref
let h = this . currHour ;
// define rain hour of which the correlation is the biggest this doesnt belong in this section do this afterwards
// let rainH = h - this.calcTimeShift ;
// how much rain fell on rainH (define category)
// fetch current hour from actual time
const currentHour = new Date ( ) . getHours ( ) ;
//on hour change begin log
if ( h !== currentHour ) {
//write current total to object
this . qLineRaw . h = this . tmpTotQ
//reset tmpTotQ
//set this.currHour to currentHour
}
}
//create objects where to push arrays in to keep track of data
createMinMaxSeen ( ) {
//check which hour it is , then make sum , after sum is complete check which hour it is
//loop over sampling time expressed in hours
for ( let h = 1 ; h < this . sampling _time ; h ++ ) {
this . minSeen = { } ;
}
}
} // end of class
module . exports = Monster ;
const mConfig = {
general : {
name : "Monster" ,
logging : {
logLevel : "debug" ,
enabled : true ,
} ,
} ,
asset : {
emptyWeightBucket : 3 ,
} ,
constraints : {
minVolume : 4 ,
maxWeight : 23 ,
} ,
}
let monster = new Monster ( mConfig ) ;
2025-08-08 09:51:57 +02:00
monster . rain _data = [ { "latitude" : 51.7 , "longitude" : 4.8139997 , "generationtime_ms" : 0.03802776336669922 , "utc_offset_seconds" : 3600 , "timezone" : "Europe/Berlin" , "timezone_abbreviation" : "CET" , "elevation" : 0 , "hourly_units" : { "time" : "iso8601" , "precipitation" : "mm" , "precipitation_probability" : "%" } , "hourly" : { "time" : [ "2024-11-04T00:00" , "2024-11-04T01:00" , "2024-11-04T02:00" , "2024-11-04T03:00" , "2024-11-04T04:00" , "2024-11-04T05:00" , "2024-11-04T06:00" , "2024-11-04T07:00" , "2024-11-04T08:00" , "2024-11-04T09:00" , "2024-11-04T10:00" , "2024-11-04T11:00" , "2024-11-04T12:00" , "2024-11-04T13:00" , "2024-11-04T14:00" , "2024-11-04T15:00" , "2024-11-04T16:00" , "2024-11-04T17:00" , "2024-11-04T18:00" , "2024-11-04T19:00" , "2024-11-04T20:00" , "2024-11-04T21:00" , "2024-11-04T22:00" , "2024-11-04T23:00" , "2024-11-05T00:00" , "2024-11-05T01:00" , "2024-11-05T02:00" , "2024-11-05T03:00" , "2024-11-05T04:00" , "2024-11-05T05:00" , "2024-11-05T06:00" , "2024-11-05T07:00" , "2024-11-05T08:00" , "2024-11-05T09:00" , "2024-11-05T10:00" , "2024-11-05T11:00" , "2024-11-05T12:00" , "2024-11-05T13:00" , "2024-11-05T14:00" , "2024-11-05T15:00" , "2024-11-05T16:00" , "2024-11-05T17:00" , "2024-11-05T18:00" , "2024-11-05T19:00" , "2024-11-05T20:00" , "2024-11-05T21:00" , "2024-11-05T22:00" , "2024-11-05T23:00" , "2024-11-06T00:00" , "2024-11-06T01:00" , "2024-11-06T02:00" , "2024-11-06T03:00" , "2024-11-06T04:00" , "2024-11-06T05:00" , "2024-11-06T06:00" , "2024-11-06T07:00" , "2024-11-06T08:00" , "2024-11-06T09:00" , "2024-11-06T10:00" , "2024-11-06T11:00" , "2024-11-06T12:00" , "2024-11-06T13:00" , "2024-11-06T14:00" , "2024-11-06T15:00" , "2024-11-06T16:00" , "2024-11-06T17:00" , "2024-11-06T18:00" , "2024-11-06T19:00" , "2024-11-06T20:00" , "2024-11-06T21:00" , "2024-11-06T22:00" , "2024-11-06T23:00" , "2024-11-07T00:00" , "2024-11-07T01:00" , "2024-11-07T02:00" , "2024-11-07T03:00" , "2024-11-07T04:00" , "2024-11-07T05:00" , "2024-11-07T06:00" , "2024-11-07T07:00" , "2024-11-07T08:00" , "2024-11-07T09:00" , "2024-11-07T10:00" , "2024-11-07T11:00" , "2024-11-07T12:00" , "2024-11-07T13:00" , "2024-11-07T14:00" , "2024-11-07T15:00" , "2024-11-07T16:00" , "2024-11-07T17:00" , "2024-11-07T18:00" , "2024-11-07T19:00" , "2024-11-07T20:00" , "2024-11-07T21:00" , "2024-11-07T22:00" , "2024-11-07T23:00" ] , "precipitation" : [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] , "precipitation_probability" : [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] } } , { "latitude" : 51.736 , "longitude" : 4.785 , "generationtime_ms" : 0.031948089599609375 , "utc_offset_seconds" : 3600 , "timezone" : "Europe/Berlin" , "timezone_abbreviation" : "CET" , "elevation" : 2 , "location_id" : 1 , "hourly_units" : { "time" : "iso8601" , "precipitation" : "mm" , "precipitation_probability" : "%" } , "hourly" : { "time" : [ "2024-11-04T00:00" , "2024-11-04T01:00" , "2024-11-04T02:00" , "2024-11-04T03:00" , "2024-11-04T04:00" , "2024-11-04T05:00" , "2024-11-04T06:00" , "2024-11-04T07:00" , "2024-11-04T08:00" , "2024-11-04T09:00" , "2024-11-04T10:00" , "2024-11-04T11:00" , "2024-11-04T12:00" , "2024-11-04T13:00" , "2024-11-04T14:00" , "2024-11-04T15:00" , "2024-11-04T16:00" , "2024-11-04T17:00" , "2024-11-04T18:00" , "2024-11-04T19:00" , "2024-11-04T20:00" , "2024-11-04T21:00" , "2024-11-04T22:00" , "2024-11-04T23:00" , "2024-11-05T00:00" , "2024-11-05T01:00" , "2024-11-05T02:00" , "2024-11-05T03:00" , "2024-11-05T04:00" , "2024-11-05T05:00" , "2024-11-05T06:00" , "2024-11-05T07:00" , "2024-11-05T08:00" , "2024-11-05T09:00" , "2024-11-05T10:00" , "2024-11-05T11:00" , "2024-11-05T12:00" , "2024-11-05T13:00" , "2024-11-05T14:00" , "2024-11-05T15:00" , "2024-11-05T16:00" , "2024-11-05T17:00" , "2024-11-05T18:00" , "2024-11-05T19:00" , "2024-11-05T20:00" , "2024-11-05T21:00" , "2024-11-05T22:00" , "2024-11-05T23:00" , "2024-11-06T00:00" , "2024-11-06T01:00" , "2024-11-06T02:00" , "2024-11-06T03:00" , "2024-11-06T04:00" , "2024-11-06T05:00" , "2024-11-06T06:00" , "2024-11-06T07:00" , "2024-11-06T08:00" , "2024-11-06T09:00" , "2024-11-06T10:00" , "2024-11-06T11:00" , "2024-11-06T12:00" , "2024-11-06T13:00" , "2024-11-06T14:00" , "2024-11-06T15:00" , "2024-11-06T16:00" , " 20
2025-05-14 10:08:34 +02:00
2025-08-08 09:51:57 +02:00
monster . get _model _prediction ( ) ;
2025-05-14 10:08:34 +02:00
2025-08-08 09:51:57 +02:00
/ *
// combine 24 hourly predictions to make one daily prediction (for the next 24 hours including the current hour)
let inputs = [ ] ;
for ( let predHour = 0 ; predHour <= 23 ; predHour ++ ) {
// select 24 timestamps based on hour te be predicted
let now = new Date ( ) ;
const lastHour = new Date ( now . setHours ( now . getHours ( ) + predHour ) ) ;
let timestamps = monster . rain _data [ 0 ] . hourly . time . map ( ts => new Date ( ts ) ) ;
timestamps _24 = timestamps . filter ( ts => ts <= lastHour ) . slice ( - 24 )
// for each relevant hour calculate the mean precipitation across all areas
let precipitation = [ ] ;
for ( let i = 0 ; i < timestamps . length ; i ++ ) {
if ( timestamps _24 . includes ( timestamps [ i ] ) ) {
2025-05-14 10:08:34 +02:00
2025-08-08 09:51:57 +02:00
let values = [ ] ;
for ( let j = 0 ; j < monster . rain _data . length ; j ++ ) {
2025-05-14 10:08:34 +02:00
2025-08-08 09:51:57 +02:00
values . push ( monster . rain _data [ j ] . hourly . precipitation [ i ] ) ;
}
let mean = values . reduce ( ( sum , value ) => sum + value , 0 ) / monster . rain _data . length ;
precipitation . push ( mean ) ;
}
}
2025-05-14 10:08:34 +02:00
2025-08-08 09:51:57 +02:00
// standardize variables for prediction and 'zip' them
let hours = timestamps _24 . map ( ts => ts . getHours ( ) ) ;
hours = hours . map ( hour => ( hour - 11.50430734 ) / 6.92241142 ) ;
precipitation = precipitation . map ( value => ( value - 0.09011861 ) / 0.43853627 ) ;
zipped = hours . map ( ( value , i ) => [ value , precipitation [ i ] ] ) ;
2025-05-14 10:08:34 +02:00
2025-08-08 09:51:57 +02:00
// collect inputdata for model
inputs . push ( zipped ) ;
}
( async ( ) => {
try {
const localURL = "http://127.0.0.1:1880/generalFunctions/datasets/lstmData/tfjs_model/model.json" ;
// Could you log the original model JSON to help determine the correct input shape?
const response = await fetch ( localURL ) ;
const modelJSON = await response . json ( ) ;
console . log ( 'Original model config:' , JSON . stringify ( modelJSON . modelTopology . model _config . config . layers [ 0 ] , null , 24 , 2 ) ) ;
// Try loading with default input shape
const model = await monster . modelLoader . loadModel ( localURL ) ;
console . log ( 'Model loaded successfully!' ) ;
// make predictions
let dailyPred = 0 ;
for ( const input of inputs ) {
const inputTensor = tf . tensor3d ( [ input ] ) ;
const predict = model . predict ( inputTensor ) ;
let predictValue = await predict . data ( ) ;
2025-05-14 10:08:34 +02:00
2025-08-08 09:51:57 +02:00
// back-transformation because of standardization of the response variable
predictValue = predictValue [ 0 ] * 1024.1940942 + 1188.0105115 ;
dailyPred += predictValue ;
}
console . log ( 'Daily prediction: ' + dailyPred ) ;
} catch ( error ) {
console . error ( 'Failed to load model:' , error ) ;
}
} ) ( ) ; * /
2025-05-14 10:08:34 +02:00