Skip to content

Commit

Permalink
FSR-1293 | Latest Levels (#829)
Browse files Browse the repository at this point in the history
* added links to flood warnings target areas in thresholds

* altered logic for top of normal range and fixed unit tests

* changed chart.js threshold references

* fixed sonar issues

* refactored code to reduce complexity for Sonarcloud

* updated short name on thresholds to include name of flood warning area

* removed incorrect capital letter

* CSS fix to handle stations with historical threshold values

* added process warning thresholds functions and tests

* fixing sonar issues

* fixed linting issue

* sonar fix

* fixed chart label and latest levels CSS

* stopped displaying inactive warnings as thresholds

* sonar fix

* rejig of logic to show top of normal range

* sonar fixes

* altered value on top of normal range

* sonar fix

* changed top of normal range to 2dp

* rejig of 2dp fix

* altered id of top of normal range thresholds

* roll back of _chart-controls.scss

* added  stage datum calculations to warnings

* sonar fix

* bring process threshold into it's own class

* removed uneeded export

* linting changes
  • Loading branch information
LeeGordon83 authored Oct 22, 2024
1 parent c17efa0 commit 4da173b
Show file tree
Hide file tree
Showing 24 changed files with 683 additions and 323 deletions.
2 changes: 1 addition & 1 deletion server/models/views/lib/find-min-threshold.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function filterImtdThresholds (imtdThresholds) {

return {
alert: minObjectA ? minObjectA.value : null,
warning: minObjectW ? minObjectW.value : null
warning: minObjectW || null
}
}

Expand Down
2 changes: 1 addition & 1 deletion server/models/views/lib/latest-levels.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { formatElapsedTime } = require('../../../util')
const { processThreshold } = require('./process-imtd-thresholds') // Import processThreshold from the module
const processThreshold = require('./process-threshold') // Import processThreshold from the module

const WARNING_THRESHOLD_TYPES = ['FW RES FW', 'FW ACT FW', 'FW ACTCON FW']

Expand Down
98 changes: 67 additions & 31 deletions server/models/views/lib/process-imtd-thresholds.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,82 @@
function processThreshold (threshold, stationStageDatum, stationSubtract, postProcess) {
if (threshold) {
if (postProcess) {
if (stationStageDatum > 0) {
threshold = threshold - stationStageDatum
} else if (stationStageDatum <= 0 && stationSubtract > 0) {
threshold = threshold - stationSubtract
} else {
return parseFloat(threshold).toFixed(2)
}
}
return parseFloat(threshold).toFixed(2)
}
return null
}
const processThreshold = require('./process-threshold')

function processImtdThresholds (imtdThresholds, stationStageDatum, stationSubtract, postProcess) {
const TOP_OF_NORMAL_RANGE = 'Top of normal range'

function processImtdThresholds (imtdThresholds, stationStageDatum, stationSubtract, postProcess, pc5) {
const thresholds = []

const imtdThresholdWarning = processThreshold(imtdThresholds?.warning, stationStageDatum, stationSubtract, postProcess)
const imtdThresholdWarning = calculateWarningThreshold(imtdThresholds, stationStageDatum, stationSubtract, postProcess)
const imtdThresholdAlert = calculateAlertThreshold(imtdThresholds, stationStageDatum, stationSubtract, postProcess, pc5)

if (imtdThresholdWarning) {
// Correct threshold value if value > zero (Above Ordnance Datum) [FSR-595]
thresholds.push(imtdThresholdWarning)
}

if (imtdThresholdAlert.length > 0) {
for (const alert of imtdThresholdAlert) {
thresholds.push(alert)
}
} else if (pc5) {
thresholds.push({
id: 'pc5',
description: 'Top of normal range. Low lying land flooding possible above this level',
shortname: TOP_OF_NORMAL_RANGE,
value: pc5
})
} else { return thresholds }

return thresholds
}

function calculateWarningThreshold (imtdThresholds, stationStageDatum, stationSubtract, postProcess) {
const imtdThresholdWarning = processThreshold(imtdThresholds?.warning?.value, stationStageDatum, stationSubtract, postProcess)

if (imtdThresholdWarning) {
return {
id: 'warningThreshold',
description: 'Property flooding is possible above this level. One or more flood warnings may be issued',
description: 'Property flooding is possible above this level',
shortname: 'Possible flood warnings',
value: imtdThresholdWarning
})
}
}

return null
}

function calculateAlertThreshold (imtdThresholds, stationStageDatum, stationSubtract, postProcess, pc5) {
const imtdThresholdAlert = processThreshold(imtdThresholds?.alert, stationStageDatum, stationSubtract, postProcess)
const imtdThresholdAlerts = []
if (imtdThresholdAlert) {
thresholds.push({
id: 'alertThreshold',
description: 'Low lying land flooding is possible above this level. One or more flood alerts may be issued',
shortname: 'Possible flood alerts',
value: imtdThresholdAlert
})
// First condition: if imtdThresholdAlert is not equal to Top of Normal Range (pc5)
if (Number(imtdThresholdAlert) !== Number(pc5)) {
imtdThresholdAlerts.push({
id: 'alertThreshold',
description: 'Low lying land flooding possible above this level. One or more flood alerts may be issued',
shortname: 'Possible flood alerts',
value: imtdThresholdAlert
})
}
// Second condition: if imtdThresholdAlert is equal to Top of Normal Range (pc5)
if (Number(imtdThresholdAlert) === Number(pc5)) {
imtdThresholdAlerts.push({
id: 'alertThreshold',
description: 'Top of normal range. Low lying land flooding possible above this level. One or more flood alerts may be issued',
shortname: TOP_OF_NORMAL_RANGE,
value: imtdThresholdAlert
})
} else if (Number(pc5)) {
imtdThresholdAlerts.push({
id: 'pc5',
description: TOP_OF_NORMAL_RANGE,
shortname: TOP_OF_NORMAL_RANGE,
value: parseFloat(Number(pc5)).toFixed(2)
})
} else {
return imtdThresholdAlerts
}
}
return thresholds
}

module.exports = {
processImtdThresholds,
processThreshold
return imtdThresholdAlerts
}

module.exports = processImtdThresholds
17 changes: 17 additions & 0 deletions server/models/views/lib/process-threshold.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const processThreshold = (threshold, stationStageDatum, stationSubtract, postProcess) => {
if (threshold) {
if (postProcess) {
if (stationStageDatum > 0) {
threshold = threshold - stationStageDatum
} else if (stationStageDatum <= 0 && stationSubtract > 0) {
threshold = threshold - stationSubtract
} else {
return parseFloat(threshold).toFixed(2)
}
}
return parseFloat(threshold).toFixed(2)
}
return null
}

module.exports = processThreshold
65 changes: 65 additions & 0 deletions server/models/views/lib/process-warning-thresholds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const processThreshold = require('./process-threshold')

const FLOOD_WARNING_THRESHOLD = 2
const SEVERE_FLOOD_WARNING_THRESHOLD = 3
function filterThresholdsBySeverity (thresholds) {
return thresholds.filter(item =>
item.fwis_type === 'W' &&
item.severity_value !== null &&
(item.severity_value === FLOOD_WARNING_THRESHOLD || item.severity_value === SEVERE_FLOOD_WARNING_THRESHOLD)
)
}

function getMaxForEachFwisCode (thresholds) {
const maxValuesByFwisCode = {}

thresholds.forEach(threshold => {
const fwisCode = threshold.fwis_code
const severityValue = threshold.severity_value
const thresholdValue = threshold.value

// Check if there's already a threshold for this fwis_code
if (!maxValuesByFwisCode[fwisCode]) {
// If it's the first threshold for this fwis_code, store it
maxValuesByFwisCode[fwisCode] = threshold
} else {
const currentMax = maxValuesByFwisCode[fwisCode]

// Compare based on severity_value first, or if severity_value is the same, compare based on the threshold value
if (severityValue > currentMax.severity_value ||
(severityValue === currentMax.severity_value && parseFloat(thresholdValue) > parseFloat(currentMax.value))) {
maxValuesByFwisCode[fwisCode] = threshold
}
}
})

return Object.values(maxValuesByFwisCode)
}

function createWarningObject (threshold, stationStageDatum, stationSubtract, postProcess) {
const warningType =
threshold.severity_value === SEVERE_FLOOD_WARNING_THRESHOLD
? 'Severe flood warning'
: 'Flood warning'

const imtdThresholdWarning = processThreshold(parseFloat(threshold.threshold_value).toFixed(2), stationStageDatum, stationSubtract, postProcess)

return {
id: 'warningThreshold',
description: `${warningType} issued: <a href="/target-area/${threshold.fwis_code}">${threshold.ta_name}</a>`,
shortname: `${threshold.ta_name}`,
value: imtdThresholdWarning
}
}
function processWarningThresholds (imtdThresholds, stationStageDatum, stationSubtract, postProcess) {
const filteredThresholds = filterThresholdsBySeverity(imtdThresholds)
const maxThresholdsByFwisCode = getMaxForEachFwisCode(filteredThresholds)

const warningObjects = maxThresholdsByFwisCode.map(threshold =>
createWarningObject(threshold, stationStageDatum, stationSubtract, postProcess)
)

return warningObjects
}

module.exports = processWarningThresholds
29 changes: 16 additions & 13 deletions server/models/views/station.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ const Station = require('./station-data')
const Forecast = require('./station-forecast')
const util = require('../../util')
const tz = 'Europe/London'
const { processImtdThresholds, processThreshold } = require('./lib/process-imtd-thresholds')
const processImtdThresholds = require('./lib/process-imtd-thresholds')
const processThreshold = require('./lib/process-threshold')
const processWarningThresholds = require('./lib/process-warning-thresholds')
const filterImtdThresholds = require('./lib/find-min-threshold')

const bannerIconId3 = 3
Expand Down Expand Up @@ -250,11 +252,21 @@ class ViewModel {
id: 'highest',
value: this.station.porMaxValue,
description: this.station.thresholdPorMaxDate
? 'Water reaches the highest level recorded at this measuring station (recorded on ' + this.station.thresholdPorMaxDate + ')'
? `Water reaches the highest level recorded at this measuring station (${this.station.thresholdPorMaxDate})`
: 'Water reaches the highest level recorded at this measuring station',
shortname: 'Highest level on record'
})
}

if (imtdThresholds?.length > 0) {
const processedWarningThresholds = processWarningThresholds(
imtdThresholds,
this.station.stageDatum,
this.station.subtract,
this.station.post_process)
thresholds.push(...processedWarningThresholds)
}

this.imtdThresholds = imtdThresholds?.length > 0
? filterImtdThresholds(imtdThresholds)
: []
Expand All @@ -263,20 +275,11 @@ class ViewModel {
this.imtdThresholds,
this.station.stageDatum,
this.station.subtract,
this.station.post_process
this.station.post_process,
this.station.percentile5
)
thresholds.push(...processedImtdThresholds)

if (this.station.percentile5) {
// Only push typical range if it has a percentil5
thresholds.push({
id: 'pc5',
value: this.station.percentile5,
description: 'This is the top of the normal range',
shortname: TOP_OF_NORMAL_RANGE
})
}

// Handle chartThreshold: add tidThreshold if a valid tid is present; if not, fallback to 'pc5'; if 'pc5' is unavailable, use 'alertThreshold' with "Top of normal range" description.
// Extract tid from request URL if valid
let tid = null
Expand Down
Loading

0 comments on commit 4da173b

Please sign in to comment.