aboutsummaryrefslogtreecommitdiff

prognosis

Hour-by-hour weather for the terminal, with official Polish warnings and pollen.

Companion to wego, which renders only four dayparts per day. prognosis fills the gap wego cannot: an hourly table, a temperature chart, IMGW warnings for your own powiat, and pollen counts with qualitative bands.

No API key. Written in Go with no third-party modulesmake ci fails if one creeps in.

Krakow, PL   Tue 25 Aug   05:44 up  19:39 down  GMT+2
 day    12-17°   rain 1.9mm over 7h   sun 0h43m of 13h54m daylight
 pollen ragweed 5.0  mugwort 4.4 low  grass 3.1 low  birch 0.0 none

 hr  temp  feels  conditions         hum    mm  rain
 16  16°          lt drizzle         80%   0.3   44%
 17  16°          overcast           77%   0.0   27%
 18  16°          lt drizzle         78%   0.1   27%
 19  15°                             86%   0.2   36%
 20  15°                             87%   0.4   45%
 21  15°                             85%   0.2   52%
 22  15°   (14)   overcast           80%   0.0   58%
 23  15°                             78%   0.0   59%

15.9°│        ████████████████
     │▄▄▄▄▄▄▄▄████████████████
15.2°│████████████████████████                                ▄▄▄▄▄▄▄▄
     │████████████████████████▄▄▄▄▄▄▄▄                ████████████████
14.6°│████████████████████████████████▄▄▄▄▄▄▄▄████████████████████████
 rain│▆▆▆▆▆▆▆▆▁▁▁▁▁▁▁▁▃▃▃▃▃▃▃▃▅▅▅▅▅▅▅▅████████▅▅▅▅▅▅▅▅▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁  max 0.4mm
      16      17      18      19      20      21      22      23
      14.6°-15.9°

When IMGW has issued a warning for your powiat, it leads the output:

 ! Upał  level 1  08-10 11:00 -> 08-10 20:00  (85%)
   Prognozuje się upał. Temperatura maksymalna wyniesie od 30°C do 33°C.

Install

make build      # build ./prognosis
make install    # build and install into ~/.local/bin
make cross      # dist/ binaries for linux, android, darwin and freebsd
make ci         # gofmt, vet, tests, man page, no-dependencies check
make install-hooks   # pre-push hook that runs make ci

Packagers: make install PREFIX=/usr DESTDIR=/tmp/pkg installs the binary and man/prognosis.1.

Needs ~/.local/bin on PATH. For the phone, copy dist/prognosis-android-arm64 across — no interpreter, no shebang, nothing to install.

Configuration

~/.config/prognosis/config, written with commented defaults on first run (prognosis -config prints the path). One KEY=VALUE per line, # comments. Flags override the file; the file overrides the built-in defaults.

Columns are chosen and ordered freely:

columns=hour,icon,temp,feels,conditions,wind,gusts,rain

Available: hour icon temp feels conditions mm rain wind gusts dir humidity dew uv cloud pressure visibility. Only the fields you select are requested from the API. An unknown name is a startup error listing the valid ones, never a silently blank column.

icons= picks the glyph set: nerd (default), emoji or none. Nerd Font glyphs are single-width and monochrome, so they follow the terminal palette; emoji are colour glyphs from a fallback font and are not all one cell wide.

Pollen

pollen= decides whether the allergen line appears at all and which species it carries:

pollen=none            # no pollen line
pollen=all             # every species that has a reading
pollen=birch,mugwort   # exactly these, always — even at zero

A species you name is shown even when it reads zero: you named it because you react to it, so "none today" is the answer you wanted. With all nobody chose, so absent species are dropped rather than printing a line of zeroes. -pollen does the same for one run.

Custom columns

Any field the two Open-Meteo APIs expose can be displayed, whether or not prognosis ships with it. Declare a short name, then use it:

columns=hour,temp,birch,soil,conditions

column.birch = air:birch_pollen
column.soil  = forecast:soil_temperature_0cm

label.birch    = birch
decimals.birch = 1
suffix.soil    = °

forecast is the weather API, air the air-quality one that carries the allergens — they are separate services with separate fields, which is why the source is explicit. An air column costs one extra request, made only when one is declared.

label., width., decimals. and suffix. are optional. A custom name may not shadow a built-in column, and a value the API does not supply renders blank rather than as zero: for an allergen, "no data" and "none" are different claims.

display_lang= is en or pl, covering everything prognosis writes itself — headers, condition names, labels, dates, pollen species and bands. IMGW publishes its warning text in Polish only, so that text stays Polish in either language: an invented English rendering of an official warning would be worse than the original.

Usage

prognosis                    # next 12 hours where you live
prognosis -l krakow          # somewhere else
prognosis 50.0617,19.9373    # or by coordinates
prognosis -n 24              # next 24 hours
prognosis -d 3               # three days, 24h each (max 15)

Every flag, and every setting in the config file, is documented in man prognosis. In short:

flag
-l PLACE place to query, overriding location=
-pick N choose the Nth candidate for an ambiguous name, and remember it
-n N / -d N hours, or days of 24 hours
-columns LIST columns to display, in order
-icons SET nerd, emoji or none
-lang LANG en or pl
-pollen LIST allergens to show: a list, or all / none
-weather forecast only: no sun times, summary, pollen or chart
-no-warnings omit IMGW warnings
-no-graph table only
-ascii ASCII only, so an SMS stays in GSM-7
-no-color plain output
-svg produce an SVG meteogram instead of the table
-o PATH, -out PATH write to PATH; a directory gets a generated filename
-config print the config file path and exit
-version print the version and exit

Location comes from location= in the config file; -l overrides it for one run. prognosis is standalone — it reads no other program's configuration and will not guess where you are.

Quote a name that contains a comma or a space. -l Wiry, PL is two arguments once the shell has finished with it, and prognosis refuses it rather than quietly forecasting for whatever Wiry, alone resolves to.

An ambiguous name is never guessed. prognosis lists what matched and fetches nothing, so a request for one place can't silently return another:

$ prognosis -l "Wiry, PL"
prognosis: "Wiry, PL" is ambiguous - nothing fetched.
  1  Wiry, PL  Greater Poland        52.3205,16.8532
  2  Wiry, PL  Lower Silesia         50.8367,16.6467
Re-run with -pick N, or give coordinates as the place.

-pick N chooses one and remembers it, so you only do this once per name. It also re-resolves rather than reading the cache, which is what lets it correct a name that was cached wrongly. Exit status is 2 for both the ambiguity and an out-of-range -pick.

Output is pipe-safe: colour switches off when stdout is not a terminal, so prognosis > file is clean UTF-8. Notes and failures go to stderr, so use 2>&1 for an unattended job.

Meteogram

-svg writes a standalone chart: temperature with feels-like dashed, precipitation bars with the probability on its own right-hand axis, and humidity — three panels over one time axis, with shaded night bands.

prognosis -l Krakow -d 2 -svg > meteogram.svg
prognosis -l Krakow -d 2 -svg -o ~/photos/weather
prognosis -l Krakow -d 2 -svg -o meteogram.svg

-o takes a file or a directory. Given a directory it generates the name from the place and the date — krakow-pl-2026-08-27.svg — so a daily run does not overwrite yesterday. It works for the text table too, which then gets .txt and no colour.

No plotting library and no gnuplot: SVG is markup, so this works anywhere the binary does. The meteogram requests the fields it needs regardless of columns=.

Not every viewer reads a pipe. feh - and chafa do; nsxiv has no stdin mode and answers with its usage, which hides the cause:

prognosis -l Krakow -svg | feh -
prognosis -l Krakow -svg | chafa
prognosis -l Krakow -svg -o ~/tmp && nsxiv ~/tmp/krakow-pl-*.svg

Given the file, most viewers handle it: plumber, nsxiv, feh, display, a browser, or chafa in the terminal. One caveat: convert may fall back to ImageMagick's own SVG renderer, which drops the polylines — rsvg-convert does not, and display is fine because ImageMagick delegates to it.

Data sources

Source Used for
Open-Meteo forecast hourly temps, precipitation, codes, daily summary, sun times
Open-Meteo air quality pollen per species
Open-Meteo geocoding place name → coordinates
GUGiK coordinates → TERYT powiat code (Poland)
IMGW official meteorological warnings

Geocoding and TERYT results are cached in ~/.cache/prognosis/cache.json. Forecast, warnings and pollen are fetched every run: three HTTP calls, or four when a custom air: column is declared.

The cache is written one entry per line with its keys sorted, so it is readable and hand-editable. It is also treated as disposable but never as worthless: an entry that will not parse is skipped and the rest of the file is kept, and a file that will not parse at all is moved to cache.json.bad rather than overwritten, so a typo costs you the lookups but not what you wrote.

Warnings

IMGW publishes every warning in Poland, each tagged with the TERYT codes of the powiats it covers. GUGiK turns your coordinates into that code, so warnings are filtered to your area rather than the whole country.

Three states, kept deliberately distinct — silence must never be mistaken for all-clear:

  • warnings printed — in force for your powiat
  • nothing printed — checked, none in force
  • warnings: could not check IMGW — the check itself failed
  • warnings: IMGW covers Poland only — the location is abroad

Thresholds and why they are what they are

Nothing here is a matter of taste, except where it says so.

Temperature colours use IMGW's own warning criteria, so a red temperature means the met office would issue a warning about it:

criterion colour
Silny mróz, stopień 1 Tmin ≤ -15°C bright blue
Upał, stopień 1 Tmax ≥ 30°C red
Upał, higher level Tmax > 35°C bright red

The divisions between (0, 10, 20) are round numbers, not thresholds from any source; they only subdivide the range nobody warns about.

Pollen bands, grains/m³, from Polish clinical sources:

  • grass — 20 = first nasal symptoms in ~25% of sufferers, 50 = symptoms in all tested, 65 = intensified in over 75%, 120 = dyspnoea after 30 minutes (alergen.info.pl)
  • birch — 80 provokes symptoms in over 95% of allergics (mp.pl)
  • mugwort — over 70 counts as high, intensified symptoms (mp.pl)

Birch and mugwort have a single published anchor each, so they get a two-way split rather than four bands — their "low" is weaker evidence than grass's. Alder, olive and ragweed have no Polish threshold I could source and are shown as bare numbers rather than banded on a guess.

Notes for anyone editing this

  • Colour uses ANSI slots 0–15 only, never 256-colour indices. It runs on a terminal whose palette remaps the low slots to shades of green; a hardcoded 38;5;196 would be the one non-green thing on screen.
  • Pad cells to width before colouring them. Escape sequences carry no visible width, so padding a coloured string misaligns the whole column. This is invisible when piped (colour off) and obvious in a real terminal.
  • Open-Meteo hourly arrays start at 00:00 local. Slicing from the front reports this morning, not the hours ahead. See openmeteo.WindowStart.
  • The table does not shrink to fit. Column widths are fixed; the default set needs 40 columns. TestTableMinimumWidthIsKnown pins that figure. Use columns= for a narrow terminal.
  • GUGiK's default search radius is 100 m, which finds nothing in the mountains or deep countryside — indistinguishable from being abroad. The request asks for more; GUGiK clamps it to its own 5 km maximum.
  • The chart downsamples. A week is 168 hourly points; columns cover several hours on long spans and the header says Nh/col when they do.

Licence

GNU General Public License v3. See LICENSE.