1- import { sendNewTomorrowAlertMessage } from '../utilities/telegram'
1+ import { AlertDay , sendMeteoAlertMessage } from '../utilities/telegram'
22import { createAlertReport , getAlertReportByNumber } from '../models/alert-report'
3- import { getTomorrowMeteoAlert } from '../services/meteo-alerts'
3+ import { getTodayMeteoAlert , getTomorrowMeteoAlert , MeteoAlert } from '../services/meteo-alerts'
44import { parseMeteoAlert , ParsedMeteoAlert } from '../utilities/meteo-alerts'
5+ import customMoment from '../custom-components/custom-moment'
56import { config } from '../config/config'
67import logger from '../logger'
78
89const log = logger . child ( { task : 'meteo-alerts' } )
910
10- export const runMeteoAlertCheck = async ( ) : Promise < ParsedMeteoAlert | undefined > => {
11- const tomorrowAlert = await getTomorrowMeteoAlert ( )
12-
13- if ( ! tomorrowAlert ) {
14- log . info ( { event : 'no-tomorrow-alert' } , 'No alert published for tomorrow' )
15- return undefined
16- }
11+ // A bulletin covers a multi-day window and carries a FIXED criticality per day, so the same
12+ // document (e.g. allerta075/2026) is green tomorrow yet orange today. De-dup therefore has to key
13+ // on (bulletin, referenced date), not the bulletin number alone. An escalation arrives as a NEW
14+ // bulletin number for the same date, so it re-sends; re-observing the same bulletin does not.
15+ const reportKey = ( id : string , date : string ) : string => `${ id } @${ date } `
1716
18- const parsedAlert = parseMeteoAlert ( tomorrowAlert , config . alert_zone )
17+ const handleDayAlert = async ( raw : MeteoAlert , day : AlertDay , date : string ) : Promise < ParsedMeteoAlert > => {
18+ const parsedAlert = parseMeteoAlert ( raw , config . alert_zone )
19+ const key = reportKey ( parsedAlert . id , date )
1920
20- // We already handled this bulletin: its report row exists. The existence check (instead of an
21- // INSERT ... ON CONFLICT) makes the de-dup independent of any unique constraint on the table.
22- const existing = await getAlertReportByNumber ( parsedAlert . id )
21+ const existing = await getAlertReportByNumber ( key )
2322
2423 if ( existing ) {
25- log . info (
26- { event : 'already-handled' , reportNumber : parsedAlert . id , critic : parsedAlert . isCritic } ,
27- 'Bulletin already handled'
28- )
24+ log . info ( { event : 'already-handled' , reportNumber : key , critic : parsedAlert . isCritic } , 'Bulletin already handled' )
2925 return parsedAlert
3026 }
3127
3228 const report = {
33- report_number : parsedAlert . id ,
29+ report_number : key ,
3430 is_critic : parsedAlert . isCritic ,
3531 estofex_sent : false ,
3632 pretemp_sent : false ,
@@ -43,21 +39,40 @@ export const runMeteoAlertCheck = async (): Promise<ParsedMeteoAlert | undefined
4339 // Non-critical bulletin: just record it (so pretemp/estofex see the latest report); no message.
4440 if ( ! parsedAlert . isCritic ) {
4541 await createAlertReport ( report )
46- log . info ( { event : 'recorded-non-critical' , reportNumber : parsedAlert . id } , 'Recorded non-critical bulletin' )
42+ log . info ( { event : 'recorded-non-critical' , reportNumber : key } , 'Recorded non-critical bulletin' )
4743 return parsedAlert
4844 }
4945
50- // Critical bulletin: send the message FIRST and record it only once the send succeeds. This way
51- // a failed send is logged AND retried on the next tick, instead of being silently de-duplicated
52- // forever (the report row would otherwise mark the bulletin as "seen" even though nothing was
53- // delivered).
46+ // Critical bulletin: send FIRST and record only once the send succeeds, so a failed send is
47+ // retried on the next tick instead of being silently de-duplicated forever.
5448 try {
55- await sendNewTomorrowAlertMessage ( parsedAlert )
49+ await sendMeteoAlertMessage ( parsedAlert , day )
5650 await createAlertReport ( report )
57- log . info ( { event : 'sent' , reportNumber : parsedAlert . id } , 'Meteo alert sent' )
51+ log . info ( { event : 'sent' , reportNumber : key , day } , 'Meteo alert sent' )
5852 } catch ( err ) {
59- log . error ( { err, alertId : parsedAlert . id } , 'Failed to send meteo alert; will retry next tick' )
53+ log . error ( { err, alertId : key } , 'Failed to send meteo alert; will retry next tick' )
6054 }
6155
6256 return parsedAlert
6357}
58+
59+ export const runMeteoAlertCheck = async ( ) : Promise < ParsedMeteoAlert | undefined > => {
60+ const todayDate = customMoment ( ) . format ( 'YYYY-MM-DD' )
61+ const tomorrowDate = customMoment ( ) . add ( 1 , 'day' ) . format ( 'YYYY-MM-DD' )
62+
63+ // Process today first so the tomorrow row is the most recent alert_reports row: pretemp/estofex
64+ // key off getLastAlertReport and are inherently next-day outlooks.
65+ const todayRaw = await getTodayMeteoAlert ( )
66+ const todayAlert = todayRaw ? await handleDayAlert ( todayRaw , 'today' , todayDate ) : undefined
67+ if ( ! todayRaw ) {
68+ log . info ( { event : 'no-today-alert' } , 'No alert published for today' )
69+ }
70+
71+ const tomorrowRaw = await getTomorrowMeteoAlert ( )
72+ const tomorrowAlert = tomorrowRaw ? await handleDayAlert ( tomorrowRaw , 'tomorrow' , tomorrowDate ) : undefined
73+ if ( ! tomorrowRaw ) {
74+ log . info ( { event : 'no-tomorrow-alert' } , 'No alert published for tomorrow' )
75+ }
76+
77+ return tomorrowAlert ?? todayAlert
78+ }
0 commit comments