WeatherTool is a Scala backend, SolidJS frontend, and PostgreSQL database used to inspect Latvian weather observations and prepare map-based newsroom graphics.
Staging exercises the PostgreSQL → API → frontend path with real LVĢMC open-data observations (station temperatures, water temperatures, warnings) as of 2026-08-23. The private FTP station feed and the DMI HARMONIE forecast feed still exist in code but are gated behind `ENABLE_LEGACY_PROVIDER_JOBS` (default off) pending real credentials — see `docs/UPDATE_ROADMAP.md` Phase 7.
- Contains ingestion/parsing code for LVGMC station data: a free/keyless open-data path (`fetch.lvgmc.OpenDataStationService`) that pivots LVĢMC's public `data.gov.lv` station-observation feed into the `weather` table, and the original private FTP feed (`fetch.lvgmc.FetchService`), kept but gated off pending real credentials.
- Fetches LVĢMC open hydrometeorological-warning metadata and polygon coordinates on request (`fetch.warnings.WarningService`), and water temperatures per named zone (`fetch.lvgmc.WaterTemperatureService`) — both free/keyless `data.gov.lv` reads with a short in-memory cache and stale-data fallback if the upstream call fails; neither persists to PostgreSQL.
- Contains HARMONIE discovery, download, GRIB parsing, and rendering support, gated off pending real DMI credentials.
- Starts the safe scheduled ingestion/cleanup tasks (open-data stations, GRIB cleanup) unless `ENABLE_SCHEDULED_JOBS=false`. The legacy FTP station fetch and HARMONIE fetch are scheduled independently and only run if `ENABLE_LEGACY_PROVIDER_JOBS=true` — kept separate so placeholder credentials on the legacy path can't crash-loop the whole app (both paths use `cats.effect.IO`'s `parMapN`, which cancels and fails every sibling task the instant one throws).
- Uses normalized transparent PNG assets for weather symbols; the internal letter codes remain stable identifiers and are not rendered as text or emoji.
1. A scheduled ingestion job parses provider station data (the open-data path by default; the legacy FTP path only if `ENABLE_LEGACY_PROVIDER_JOBS=true`).
Faktiskā has a fixed 13-position station configuration. On page entry the frontend calls `/api/query/latest-temperatures/<cities>`. The backend selects the newest row per requested city and returns `tempAvg`, the station name, and its observation timestamp. The UI highlights missing or older observations and permits an explicit manual override without changing the stored observation.
Weather symbols and wind values remain manual editorial inputs. One symbol can be applied to all fixed stations and exceptions changed individually. The browser composes these values on the fixed wind-template canvas and exports exactly 3840×1440 pixels. The production master images define the required output geometry; final Monda pixel-level comparison remains pending.
The renderer attaches each normalized 256×256 weather image to the measured edge of its temperature badge. All assets share one canvas, center, and scale, so placement does not depend on font bearings or baselines. The configuration permits per-station offsets when later production comparison requires them. Operator input selects the symbol but never controls its coordinates.
Ūdens is an independent fixed-template renderer. As of 2026-08-23 its six ranges auto-populate from `fetch.lvgmc.WaterTemperatureService` on load — real per-zone min/max, not station-query results from PostgreSQL — with manual override and a per-zone "Atiestatīt" reset still fully available, mirroring the auto-load/override/reset pattern Faktiskā already used. Each of the six named zones (Jūra, Līcis, Kurzeme, Zemgale, Vidzeme, Latgale) is a geographic grouping of every currently-reporting LVĢMC hydrological station in that area (56 inland `WTEMD` + 9 coastal `SEDUT` stations nationwide, classified by approximate historical-region boundaries — the mapping isn't derived from an authoritative region field, since the open-data portal doesn't publish one), so the displayed range reflects genuine spread across that zone's stations rather than one station's value shown twice. Readings older than 12h are dropped so one stuck sensor can't skew a zone's range. The 1920×1080 and 3840×1440 products use separate authoritative templates and six separately measured value rectangles. Canvas `TextMetrics` visible-glyph bounds center the Monda text optically inside each rectangle. Preview scaling never changes native export geometry, and both output sizes have been visually validated against the supplied newsroom templates.
Monda Regular and Bold are bundled locally and used by the interface and generated text. Weather symbols are bundled as independent transparent PNG assets.
Brīdinājumi renders the current LVĢMC warning polygons over the same `map_1920x1080`/`map_3840x1440` backgrounds used by Kartes, plus a production border-outline overlay drawn last so it stays crisp above the softened data layer. Warning severity is blurred and filled onto an offscreen layer before compositing, giving the broadcast graphic a feathered rather than hard-edged warning area.
The canvas renders every operator-checked warning at once, composited onto one offscreen layer before the shared feather blur is applied; there is no on-canvas legend. Polygons are drawn in ascending severity order (yellow, then orange, then red) so a higher severity painting over a lower one on the same ground reads as the dominant color where warnings overlap — this is how the tool supports the newsroom practice of combining multiple severity levels of one phenomenon (e.g. yellow + orange wind) onto a single export, while mixing different phenomena together remains an operator responsibility rather than something the UI prevents. The title is derived from the first checked warning's phenomenon (`Brīdinājums: {phenomenon}`) but stays manually editable. Each warning is listed as a compact phenomenon-icon chip (icon from the already-bundled `lucide-solid` set, keyword-matched against the LVĢMC phenomenon text) with a fully severity-colored background and white icon/text; a checkbox (custom-styled, not the browser default) independently controls map selection — any number may be checked — while clicking the chip body opens that warning's full text in a dialog rendered through a Solid `Portal` above a dimmed, blurred backdrop, without affecting the selection.
The backend returns warning polygons as raw latitude/longitude points; the frontend projects them to pixel space with a per-resolution affine transform (`x = a·lon + b·lat + c`, `y = d·lon + e·lat + f`). Each transform is a least-squares fit of well-spread Latvian cities' real-world coordinates against the pixel positions already validated for these same map backgrounds by Kartes/Faktiskā (`cityCoords.ts` + `mapConsts.ts`), rather than an independently eyeballed calibration. An earlier 4-corner bounding-box version of this projection could not represent the source artwork's rotation/shear and drifted up to ~200px on the 3840 canvas; the affine fit lands within ~35px.
Each checked warning can additionally carry one editorial weather-symbol placement (`IconPlacement`: symbol code plus `x`/`y`/`size` as fractions of the canvas), reusing the same normalized symbol set and image-loading cache (`components/weatherIcons/weatherIconAssets.ts`) that Faktiskā already uses, rather than a separate asset pipeline. The map canvas itself is the position/size control: pointer-event handlers on the `<canvas>` hit-test against each placement's bounding box and a small corner handle to distinguish a move-drag from a resize-drag, converting screen coordinates through the canvas's internal-vs-displayed size ratio. This is the app's first direct-manipulation canvas control — everywhere else uses plain form inputs. The interactive dashed box and resize handle are drawn as a separate pass (`drawInteractionHandles`, in `Warnings.tsx`) layered on top of the plain export render; `downloadPng` re-runs the plain render immediately before calling `canvas.toBlob`, captures the blob, then restores the interactive overlay, so the handles never reach the exported PNG despite both preview and export sharing the same canvas element.
WeatherTool has not yet been deployed publicly. The proposed month-long user-acceptance environment adds a removable edge layer without changing the application into the authentication authority:
Rocky remains the trusted build and verification host. The VPS receives a commit-addressed immutable image and release metadata rather than pulling and building the development repository. WeatherTool, its database, and Authelia must use a dedicated Compose project, private network, storage paths, and loopback-only upstream port so the existing VPS services remain untouched.
See `VPS_STAGING_PLAN.md` for the proposed topology and release procedure.