diff --git a/server/models/views/lib/latest-levels.js b/server/models/views/lib/latest-levels.js index 718d4d49b..37fac6a2a 100644 --- a/server/models/views/lib/latest-levels.js +++ b/server/models/views/lib/latest-levels.js @@ -2,6 +2,19 @@ const { formatElapsedTime } = require('../../../util') const WARNING_THRESHOLD_TYPES = ['FW RES FW', 'FW ACT FW', 'FW ACTCON FW'] +function adjustThresholdValue (value, stageDatum, subtract, postProcess) { + if (postProcess) { + if (stageDatum && stageDatum > 0) { + value -= stageDatum + } else if (stageDatum <= 0 && subtract && subtract > 0) { + value -= subtract + } else { + return parseFloat(value).toFixed(2) + } + } + return parseFloat(value).toFixed(2) +} + function getThresholdsForTargetArea (thresholds) { const filteredThresholds = thresholds.filter(threshold => threshold.status !== 'Closed' && @@ -9,9 +22,19 @@ function getThresholdsForTargetArea (thresholds) { ) const warningThresholds = findPrioritisedThresholds(filteredThresholds, WARNING_THRESHOLD_TYPES) + return warningThresholds.map(threshold => { threshold.formatted_time = formatElapsedTime(threshold.value_timestamp) threshold.isSuspendedOrOffline = threshold.status === 'Suspended' || (threshold.status === 'Active' && threshold.latest_level === null) + + // Use adjustThresholdValue for threshold_value adjustment + threshold.threshold_value = adjustThresholdValue( + threshold.threshold_value, + threshold.stage_datum, + threshold.subtract, + threshold.post_process + ) + return threshold }) } diff --git a/server/models/views/station.js b/server/models/views/station.js index e42d27d06..fb9cfd5d3 100644 --- a/server/models/views/station.js +++ b/server/models/views/station.js @@ -12,9 +12,11 @@ const bannerIconId3 = 3 const outOfDateMax = 5 const dataStartDateTimeDaysToSubtract = 5 +const TOP_OF_NORMAL_RANGE = 'Top of normal range' + class ViewModel { constructor (options) { - const { station, telemetry, forecast, imtdThresholds, impacts, river, warningsAlerts } = options + const { station, telemetry, forecast, imtdThresholds, impacts, river, warningsAlerts, requestUrl } = options this.station = new Station(station) this.station.riverNavigation = river @@ -39,7 +41,6 @@ class ViewModel { const numSevereWarnings = warningsAlertsGroups['3'] ? warningsAlertsGroups['3'].length : 0 // Determine appropriate warning/alert text for banner - this.banner = numAlerts || numWarnings || numSevereWarnings switch (numAlerts) { @@ -230,12 +231,12 @@ class ViewModel { } this.metaDescription = `Check the latest recorded ${stationType.toLowerCase()} level and recent 5-day trend at ${stationLocation}` - // Thresholds + // Array to hold thresholds let thresholds = [] + // Check if recent value exists and add it to thresholds if (this.station.recentValue && !this.station.recentValue.err) { const tVal = this.station.type !== 'c' && this.station.recentValue._ <= 0 ? 0 : this.station.recentValue._.toFixed(2) - thresholds.push({ id: 'latest', value: tVal, @@ -243,6 +244,7 @@ class ViewModel { shortname: '' }) } + // Add the highest level threshold if available if (this.station.porMaxValue) { thresholds.push({ id: 'highest', @@ -253,7 +255,6 @@ class ViewModel { shortname: 'Highest level on record' }) } - this.imtdThresholds = imtdThresholds?.length > 0 ? filterImtdThresholds(imtdThresholds) : [] @@ -265,9 +266,33 @@ class ViewModel { 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 + try { + tid = requestUrl?.startsWith('http') ? new URL(requestUrl).searchParams.get('tid') : null + } catch (e) { + console.error('Invalid request URL:', e) + } + + // Retrieve the applicable threshold for chartThreshold + const chartThreshold = [getThresholdByThresholdId(tid, imtdThresholds, thresholds)].filter(Boolean) + + // Set chartThreshold property + this.chartThreshold = chartThreshold + // Add impacts if (impacts.length > 0) { this.station.hasImpacts = true @@ -354,7 +379,6 @@ class ViewModel { this.zoom = 14 // Forecast Data Calculations - let forecastData if (isForecast) { this.isFfoi = isForecast @@ -424,4 +448,28 @@ function telemetryForecastBuilder (telemetryRawData, forecastRawData, stationTyp } } +// Function to retrieve a threshold by tid or fall back to 'pc5' or 'alertThreshold' +const getThresholdByThresholdId = (tid, imtdThresholds, thresholds) => { + // Check if a threshold exists based on tid + const tidThreshold = tid && imtdThresholds?.find(thresh => thresh.station_threshold_id === tid) + if (tidThreshold) { + return { + id: tidThreshold.station_threshold_id, + value: Number(tidThreshold.value).toFixed(2), + description: `${tidThreshold.value}m ${tidThreshold.ta_name || ''}`, + shortname: tidThreshold.ta_name || 'Target Area Threshold' + } + } + + // Fallback to 'pc5' if present, else look for 'alertThreshold' + const pc5Threshold = thresholds.find(t => t.id === 'pc5') + if (pc5Threshold) { + return pc5Threshold + } + + // Fallback to 'alertThreshold' if description includes 'Top of normal range' + const alertThreshold = thresholds.find(t => t.id === 'alertThreshold' && t.description.includes(TOP_OF_NORMAL_RANGE)) + return alertThreshold ? { ...alertThreshold, shortname: TOP_OF_NORMAL_RANGE } : null +} + module.exports = ViewModel diff --git a/server/routes/station.js b/server/routes/station.js index 23664da2e..0162af6a5 100644 --- a/server/routes/station.js +++ b/server/routes/station.js @@ -55,6 +55,9 @@ module.exports = { request.server.methods.flood.getRiverStationByStationId(id, direction) ]) + // const requestUrl = request.url.href + const requestUrl = request.url.toString() + if (station.status === 'Closed') { const river = [] const model = new ViewModel({ station, telemetry, imtdThresholds, impacts, river, warningsAlerts }) @@ -68,11 +71,11 @@ module.exports = { // Forecast station const values = await request.server.methods.flood.getStationForecastData(station.wiski_id) const forecast = { forecastFlag, values } - const model = new ViewModel({ station, telemetry, forecast, imtdThresholds, impacts, river, warningsAlerts }) + const model = new ViewModel({ station, telemetry, forecast, imtdThresholds, impacts, river, warningsAlerts, requestUrl }) return h.view('station', { model }) } else { // Non-forecast Station - const model = new ViewModel({ station, telemetry, imtdThresholds, impacts, river, warningsAlerts }) + const model = new ViewModel({ station, telemetry, imtdThresholds, impacts, river, warningsAlerts, requestUrl }) return h.view('station', { model }) } }, diff --git a/server/src/js/components/chart.js b/server/src/js/components/chart.js index 7cc43d4ad..54e23d148 100644 --- a/server/src/js/components/chart.js +++ b/server/src/js/components/chart.js @@ -776,24 +776,25 @@ function LineChart (containerId, stationId, data, options = {}) { .attr('class', 'threshold__line') .attr('aria-hidden', true) .attr('x2', xScale(xExtent[1])).attr('y2', 0) - - // Label - const copy = `${threshold.level}m ${threshold.name}`.match(/[\s\S]{1,35}(?!\S)/g, '$&\n') - const label = thresholdContainer.append('g') - .attr('class', 'threshold-label') + // Construct label text and split into lines of up to 35 characters + const thresholdLabel = `${threshold.level}m ${threshold.name}` + const labelSegments = thresholdLabel.match(/.{1,35}(\s|$)/g).map(line => line.trim()) + const label = thresholdContainer.append('g').attr('class', 'threshold-label') const path = label.append('path') .attr('aria-hidden', true) .attr('class', 'threshold-label__bg') const text = label.append('text') .attr('class', 'threshold-label__text') text.append('tspan').attr('font-size', 0).text('Threshold: ') - copy.map((l, i) => text.append('tspan').attr('x', 10).attr('y', (i + 1) * 22).text(l.trim())) + // Add each line of the split text (up to 35 characters per line) as separate tspans + labelSegments.forEach((line, i) => { + text.append('tspan').attr('x', 10).attr('y', (i + 1) * 22).text(line.trim()) + }) const textWidth = Math.round(text.node().getBBox().width) const textHeight = Math.round(text.node().getBBox().height) - path.attr('d', `m-0.5,-0.5 l${textWidth + 20},0 l0,36 l-${((textWidth + 20) / 2) - 7.5},0 l-7.5,7.5 l-7.5,-7.5 l-${((textWidth + 20) / 2) - 7.5},0 l0,-36 l0,0`) + path.attr('d', `m-0.5,-0.5 l${textWidth + 20},0 l0,${19 + textHeight} l-${((textWidth + 20) / 2) - 7.5},0 l-7.5,7.5 l-7.5,-7.5 l-${((textWidth + 20) / 2) - 7.5},0 l0,-${19 + textHeight} l0,0`) label.attr('transform', `translate(${Math.round(width / 2 - ((textWidth + 20) / 2))}, -${29 + textHeight})`) - - // Remove button + const remove = thresholdContainer.append('a') .attr('role', 'button') .attr('class', 'threshold__remove') @@ -1430,16 +1431,25 @@ if (document.getElementById('bar-chart')) { window.flood.charts.createBarChart('bar-chart', window.flood.model.stationId, window.flood.model.telemetry) } -// Line chart -if (document.getElementById('line-chart')) { - const lineChart = window.flood.charts.createLineChart('line-chart', window.flood.model.id, window.flood.model.telemetry) - const thresholdId = 'threshold-pc5' - const threshold = document.querySelector(`[data-id="${thresholdId}"]`) +// Line chart setup to display thresholds based on chartThreshold configuration ----- +function addThresholdToChart (lineChart, threshold) { if (threshold) { lineChart.addThreshold({ - id: thresholdId, - name: threshold.getAttribute('data-name'), - level: Number(threshold.getAttribute('data-level')) + id: threshold.id, + name: threshold.shortname, + level: Number(threshold.value) }) + } else { + console.error('No valid threshold found to add to the chart.') } } + +// Initialize the line chart and set thresholds using chartThreshold from ViewModel +if (document.getElementById('line-chart')) { + const lineChart = window.flood.charts.createLineChart('line-chart', window.flood.model.id, window.flood.model.telemetry) + + // Iterate through each threshold in chartThreshold and add it to the chart + window.flood.model.chartThreshold.forEach(threshold => { + addThresholdToChart(lineChart, threshold) + }) +} diff --git a/server/src/sass/components/_latest-levels-box.scss b/server/src/sass/components/_latest-levels-box.scss index 98886a2b5..599291e5e 100644 --- a/server/src/sass/components/_latest-levels-box.scss +++ b/server/src/sass/components/_latest-levels-box.scss @@ -13,6 +13,14 @@ text-transform: uppercase; background-color: #1C70B8; color: white; + word-spacing: 0.15em; + + // Add contrast-specific styles for high contrast mode + @media (forced-colors: active) { + background-color: Canvas; // Ensures light background in contrast mode + border: 2px solid ButtonText; // Adds a visible border in high contrast + color: ButtonText; // Uses the system's high-contrast text color + } } &__supplementary { @include govuk-font($size: 16); diff --git a/server/src/sass/objects/_buttons.scss b/server/src/sass/objects/_buttons.scss index 2f350f634..cbbd584f5 100644 --- a/server/src/sass/objects/_buttons.scss +++ b/server/src/sass/objects/_buttons.scss @@ -143,4 +143,4 @@ button.defra-button-secondary { margin-bottom: -4px; top: 0px; } -} +} \ No newline at end of file diff --git a/server/views/partials/latest-levels.html b/server/views/partials/latest-levels.html index 0d9386ef1..c00f58800 100644 --- a/server/views/partials/latest-levels.html +++ b/server/views/partials/latest-levels.html @@ -3,21 +3,22 @@

Latest level{% if model.latestLevels.length > 1 %} {% for warnings in model.latestLevels %}
{% if warnings.isSuspendedOrOffline %} -

Latest Level

+

{{ warnings.formatted_time }}

The {{ warnings.river_name }} level at {{ warnings.agency_name }} is currently unavailable.

{% else %}

{{ warnings.formatted_time }}

The {{ warnings.river_name }} level at {{ warnings.agency_name }} was {{ warnings.latest_level | toFixed(2) }} metres. Property flooding is possible when it goes above {{ warnings.threshold_value | toFixed(2) }} metres. {% if model.latestLevels.length > 1 %} - Monitor the {{ warnings.river_name }} level at {{ warnings.agency_name }} + Monitor the {{ warnings.river_name }} level at {{ warnings.agency_name }} {% endif %}

{% if model.latestLevels.length == 1 %}

- Monitor the latest{% if model.latestLevels.length > 1 %} {{ warnings.river_name }}{% endif %} level at {{ warnings.agency_name }} + Monitor the latest{% if model.latestLevels.length > 1 %} {{ warnings.river_name }}{% endif %} level at {{ warnings.agency_name }}

{% endif %} {% endif %}
{% endfor %} +

{% if model.latestLevels.length > 1 %}These levels{% else %}This level{% endif %} will update automatically

diff --git a/server/views/target-area.html b/server/views/target-area.html index d5a2097b2..039ffdeae 100644 --- a/server/views/target-area.html +++ b/server/views/target-area.html @@ -51,22 +51,35 @@

{{ model.pageTitle }}

+ + +

{% include "partials/sign-up-for-flood-warnings.html" %}

+

- - -
{{ model.situation | safe }}
+ + +

{{ model.areaDescription | safe }}

+ + {% if model.severity %} +

+ + Give feedback on this flood warning information + +

+ {% endif %} + {% if model.latestLevels and model.latestLevels.length > 0 and model.latestLevels.length <= 4 %} {% include "partials/latest-levels.html" %} @@ -75,22 +88,9 @@

{{ model.pageTitle }}

- Find other river and sea levels - -

- - - {% include "partials/sign-up-for-flood-warnings.html" %} - - - {% if model.severity %} -

- Could this information be better? - - Tell us how to improve it. + Find a river, sea, groundwater or rainfall level in this area

- {% endif %} {% include "partials/context-footer.html" %} @@ -111,7 +111,7 @@

{{ model.pageTitle }}

window.flood = {} window.flood.model = {{ model | dump(2) | safe }} window.flood.model.mapButtonText = {{ model.mapButtonText | dump | safe }} - window.flood.model.mapButtonClass = 'defra-button-secondary defra-button-secondary--icon govuk-!-margin-top-4' + window.flood.model.mapButtonClass = 'defra-button-secondary govuk-!-margin-top-4 defra-button-blue-text-black-icon'; window.flood.model.mapLayers = 'mv,ts,tw,ta', window.flood.model.data = { button: 'Target Area:Map view:TA - Map view' diff --git a/test/data/nullTelemetry.json b/test/data/nullTelemetry.json index 25a8f493d..a18c76461 100644 --- a/test/data/nullTelemetry.json +++ b/test/data/nullTelemetry.json @@ -104,5 +104,6 @@ "lon": -0.306835309255374, "lat": 50.8828385361225 }, - "warningsAlerts": [] + "warningsAlerts": [], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationAWSW.json b/test/data/stationAWSW.json index c7a43e4a5..0f774b567 100644 --- a/test/data/stationAWSW.json +++ b/test/data/stationAWSW.json @@ -124,5 +124,6 @@ "severity":"Flood warning", "geometry":"{\"type\":\"Point\",\"coordinates\":[-3.03674273776571,53.8811798090409]}" } - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationActiveAlert.json b/test/data/stationActiveAlert.json index 62d364b93..c554d0f6b 100644 --- a/test/data/stationActiveAlert.json +++ b/test/data/stationActiveAlert.json @@ -97,5 +97,6 @@ "severity_value":1, "severity":"Flood alert", "geometry":"{\"type\":\"Point\",\"coordinates\":[-0.992084272973095,51.1269040605477]}"} - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationActiveWarning.json b/test/data/stationActiveWarning.json index c127df223..15d5117f1 100644 --- a/test/data/stationActiveWarning.json +++ b/test/data/stationActiveWarning.json @@ -97,5 +97,6 @@ "severity_value":2, "severity":"Flood warning", "geometry":"{\"type\":\"Point\",\"coordinates\":[-3.03674273776571,53.8811798090409]}"} - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationCoastal.json b/test/data/stationCoastal.json index 58a8c9efa..9bd9ba3c5 100644 --- a/test/data/stationCoastal.json +++ b/test/data/stationCoastal.json @@ -2482,5 +2482,6 @@ } ], "impacts":[], - "warningsAlerts":[] + "warningsAlerts":[], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationForecastData.json b/test/data/stationForecastData.json index e753acc45..bdcab2153 100644 --- a/test/data/stationForecastData.json +++ b/test/data/stationForecastData.json @@ -356,5 +356,6 @@ "value": "4.3", "threshold_type": "FW ACT FW" } - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationGroudwater.json b/test/data/stationGroudwater.json index 17c45da03..238792002 100644 --- a/test/data/stationGroudwater.json +++ b/test/data/stationGroudwater.json @@ -83,5 +83,6 @@ "centroid":"0101000020E6100000893AA31F375496BFB3A4EA31CFAB4940", "lon":-0.0218056309757295, "lat":51.3422605891914}, - "warningsAlerts":[] + "warningsAlerts":[], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationMultipleAW.json b/test/data/stationMultipleAW.json index 4d2bf3df3..b23dfab15 100644 --- a/test/data/stationMultipleAW.json +++ b/test/data/stationMultipleAW.json @@ -163,5 +163,6 @@ "severity":"Flood warning", "geometry":"{\"type\":\"Point\",\"coordinates\":[-3.03674273776571,53.8811798090409]}" } - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationRiver.json b/test/data/stationRiver.json index 01c9fd90b..7b8a23973 100644 --- a/test/data/stationRiver.json +++ b/test/data/stationRiver.json @@ -141,5 +141,6 @@ "value": "4.3", "threshold_type": "FW RES FW" } - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationRiverACTCON.json b/test/data/stationRiverACTCON.json index b5abb6ffa..e212df01b 100644 --- a/test/data/stationRiverACTCON.json +++ b/test/data/stationRiverACTCON.json @@ -105,5 +105,6 @@ "value": "3.88", "threshold_type": "FW ACTCON FW" } - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationRiverSpike.json b/test/data/stationRiverSpike.json index 8ea93fc60..c4e3a1b00 100644 --- a/test/data/stationRiverSpike.json +++ b/test/data/stationRiverSpike.json @@ -2474,5 +2474,6 @@ "lon":-0.306835309255374, "lat":50.8828385361225 }, - "warningsAlerts":[] + "warningsAlerts":[], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationSevereWarning.json b/test/data/stationSevereWarning.json index 475ebe29a..12e4edd53 100644 --- a/test/data/stationSevereWarning.json +++ b/test/data/stationSevereWarning.json @@ -97,5 +97,6 @@ "severity_value":3, "severity":"Severe flood warning", "geometry":"{\"type\":\"Point\",\"coordinates\":[-3.03674273776571,53.8811798090409]}"} - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/taThresholdsData.json b/test/data/taThresholdsData.json index e3ebf315b..8fa9583cb 100644 --- a/test/data/taThresholdsData.json +++ b/test/data/taThresholdsData.json @@ -202,6 +202,7 @@ "rloi_id": 7174, "station_threshold_id": 123459, "river_name": "River Pinn", + "station_threshold_id": "1747544", "agency_name": "Eastcote Road", "status": "Active", "iswales": false, @@ -216,6 +217,7 @@ "station_threshold_id": 123459, "river_name": "River Pinn", "agency_name": "Eastcote Road", + "station_threshold_id": "1747544", "status": "Active", "iswales": false, "latest_level": "0.352", @@ -228,6 +230,7 @@ "rloi_id": 7173, "station_threshold_id": 123465, "river_name": "River Pinn", + "station_threshold_id": "1747543", "agency_name": "Avenue Road", "status": "Active", "iswales": false, @@ -241,6 +244,7 @@ "rloi_id": 7173, "station_threshold_id": 123465, "river_name": "River Pinn", + "station_threshold_id": "1747543", "agency_name": "Avenue Road", "status": "Active", "iswales": false, @@ -254,6 +258,7 @@ "rloi_id": 7201, "station_threshold_id": 123457, "river_name": "River Pinn", + "station_threshold_id": "1747541", "agency_name": "Moss Close", "status": "Active", "iswales": false, @@ -267,6 +272,7 @@ "rloi_id": 7201, "station_threshold_id": 123457, "river_name": "River Pinn", + "station_threshold_id": "1747541", "agency_name": "Moss Close", "status": "Active", "iswales": false, diff --git a/test/data/toggleTipBelowZeroStation.json b/test/data/toggleTipBelowZeroStation.json index 2f3adb136..64c8deb57 100644 --- a/test/data/toggleTipBelowZeroStation.json +++ b/test/data/toggleTipBelowZeroStation.json @@ -84,5 +84,6 @@ "lon":-0.306835309255374, "lat":50.8828385361225 }, - "warningsAlerts":[] + "warningsAlerts":[], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/toggleTipRiverBedStation.json b/test/data/toggleTipRiverBedStation.json index b68a9c281..90a936d04 100644 --- a/test/data/toggleTipRiverBedStation.json +++ b/test/data/toggleTipRiverBedStation.json @@ -84,5 +84,6 @@ "lon":-0.306835309255374, "lat":50.8828385361225 }, - "warningsAlerts":[] + "warningsAlerts":[], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/toggleTipSeaLevelStation.json b/test/data/toggleTipSeaLevelStation.json index 13087fc14..93540dc71 100644 --- a/test/data/toggleTipSeaLevelStation.json +++ b/test/data/toggleTipSeaLevelStation.json @@ -84,5 +84,6 @@ "lon":-0.306835309255374, "lat":50.8828385361225 }, - "warningsAlerts":[] + "warningsAlerts":[], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/models/lib/latest-levels.js b/test/models/lib/latest-levels.js index 414d7b52e..517e30a11 100644 --- a/test/models/lib/latest-levels.js +++ b/test/models/lib/latest-levels.js @@ -107,43 +107,60 @@ lab.experiment('getThresholdsForTargetArea', () => { expect(result).to.have.length(0) }) - lab.test('should prioritize the first type in the WARNING_THRESHOLD_TYPES array when all thresholds are of the same type', () => { + lab.test('should adjust threshold_value using stageDatum if postProcess is true and stageDatum > 0', () => { const thresholds = [ - { rloi_id: 1, threshold_type: 'FW RES FW', value_timestamp: '2024-08-12T11:45:00.000Z' }, - { rloi_id: 2, threshold_type: 'FW RES FW', value_timestamp: '2024-08-12T12:45:00.000Z' } + { + rloi_id: 1, + threshold_type: 'FW RES FW', + value_timestamp: '2024-08-12T11:45:00.000Z', + threshold_value: '5.00', + stage_datum: 2.5, + subtract: 1.0, + post_process: true + } ] const result = getThresholdsForTargetArea(thresholds) - expect(result).to.have.length(2) - expect(result[0].threshold_type).to.equal('FW RES FW') - expect(result[0].formatted_time).to.equal('More than 1 hour ago') - expect(result[1].formatted_time).to.equal('0 minutes ago') + expect(result).to.have.length(1) + expect(result[0].threshold_value).to.equal('2.50') // 5.00 - 2.5 }) - lab.test('should correctly handle multiple thresholds with the same RLOI ID and type', () => { + lab.test('should adjust threshold_value using subtract if postProcess is true, stageDatum <= 0, and subtract > 0', () => { const thresholds = [ - { rloi_id: 1, threshold_type: 'FW RES FW', value_timestamp: '2024-08-12T11:45:00.000Z' }, - { rloi_id: 1, threshold_type: 'FW RES FW', value_timestamp: '2024-08-12T12:45:00.000Z' } + { + rloi_id: 2, + threshold_type: 'FW ACT FW', + value_timestamp: '2024-08-12T10:45:00.000Z', + threshold_value: '5.00', + stage_datum: 0, + subtract: 1.5, + post_process: true + } ] const result = getThresholdsForTargetArea(thresholds) expect(result).to.have.length(1) - expect(result[0].threshold_type).to.equal('FW RES FW') - expect(result[0].formatted_time).to.equal('More than 1 hour ago') + expect(result[0].threshold_value).to.equal('3.50') // 5.00 - 1.5 }) - lab.test('should ignore invalid thresholds and process only valid ones', () => { + lab.test('should not adjust threshold_value if postProcess is false', () => { const thresholds = [ - { rloi_id: 1, threshold_type: 'FW RES FW', value_timestamp: '2024-08-12T11:45:00.000Z' }, - { rloi_id: 2, threshold_type: 'INVALID TYPE', value_timestamp: '2024-08-12T12:45:00.000Z' } + { + rloi_id: 3, + threshold_type: 'FW ACTCON FW', + value_timestamp: '2024-08-12T11:45:00.000Z', + threshold_value: '4.00', + stage_datum: 1.0, + subtract: 1.5, + post_process: false + } ] const result = getThresholdsForTargetArea(thresholds) expect(result).to.have.length(1) - expect(result[0].threshold_type).to.equal('FW RES FW') - expect(result[0].formatted_time).to.equal('More than 1 hour ago') + expect(result[0].threshold_value).to.equal('4.00') // No adjustment }) }) diff --git a/test/models/station.js b/test/models/station.js index d373916f4..0d85f8888 100644 --- a/test/models/station.js +++ b/test/models/station.js @@ -64,7 +64,6 @@ lab.experiment('Station model test', () => { const viewModel = new ViewModel(stationData) const Result = viewModel - Code.expect(Result.thresholds[1].values).to.equal([ { id: 'warningThreshold', @@ -302,4 +301,63 @@ lab.experiment('Station model test', () => { Code.expect(Result.telemetryRefined.observed.length).to.equal(4) }) + lab.test('Test station viewModel one warning in force', async () => { + const stationData = data.stationActiveWarning + + const viewModel = new ViewModel(stationData) + + const Result = viewModel + + Code.expect(Result.station.id).to.equal(1001) + Code.expect(Result.banner).to.equal(1) + Code.expect(Result.severityLevel).to.equal('warning') + Code.expect(Result.warningsBanner).to.equal('Flood warning for Coast from Fleetwood to Blackpool') + Code.expect(Result.warningsLink).to.equal('/target-area/012WACFB') + }) + + lab.test('Test station viewModel one alert in force', async () => { + const stationData = data.stationActiveAlert + + const viewModel = new ViewModel(stationData) + + const Result = viewModel + + Code.expect(Result.station.id).to.equal(1001) + Code.expect(Result.banner).to.equal(1) + Code.expect(Result.severityLevel).to.equal('alert') + Code.expect(Result.alertsBanner).to.equal('There is a flood alert within 5 miles of this measuring station') + Code.expect(Result.alertsLink).to.equal('/target-area/061FAG30Alton') + }) + + lab.test('Test station viewModel one Severe Warning in force', async () => { + const stationData = data.stationSevereWarning + + const viewModel = new ViewModel(stationData) + + const Result = viewModel + + Code.expect(Result.station.id).to.equal(1001) + Code.expect(Result.banner).to.equal(1) + Code.expect(Result.severityLevel).to.equal('severe') + Code.expect(Result.severeBanner).to.equal('Severe flood warning for Coast from Fleetwood to Blackpool') + Code.expect(Result.severeLink).to.equal('/target-area/012WACFB') + }) + + lab.test('Test station viewModel multiple Warnings and Alerts in force', async () => { + const stationData = data.stationMultipleAW + + const viewModel = new ViewModel(stationData) + + const Result = viewModel + + Code.expect(Result.station.id).to.equal(1001) + Code.expect(Result.banner).to.equal(2) + Code.expect(Result.severityLevel).to.equal('severe') + Code.expect(Result.severeBanner).to.equal('There are severe flood warnings within 5 miles of this measuring station') + Code.expect(Result.severeLink).to.equal('/alerts-and-warnings?station=1001#severe') + Code.expect(Result.alertsBanner).to.equal('There are flood alerts within 5 miles of this measuring station') + Code.expect(Result.alertsLink).to.equal('/alerts-and-warnings?station=1001#alerts') + Code.expect(Result.warningsBanner).to.equal('There are flood warnings within 5 miles of this measuring station') + Code.expect(Result.warningsLink).to.equal('/alerts-and-warnings?station=1001#warnings') + }) }) diff --git a/test/routes/target-area-2.js b/test/routes/target-area-2.js index 7dee2aa81..b217bfce9 100644 --- a/test/routes/target-area-2.js +++ b/test/routes/target-area-2.js @@ -147,7 +147,7 @@ describe('target-area route', () => { expect(response.statusCode).to.equal(200) const root = parse(response.payload) linkChecker(root.querySelectorAll('a'), - 'Find other river and sea levels', + 'Find a river, sea, groundwater or rainfall level in this area', `/river-and-sea-levels/target-area/${AREA_CODE}` ) }) @@ -175,7 +175,7 @@ describe('target-area route', () => { expect(response.statusCode).to.equal(200) const root = parse(response.payload) linkChecker(root.querySelectorAll('a'), - 'Find other river and sea levels', + 'Find a river, sea, groundwater or rainfall level in this area', `/river-and-sea-levels/target-area/${AREA_CODE}` ) }) diff --git a/test/routes/target-area.js b/test/routes/target-area.js index 04ab378a2..6e5a09ca8 100644 --- a/test/routes/target-area.js +++ b/test/routes/target-area.js @@ -134,7 +134,7 @@ lab.experiment('Target-area tests', () => { // Latest level (single threshold) Code.expect(response.payload).to.contain('

Latest level

') Code.expect(response.payload).to.contain('

The River Derwent level at Derby St Marys was 0.54 metres. Property flooding is possible when it goes above 3.30 metres.') - Code.expect(response.payload).to.contain('Monitor the latest level at Derby St Marys') + Code.expect(response.payload).to.contain('Monitor the latest level at Derby St Marys') Code.expect(response.payload).to.match(/

\s*Flooding is possible - \s*be prepared\s*<\/a><\/strong>\s*<\/div>/) Code.expect(response.payload).to.contain('

30 minutes ago

') @@ -189,12 +189,13 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.match(/
\s*Flooding is expected - \s*act now\s*<\/a><\/strong>\s*<\/div>/) // latests levels (multi) + // console.log('DEFRA Live Elements:', response.payload.match(/
Latest levels') Code.expect(response.payload).to.contain('

The River Pinn level at Eastcote Road was 0.35 metres. Property flooding is possible when it goes above 1.40 metres.') Code.expect(response.payload).to.contain('

The River Pinn level at Avenue Road was 0.18 metres. Property flooding is possible when it goes above 1.46 metres.') - Code.expect(response.payload).to.contain('Monitor the River Pinn level at Avenue Road') + Code.expect(response.payload).to.contain('Monitor the River Pinn level at Avenue Road') Code.expect(response.payload).to.contain('

The River Pinn level at Moss Close was 0.13 metres. Property flooding is possible when it goes above 1.15 metres.') - Code.expect(response.payload).to.contain('Monitor the River Pinn level at Moss Close') + Code.expect(response.payload).to.contain('Monitor the River Pinn level at Moss Close') }) lab.test('Check flood severity banner link for Flood warning', async () => { const floodService = require('../../server/services/flood') @@ -286,7 +287,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.statusCode).to.equal(200) const root = parse(response.payload) - Code.expect(response.payload).to.contain('Find other river and sea levels') + Code.expect(response.payload).to.contain('Find a river, sea, groundwater or rainfall level in this area') Code.expect(response.payload).to.contain('') const relatedContentLinks = root.querySelectorAll('.defra-related-items a') @@ -357,7 +358,7 @@ lab.experiment('Target-area tests', () => { // context footer check validateFooterPresent(response) Code.expect(response.payload).to.contain('Severe flood warning for Upper River Derwent, Stonethwaite Beck and Derwent Water') - Code.expect(response.payload).to.contain('Find other river and sea levels') + Code.expect(response.payload).to.contain('Find a river, sea, groundwater or rainfall level in this area') const anchorFound = root.querySelectorAll('a').some(a => a.attributes.href === '/river-and-sea-levels/target-area/011WAFDW' @@ -562,6 +563,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.contain('

Latest level

') Code.expect(response.payload).to.contain('

The River Pinn level at Avenue Road is currently unavailable.

') + Code.expect(response.payload).to.contain('

This level will update automatically

') }) lab.test('Displays latest level for a single suspended station', async () => { @@ -610,6 +612,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.contain('

Latest level

') Code.expect(response.payload).to.contain('

The River Pinn level at Moss Close is currently unavailable.

') + Code.expect(response.payload).to.contain('

This level will update automatically

') }) lab.test('Displays multiple levels with one active but offline, one normal, and one Welsh station with no values', async () => { @@ -708,6 +711,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.contain('

Latest levels

') Code.expect(response.payload).to.contain('

The River Pinn level at Eastcote Road was 0.35 metres. Property flooding is possible when it goes above 1.40 metres.') Code.expect(response.payload).to.contain('

The River Pinn level at Avenue Road is currently unavailable.

') + Code.expect(response.payload).to.contain('

These levels will update automatically

') }) lab.test('Displays multiple levels with one Closed and one normal station', async () => { @@ -756,6 +760,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.contain('

Latest level

') Code.expect(response.payload).to.contain('

The River Pinn level at Eastcote Road was 0.35 metres. Property flooding is possible when it goes above 1.40 metres.') + Code.expect(response.payload).to.contain('

This level will update automatically

') }) lab.test('Displays multiple levels with one normal, one active but offline, and one Welsh station with no values', async () => { @@ -806,6 +811,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.contain('

The River Pinn level at Eastcote Road was 0.35 metres. Property flooding is possible when it goes above 1.40 metres.') Code.expect(response.payload).to.contain('

The River Pinn level at Avenue Road is currently unavailable.

') Code.expect(response.payload).to.not.contain('

The River Welsh station level is currently unavailable.

') + Code.expect(response.payload).to.contain('

These levels will update automatically

') }) lab.test('Displays multiple levels with one normal, one suspended, and one Closed station', async () => { @@ -856,6 +862,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.contain('

The River Pinn level at Eastcote Road was 0.35 metres. Property flooding is possible when it goes above 1.40 metres.') Code.expect(response.payload).to.contain('

The River Pinn level at Avenue Road is currently unavailable.

') Code.expect(response.payload).to.not.contain('

The River Test level at Test Road is currently unavailable.

') + Code.expect(response.payload).to.contain('

These levels will update automatically

') }) lab.test('Does not display levels if all stations are Closed or Welsh with no values', async () => { @@ -950,5 +957,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.contain('

Latest level

') Code.expect(response.payload).to.contain('

The River Welsh level at Welsh Station was 0.35 metres. Property flooding is possible when it goes above 1.40 metres.\n \n

') + Code.expect(response.payload).to.contain('
Monitor the latest level at Welsh Station (Natural Resources Wales)') + Code.expect(response.payload).to.contain('

This level will update automatically

') }) })