feat: prioritize warnings for selected province

This commit is contained in:
zv
2026-06-02 15:59:17 +02:00
parent 7b8c26d8f1
commit 22b8969379
9 changed files with 224 additions and 3 deletions

View File

@@ -1,4 +1,5 @@
export const DEFAULT_STATION_NAME = "Warszawa";
export const DEFAULT_STATION_ID = "12375";
export const APP_NAME = "wtr.";
export const APP_TAGLINE = "Pogoda z danych IMGW. Prosto. Pięknie. Aktualnie.";

View File

@@ -135,6 +135,12 @@ const translations = {
"warnings.section": "Komunikaty IMGW",
"warnings.title": "Ostrzeżenia",
"warnings.description": "Aktualne ostrzeżenia meteorologiczne i hydrologiczne publikowane przez IMGW. Szczegóły obszaru i czasu obowiązywania pochodzą bezpośrednio z API.",
"warnings.myProvince": "Moje województwo",
"warnings.myProvinceDescription": "Najpierw pokazujemy komunikaty IMGW dotyczące województwa {province}, zgodnie z lokalizacją wybraną w pogodzie.",
"warnings.myProvinceEmptyTitle": "Brak ostrzeżeń dla Twojego województwa",
"warnings.myProvinceEmptyDescription": "IMGW nie publikuje obecnie aktywnych ostrzeżeń dotyczących województwa {province}.",
"warnings.otherRegions": "Pozostałe regiony",
"warnings.otherRegionsDescription": "Aktywne komunikaty IMGW dla pozostałych obszarów Polski.",
"warnings.error": "Nie udało się pobrać ostrzeżeń meteorologicznych ani hydrologicznych.",
"warnings.emptyTitle": "Brak aktywnych ostrzeżeń",
"warnings.emptyDescription": "IMGW nie publikuje obecnie ostrzeżeń meteorologicznych ani hydrologicznych.",
@@ -296,6 +302,12 @@ const translations = {
"warnings.section": "IMGW notices",
"warnings.title": "Warnings",
"warnings.description": "Current meteorological and hydrological warnings published by IMGW. Area and validity details come directly from the API.",
"warnings.myProvince": "My province",
"warnings.myProvinceDescription": "IMGW notices for the {province} province are shown first, based on the location selected in weather.",
"warnings.myProvinceEmptyTitle": "No warnings for your province",
"warnings.myProvinceEmptyDescription": "IMGW is not currently publishing active warnings for the {province} province.",
"warnings.otherRegions": "Other regions",
"warnings.otherRegionsDescription": "Active IMGW notices for the remaining areas of Poland.",
"warnings.error": "Unable to load meteorological or hydrological warnings.",
"warnings.emptyTitle": "No active warnings",
"warnings.emptyDescription": "IMGW is not currently publishing any meteorological or hydrological warnings.",

133
lib/provinces.ts Normal file
View File

@@ -0,0 +1,133 @@
import type { Language } from "@/lib/i18n";
import type { Province } from "@/types/province";
const provinceByTerytPrefix: Record<string, Province> = {
"02": "dolnośląskie",
"04": "kujawsko-pomorskie",
"06": "lubelskie",
"08": "lubuskie",
"10": "łódzkie",
"12": "małopolskie",
"14": "mazowieckie",
"16": "opolskie",
"18": "podkarpackie",
"20": "podlaskie",
"22": "pomorskie",
"24": "śląskie",
"26": "świętokrzyskie",
"28": "warmińsko-mazurskie",
"30": "wielkopolskie",
"32": "zachodniopomorskie",
};
const provinceByStationId: Record<string, Province> = {
"12100": "zachodniopomorskie",
"12105": "zachodniopomorskie",
"12115": "pomorskie",
"12120": "pomorskie",
"12125": "pomorskie",
"12135": "pomorskie",
"12155": "pomorskie",
"12160": "warmińsko-mazurskie",
"12185": "warmińsko-mazurskie",
"12195": "podlaskie",
"12200": "zachodniopomorskie",
"12205": "zachodniopomorskie",
"12210": "zachodniopomorskie",
"12215": "zachodniopomorskie",
"12230": "wielkopolskie",
"12235": "pomorskie",
"12250": "kujawsko-pomorskie",
"12270": "mazowieckie",
"12272": "warmińsko-mazurskie",
"12280": "warmińsko-mazurskie",
"12285": "mazowieckie",
"12295": "podlaskie",
"12300": "lubuskie",
"12310": "lubuskie",
"12330": "wielkopolskie",
"12345": "wielkopolskie",
"12360": "mazowieckie",
"12375": "mazowieckie",
"12385": "mazowieckie",
"12399": "lubelskie",
"12400": "lubuskie",
"12415": "dolnośląskie",
"12418": "wielkopolskie",
"12424": "dolnośląskie",
"12435": "wielkopolskie",
"12455": "łódzkie",
"12465": "łódzkie",
"12469": "łódzkie",
"12488": "mazowieckie",
"12495": "lubelskie",
"12497": "lubelskie",
"12500": "dolnośląskie",
"12510": "dolnośląskie",
"12520": "dolnośląskie",
"12530": "opolskie",
"12540": "śląskie",
"12550": "śląskie",
"12560": "śląskie",
"12566": "małopolskie",
"12570": "świętokrzyskie",
"12575": "małopolskie",
"12580": "podkarpackie",
"12585": "świętokrzyskie",
"12595": "lubelskie",
"12600": "śląskie",
"12625": "małopolskie",
"12650": "małopolskie",
"12660": "małopolskie",
"12670": "podkarpackie",
"12690": "podkarpackie",
"12695": "podkarpackie",
};
const provinceLabels: Record<Province, Record<Language, string>> = {
"dolnośląskie": { pl: "dolnośląskie", en: "Lower Silesian" },
"kujawsko-pomorskie": { pl: "kujawsko-pomorskie", en: "Kuyavian-Pomeranian" },
"lubelskie": { pl: "lubelskie", en: "Lublin" },
"lubuskie": { pl: "lubuskie", en: "Lubusz" },
"łódzkie": { pl: "łódzkie", en: "Łódź" },
"małopolskie": { pl: "małopolskie", en: "Lesser Poland" },
"mazowieckie": { pl: "mazowieckie", en: "Masovian" },
"opolskie": { pl: "opolskie", en: "Opole" },
"podkarpackie": { pl: "podkarpackie", en: "Subcarpathian" },
"podlaskie": { pl: "podlaskie", en: "Podlaskie" },
"pomorskie": { pl: "pomorskie", en: "Pomeranian" },
"śląskie": { pl: "śląskie", en: "Silesian" },
"świętokrzyskie": { pl: "świętokrzyskie", en: "Świętokrzyskie" },
"warmińsko-mazurskie": { pl: "warmińsko-mazurskie", en: "Warmian-Masurian" },
"wielkopolskie": { pl: "wielkopolskie", en: "Greater Poland" },
"zachodniopomorskie": { pl: "zachodniopomorskie", en: "West Pomeranian" },
};
function simplifyProvinceName(value: string) {
return value
.normalize("NFD")
.replace(/\p{Diacritic}/gu, "")
.toLocaleLowerCase("pl-PL")
.replace(/^wojewodztwo\s+/, "")
.trim();
}
const provinceBySimplifiedName = Object.fromEntries(
Object.keys(provinceLabels).map((province) => [simplifyProvinceName(province), province]),
) as Record<string, Province>;
export function getProvinceFromTeryt(code: string) {
return provinceByTerytPrefix[code.trim().slice(0, 2)] ?? null;
}
export function getProvinceForStation(stationId: string | null) {
return stationId ? provinceByStationId[stationId] ?? null : null;
}
export function normalizeProvinceName(value: string | null | undefined) {
return value ? provinceBySimplifiedName[simplifyProvinceName(value)] ?? null : null;
}
export function formatProvinceName(province: Province, language: Language) {
return provinceLabels[province][language];
}

View File

@@ -9,6 +9,7 @@ import type {
WarningKind,
} from "@/types/imgw";
import { translate, type Language } from "@/lib/i18n";
import { getProvinceFromTeryt, normalizeProvinceName } from "@/lib/provinces";
const locales: Record<Language, string> = { pl: "pl-PL", en: "en-GB" };
@@ -67,10 +68,14 @@ export function normalizeHydroStation(raw: RawHydroStation): HydroStation | null
}
export function normalizeWarning(raw: RawWarning, kind: WarningKind, index: number): WeatherWarning {
const provinces = [...new Set([
...(raw.obszary ?? []).map((area) => normalizeProvinceName(area.wojewodztwo)),
...(raw.teryt ?? []).map(getProvinceFromTeryt),
].filter((province) => province !== null))];
const describedAreas = (raw.obszary ?? [])
.map((area) => area.opis?.trim() || area.wojewodztwo?.trim())
.filter((area): area is string => Boolean(area));
const areas = describedAreas.length ? describedAreas : (raw.teryt ?? []).map((code) => `TERYT ${code}`);
const areas = describedAreas.length ? describedAreas : provinces;
const title = raw.zdarzenie?.trim() || raw.nazwa_zdarzenia?.trim() || "";
return {
id: `${kind}-${raw.id ?? raw.numer ?? index}-${raw.data_od ?? raw.obowiazuje_od ?? "unknown"}`,
@@ -84,6 +89,7 @@ export function normalizeWarning(raw: RawWarning, kind: WarningKind, index: numb
publishedAt: normalizeDate(raw.opublikowano),
probability: toNumber(raw.prawdopodobienstwo),
areas,
provinces,
office: raw.biuro?.trim() || null,
};
}