Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FSR-1293 | Latest Levels #829

Merged
merged 29 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3b7922a
added links to flood warnings target areas in thresholds
LeeGordon83 Sep 27, 2024
e1f79cb
altered logic for top of normal range and fixed unit tests
LeeGordon83 Oct 1, 2024
15a2f92
changed chart.js threshold references
LeeGordon83 Oct 2, 2024
0460518
fixed sonar issues
LeeGordon83 Oct 2, 2024
8be10c9
refactored code to reduce complexity for Sonarcloud
LeeGordon83 Oct 3, 2024
b4fc81e
updated short name on thresholds to include name of flood warning area
LeeGordon83 Oct 3, 2024
40ca572
removed incorrect capital letter
LeeGordon83 Oct 8, 2024
33a01cc
CSS fix to handle stations with historical threshold values
LeeGordon83 Oct 9, 2024
f3acbff
added process warning thresholds functions and tests
LeeGordon83 Oct 14, 2024
54c45bd
fixing sonar issues
LeeGordon83 Oct 14, 2024
1625dc0
fixed linting issue
LeeGordon83 Oct 14, 2024
634901c
sonar fix
LeeGordon83 Oct 14, 2024
6fc7009
fixed chart label and latest levels CSS
LeeGordon83 Oct 15, 2024
12e1aba
stopped displaying inactive warnings as thresholds
LeeGordon83 Oct 15, 2024
8a71179
sonar fix
LeeGordon83 Oct 16, 2024
7f3a926
rejig of logic to show top of normal range
LeeGordon83 Oct 16, 2024
b1f76c8
sonar fixes
LeeGordon83 Oct 16, 2024
cba8dc5
altered value on top of normal range
LeeGordon83 Oct 17, 2024
48878ce
sonar fix
LeeGordon83 Oct 17, 2024
f747197
changed top of normal range to 2dp
LeeGordon83 Oct 17, 2024
48c17be
rejig of 2dp fix
LeeGordon83 Oct 17, 2024
1431e6d
altered id of top of normal range thresholds
LeeGordon83 Oct 17, 2024
7d592bc
roll back of _chart-controls.scss
LeeGordon83 Oct 17, 2024
666dc9c
added stage datum calculations to warnings
LeeGordon83 Oct 21, 2024
e015c89
sonar fix
LeeGordon83 Oct 21, 2024
dec2ddc
bring process threshold into it's own class
LeeGordon83 Oct 21, 2024
be14062
removed uneeded export
LeeGordon83 Oct 21, 2024
d3a6411
dev merge in
LeeGordon83 Oct 22, 2024
dacc6a7
linting changes
LeeGordon83 Oct 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading