Skip to content

Commit

Permalink
MoreCast - Grass curing column (#3374)
Browse files Browse the repository at this point in the history
- Adds grass curing column to Forecast Summary tab
- stores grass curing in wps database
- default persists grass curing on load, propagates grass curing forward on manual change
- makes GC nullable in the DataGrid
- closes #3139
  • Loading branch information
brettedw authored Feb 6, 2024
1 parent 026f1e9 commit 6b7a5d5
Show file tree
Hide file tree
Showing 18 changed files with 177 additions and 67 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""adds grass_curing to morecast_forecast
Revision ID: 5845f568a975
Revises: 403586c146ae
Create Date: 2024-02-02 08:16:57.605162
"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = '5845f568a975'
down_revision = '403586c146ae'
branch_labels = None
depends_on = None


def upgrade():
# ### start Alembic commands ###
op.add_column('morecast_forecast', sa.Column('grass_curing', sa.Float(), nullable=True))
op.create_index(op.f('ix_morecast_forecast_grass_curing'), 'morecast_forecast', ['grass_curing'], unique=False)
# ### end Alembic commands ###


def downgrade():
# ### start Alembic commands ###
op.drop_index(op.f('ix_morecast_forecast_grass_curing'), table_name='morecast_forecast')
op.drop_column('morecast_forecast', 'grass_curing')
# ### end Alembic commands ###
1 change: 1 addition & 0 deletions api/app/db/models/morecast_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class MorecastForecastRecord(Base):
precip = Column(Float, nullable=False, index=True)
wind_speed = Column(Float, nullable=False, index=True)
wind_direction = Column(Integer, nullable=True, index=True)
grass_curing = Column(Float, nullable=True, index=True)
create_timestamp = Column(TZTimeStamp, nullable=False, index=True)
create_user = Column(String, nullable=False)
update_timestamp = Column(TZTimeStamp, nullable=False, index=True)
Expand Down
1 change: 1 addition & 0 deletions api/app/morecast_v2/forecasts.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def get_forecasts(db_session: Session, start_time: Optional[datetime], end_time:
precip=forecast.precip,
wind_speed=forecast.wind_speed,
wind_direction=forecast.wind_direction,
grass_curing=forecast.grass_curing,
update_timestamp=int(forecast.update_timestamp.timestamp())) for forecast in result]
return forecasts

Expand Down
3 changes: 3 additions & 0 deletions api/app/routers/morecast_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ async def get_forecasts_by_date_range(start_date: date, end_date: date, request:
precip=forecast.precip,
wind_speed=forecast.wind_speed,
wind_direction=forecast.wind_direction,
grass_curing=forecast.grass_curing,
update_timestamp=forecast.update_timestamp.timestamp()) for forecast in result]
return MorecastForecastResponse(forecasts=morecast_forecast_outputs)

Expand All @@ -101,6 +102,7 @@ async def save_forecasts(forecasts: MoreCastForecastRequest,
precip=forecast.precip,
wind_speed=forecast.wind_speed,
wind_direction=forecast.wind_direction,
grass_curing=forecast.grass_curing,
create_user=username,
create_timestamp=now,
update_user=username,
Expand All @@ -127,6 +129,7 @@ async def save_forecasts(forecasts: MoreCastForecastRequest,
precip=forecast.precip,
wind_speed=forecast.wind_speed,
wind_direction=forecast.wind_direction,
grass_curing=forecast.grass_curing,
update_timestamp=int(now.timestamp() * 1000)) for forecast in forecasts_list]
return MorecastForecastResponse(forecasts=morecast_forecast_outputs)

Expand Down
2 changes: 1 addition & 1 deletion web/src/api/moreCast2API.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('moreCast2API', () => {
temp: { choice: 'FORECAST', value: 0 },
windDirection: { choice: 'FORECAST', value: 0 },
windSpeed: { choice: 'FORECAST', value: 0 },
grassCuring: 0
grassCuring: { choice: 'FORECAST', value: 0 }
})
it('should marshall forecast records correctly', async () => {
const res = marshalMoreCast2ForecastRecords([
Expand Down
6 changes: 3 additions & 3 deletions web/src/api/moreCast2API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export const marshalMoreCast2ForecastRecords = (forecasts: MoreCast2ForecastRow[
temp: forecast.temp.value,
wind_direction: forecast.windDirection.value,
wind_speed: forecast.windSpeed.value,
grass_curing: forecast.grassCuring
grass_curing: forecast.grassCuring.value
}
})
return forecastRecords
Expand Down Expand Up @@ -277,8 +277,8 @@ export const mapMoreCast2RowsToIndeterminates = (rows: MoreCast2Row[]): WeatherI
initial_spread_index: isForecast ? r.isiCalcForecast!.value : r.isiCalcActual,
build_up_index: isForecast ? r.buiCalcForecast!.value : r.buiCalcActual,
fire_weather_index: isForecast ? r.fwiCalcForecast!.value : r.fwiCalcActual,
danger_rating: isForecast ? null : r.rhActual,
grass_curing: r.grassCuring
danger_rating: isForecast ? null : r.dgrCalcActual,
grass_curing: isForecast ? r.grassCuringForecast!.value : r.grassCuringActual
}
})
return mappedIndeterminates
Expand Down
16 changes: 10 additions & 6 deletions web/src/features/moreCast2/components/ColumnDefBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,11 @@ export class ColumnDefBuilder implements ColDefGenerator, ForecastColDefGenerato
}

public generateForecastColDef = (headerName?: string) => {
const isCalcField = this.field.includes('Calc')

return this.generateForecastColDefWith(
`${this.field}${WeatherDeterminate.FORECAST}`,
headerName ? headerName : this.headerName,
this.precision,
isCalcField ? DEFAULT_COLUMN_WIDTH : DEFAULT_FORECAST_COLUMN_WIDTH
DEFAULT_FORECAST_COLUMN_WIDTH
)
}

Expand Down Expand Up @@ -103,7 +101,11 @@ export class ColumnDefBuilder implements ColDefGenerator, ForecastColDefGenerato
}

public generateForecastColDefWith = (field: string, headerName: string, precision: number, width?: number) => {
const isGrassField = field.includes('grass')
const isCalcField = field.includes('Calc')
if (isGrassField || isCalcField) {
width = DEFAULT_COLUMN_WIDTH
}
return {
field: field,
disableColumnMenu: true,
Expand All @@ -112,14 +114,16 @@ export class ColumnDefBuilder implements ColDefGenerator, ForecastColDefGenerato
headerName: headerName,
sortable: false,
type: 'number',
width: width || 120,
width: width ?? DEFAULT_FORECAST_COLUMN_WIDTH,
renderHeader: (params: GridColumnHeaderParams) => {
return isCalcField
return isCalcField || isGrassField
? this.gridComponentRenderer.renderHeaderWith(params)
: this.gridComponentRenderer.renderForecastHeaderWith(params)
},
renderCell: (params: Pick<GridRenderCellParams, 'row' | 'formattedValue'>) => {
return this.gridComponentRenderer.renderForecastCellWith(params, field)
return isCalcField
? this.gridComponentRenderer.renderCellWith(params)
: this.gridComponentRenderer.renderForecastCellWith(params, field)
},
valueFormatter: (params: Pick<GridValueFormatterParams, 'value'>) => {
return this.valueFormatterWith(params, precision)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { storeUserEditedRows, getSimulatedIndices } from 'features/moreCast2/sli
import { AppDispatch } from 'app/store'
import { useDispatch } from 'react-redux'
import { filterRowsForSimulationFromEdited } from 'features/moreCast2/rowFilters'
import { fillStationGrassCuringForward } from 'features/moreCast2/util'

const PREFIX = 'ForecastSummaryDataGrid'

Expand Down Expand Up @@ -48,15 +49,17 @@ const ForecastSummaryDataGrid = ({
handleClose
}: ForecastSummaryDataGridProps) => {
const dispatch: AppDispatch = useDispatch()
const processRowUpdate = async (editedRow: MoreCast2Row) => {
dispatch(storeUserEditedRows([editedRow]))

const rowsForSimulation = filterRowsForSimulationFromEdited(editedRow, rows)
const processRowUpdate = async (newRow: MoreCast2Row) => {
const filledRows = fillStationGrassCuringForward(newRow, rows)
dispatch(storeUserEditedRows(filledRows))

const rowsForSimulation = filterRowsForSimulationFromEdited(newRow, filledRows)
if (rowsForSimulation) {
dispatch(getSimulatedIndices(rowsForSimulation))
}

return editedRow
return newRow
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,15 @@ export class GridComponentRenderer {
// We need the prefix to help us grab the correct 'actual' field (eg. tempACTUAL, precipACTUAL, etc.)
const actualField = this.getActualField(field)

const isCalcField = field.includes('Calc')
const isGrassField = field.includes('grass')

const isActual = !isNaN(params.row[actualField])

return (
<TextField
disabled={isActual || isCalcField}
disabled={isActual}
size="small"
label={isCalcField ? '' : createLabel(isActual, params.row[field].choice)}
label={isGrassField ? '' : createLabel(isActual, params.row[field].choice)}
value={params.formattedValue}
></TextField>
)
Expand All @@ -83,7 +84,7 @@ export class GridComponentRenderer {
precision: number
) => {
const oldValue = params.row[field].value
const newValue = Number(params.value)
const newValue = params.value ? Number(params.value) : NaN

if (isNaN(oldValue) && isNaN(newValue)) {
return { ...params.row }
Expand Down
4 changes: 3 additions & 1 deletion web/src/features/moreCast2/components/MoreCast2Column.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export const ffmcField = new IndeterminateField('ffmcCalc', 'FFMC', 'number', 1,
export const dmcField = new IndeterminateField('dmcCalc', 'DMC', 'number', 0, false)
export const dcField = new IndeterminateField('dcCalc', 'DC', 'number', 0, false)
export const dgrField = new IndeterminateField('dgrCalc', 'DGR', 'number', 0, false)
export const gcField = new IndeterminateField('grassCuring', 'GC', 'number', 0, false)

export const MORECAST2_STATION_DATE_FIELDS: ColDefGenerator[] = [
StationForecastField.getInstance(),
Expand All @@ -140,7 +141,8 @@ export const MORECAST2_FORECAST_FIELDS: ForecastColDefGenerator[] = [
rhForecastField,
windDirForecastField,
windSpeedForecastField,
precipForecastField
precipForecastField,
gcField
]

export const MORECAST2_INDEX_FIELDS: ForecastColDefGenerator[] = [
Expand Down
3 changes: 2 additions & 1 deletion web/src/features/moreCast2/components/TabbedDataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ const TabbedDataGrid = ({ morecast2Rows, fromTo, setFromTo }: TabbedDataGridProp
if (
!isEqual(params.colDef.field, 'stationName') &&
!isEqual(params.colDef.field, 'forDate') &&
!params.colDef.field.includes('Calc')
!params.colDef.field.includes('Calc') &&
!params.colDef.field.includes('grass')
) {
setClickedColDef(params.colDef)
setContextMenu(contextMenu === null ? { mouseX: event.clientX, mouseY: event.clientY } : null)
Expand Down
7 changes: 4 additions & 3 deletions web/src/features/moreCast2/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface MoreCast2ForecastRow {
temp: PredictionItem
windDirection: PredictionItem
windSpeed: PredictionItem
grassCuring: number
grassCuring: PredictionItem
}

export interface BaseRow {
Expand Down Expand Up @@ -46,8 +46,9 @@ export interface MoreCast2Row extends BaseRow {
fwiCalcForecast?: PredictionItem
dgrCalcForecast?: PredictionItem

// Grass curing carryover
grassCuring: number
// Grass curing
grassCuringActual: number
grassCuringForecast?: PredictionItem

// Forecast properties
precipForecast?: PredictionItem
Expand Down
1 change: 1 addition & 0 deletions web/src/features/moreCast2/rowFilters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const buildValidForecastRow = (
forecastRow.tempForecast = { choice: choice, value: 2 }
forecastRow.rhForecast = { choice: choice, value: 2 }
forecastRow.windSpeedForecast = { choice: choice, value: 2 }
forecastRow.grassCuringForecast = { choice: choice, value: NaN }
forecastRow.id = id

return forecastRow
Expand Down
16 changes: 10 additions & 6 deletions web/src/features/moreCast2/saveForecast.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const baseRow = {
buiCalcActual: 0,
fwiCalcActual: 0,
dgrCalcActual: 0,
grassCuring: 0
grassCuringActual: NaN
}

const baseRowWithActuals = {
Expand All @@ -76,7 +76,8 @@ const baseRowWithActuals = {
rhActual: 0,
tempActual: 0,
windDirectionActual: 0,
windSpeedActual: 0
windSpeedActual: 0,
grassCuringActual: 0
}

const mockForDate = DateTime.fromISO('2023-02-16T20:00:00+00:00')
Expand All @@ -100,7 +101,8 @@ const buildCompleteForecast = (
rhForecast: { choice: ModelChoice.GDPS, value: 0 },
tempForecast: { choice: ModelChoice.GDPS, value: 0 },
windDirectionForecast: { choice: ModelChoice.GDPS, value: 0 },
windSpeedForecast: { choice: ModelChoice.GDPS, value: 0 }
windSpeedForecast: { choice: ModelChoice.GDPS, value: 0 },
grassCuringForecast: { choice: ModelChoice.NULL, value: 0 }
})

const buildForecastMissingWindDirection = (
Expand All @@ -123,7 +125,7 @@ const buildForecastMissingWindDirection = (
tempForecast: { choice: ModelChoice.GDPS, value: 0 },
windDirectionForecast: { choice: ModelChoice.NULL, value: NaN },
windSpeedForecast: { choice: ModelChoice.GDPS, value: 0 },
grassCuring: 0
grassCuringForecast: { choice: ModelChoice.NULL, value: 0 }
})

const buildInvalidForecast = (
Expand Down Expand Up @@ -162,7 +164,8 @@ const buildNAForecast = (
rhForecast: { choice: ModelChoice.NULL, value: NaN },
tempForecast: { choice: ModelChoice.NULL, value: NaN },
windDirectionForecast: { choice: ModelChoice.NULL, value: NaN },
windSpeedForecast: { choice: ModelChoice.NULL, value: NaN }
windSpeedForecast: { choice: ModelChoice.NULL, value: NaN },
grassCuringForecast: { choice: ModelChoice.NULL, value: NaN }
})

const buildForecastWithActuals = (
Expand All @@ -184,7 +187,8 @@ const buildForecastWithActuals = (
rhForecast: { choice: ModelChoice.GDPS, value: 0 },
tempForecast: { choice: ModelChoice.GDPS, value: 0 },
windDirectionForecast: { choice: ModelChoice.GDPS, value: 0 },
windSpeedForecast: { choice: ModelChoice.GDPS, value: 0 }
windSpeedForecast: { choice: ModelChoice.GDPS, value: 0 },
grassCuringForecast: { choice: ModelChoice.NULL, value: 0 }
})

describe('saveForecasts', () => {
Expand Down
10 changes: 5 additions & 5 deletions web/src/features/moreCast2/saveForecasts.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { ModelChoice } from 'api/moreCast2API'
import { MoreCast2ForecastRow, MoreCast2Row } from 'features/moreCast2/interfaces'
import { fillGrassCuring, validForecastPredicate } from 'features/moreCast2/util'
import { validForecastPredicate } from 'features/moreCast2/util'

// Forecast rows contain all NaN values in their 'actual' fields
export const isForecastRowPredicate = (row: MoreCast2Row) =>
isNaN(row.precipActual) &&
isNaN(row.rhActual) &&
isNaN(row.tempActual) &&
isNaN(row.windDirectionActual) &&
isNaN(row.windSpeedActual)
isNaN(row.windSpeedActual) &&
isNaN(row.grassCuringActual)

export const getForecastRows = (rows: MoreCast2Row[]): MoreCast2Row[] => {
return rows ? rows.filter(isForecastRowPredicate) : []
Expand All @@ -21,8 +22,7 @@ export const isForecastValid = (rows: MoreCast2Row[]) => {
}

export const getRowsToSave = (rows: MoreCast2Row[]): MoreCast2ForecastRow[] => {
const filledRows = fillGrassCuring(rows)
const forecastRows = getForecastRows(filledRows)
const forecastRows = getForecastRows(rows)
const rowsToSave = forecastRows.filter(validForecastPredicate)
return rowsToSave.map(r => ({
id: r.id,
Expand All @@ -34,6 +34,6 @@ export const getRowsToSave = (rows: MoreCast2Row[]): MoreCast2ForecastRow[] => {
temp: r.tempForecast ?? { choice: ModelChoice.NULL, value: NaN },
windDirection: r.windDirectionForecast ?? { choice: ModelChoice.NULL, value: NaN },
windSpeed: r.windSpeedForecast ?? { choice: ModelChoice.NULL, value: NaN },
grassCuring: r.grassCuring
grassCuring: r.grassCuringForecast ?? { choice: ModelChoice.NULL, value: NaN }
}))
}
Loading

0 comments on commit 6b7a5d5

Please sign in to comment.