Skip to content

Commit

Permalink
Merge branch 'integration/latest-levels-app' into feature/FSR-1293
Browse files Browse the repository at this point in the history
  • Loading branch information
Keyurx11 authored Oct 15, 2024
2 parents 6fc7009 + 6d1b4f7 commit e672a11
Show file tree
Hide file tree
Showing 40 changed files with 655 additions and 112 deletions.
23 changes: 23 additions & 0 deletions server/models/views/lib/latest-levels.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,39 @@ 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' &&
!(threshold.iswales && threshold.latest_level === null)
)

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
})
}
Expand Down
1 change: 1 addition & 0 deletions server/models/views/lib/process-imtd-thresholds.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const SEVERE_FLOOD_WARNING_THRESHOLD = 3
function processThreshold (threshold, stationStageDatum, stationSubtract, postProcess) {
if (threshold) {
if (postProcess) {
Expand Down
61 changes: 55 additions & 6 deletions server/models/views/station.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,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
Expand All @@ -40,7 +42,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) {
Expand Down Expand Up @@ -231,19 +232,20 @@ 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,
description: 'Latest level',
shortname: ''
})
}
// Add the highest level threshold if available
if (this.station.porMaxValue) {
thresholds.push({
id: 'highest',
Expand Down Expand Up @@ -271,9 +273,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
Expand Down Expand Up @@ -360,7 +386,6 @@ class ViewModel {
this.zoom = 14

// Forecast Data Calculations

let forecastData
if (isForecast) {
this.isFfoi = isForecast
Expand Down Expand Up @@ -430,4 +455,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
8 changes: 6 additions & 2 deletions server/routes/station.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
handler: async (request, h) => {
const { id } = request.params
let { direction } = request.params
// const thresholdId = request.query.tid?

// Convert human readable url to service parameter
direction = direction === 'downstream' ? 'd' : 'u'
Expand Down Expand Up @@ -54,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 })
Expand All @@ -67,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 })
}
},
Expand Down
49 changes: 30 additions & 19 deletions server/src/js/components/chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -777,23 +777,25 @@ function LineChart (containerId, stationId, data, options = {}) {
.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')
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: ')
// 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')
copy.map((l, i) => text.append('tspan').attr('x', 10).attr('y', (i + 1) * 22).text(l.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`)
label.attr('transform', `translate(${Math.round(width / 2 - ((textWidth + 20) / 2))}, -${29 + textHeight})`)

// Remove button
// 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,${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})`)

const remove = thresholdContainer.append('a')
.attr('role', 'button')
.attr('class', 'threshold__remove')
Expand Down Expand Up @@ -1430,16 +1432,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)
})
}
Loading

0 comments on commit e672a11

Please sign in to comment.