summaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md189
1 files changed, 189 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..17ad747
--- /dev/null
+++ b/README.md
@@ -0,0 +1,189 @@
+# prognosis
+
+Hour-by-hour weather for the terminal, with official Polish warnings and pollen.
+
+Companion to [wego](https://github.com/schachmat/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 modules** — `make ci` fails if
+one creeps in.
+
+```
+ ! Upał level 1 08-10 11:00 -> 08-10 20:00 (85%)
+ Prognozuje się upał. Temperatura maksymalna wyniesie od 30°C do 33°C.
+
+Krakow, PL Mon 10 Aug 05:15 up 20:01 down GMT+2
+ day 12-30° dry sun 12h24m of 14h45m daylight
+ pollen grass 10.5 low mugwort 4.5 low ragweed 1.4
+
+ hr temp feels conditions
+ 13 28° part cloudy
+ 14 29°
+ 15 30° (28) mainly clear
+ ...
+
+ 30°│ ▄▄▄▄▄██████████▄▄▄▄▄▄▄▄▄▄
+ │▄▄▄▄▄██████████████████████████████▄▄▄▄▄
+ 26°│████████████████████████████████████████
+ │█████████████████████████████████████████████
+ 22°│█████████████████████████████████████████████▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
+ 12 14 16 18 20 22
+ 22-30°
+```
+
+## Install
+
+ make build # build ./prognosis
+ make install # build and install into ~/.local/bin
+ make cross # dist/ binaries for linux-amd64, linux-arm64, android-arm64
+ make ci # gofmt, vet, tests, and the no-dependencies check
+
+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.
+
+`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)
+ prognosis --no-graph # table only
+ prognosis --no-color # plain text
+
+Location comes from `location=` in `~/.wegorc`, so wego and prognosis never
+disagree about where you are. `-l` overrides it for one run.
+
+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.
+
+## Data sources
+
+| Source | Used for |
+|---|---|
+| [Open-Meteo forecast](https://open-meteo.com/) | hourly temps, precipitation, codes, daily summary, sun times |
+| [Open-Meteo air quality](https://open-meteo.com/en/docs/air-quality-api) | pollen per species |
+| [Open-Meteo geocoding](https://open-meteo.com/en/docs/geocoding-api) | place name → coordinates |
+| [GUGiK](https://services.gugik.gov.pl/uug/) | coordinates → TERYT powiat code (Poland) |
+| [IMGW](https://danepubliczne.imgw.pl/) | official meteorological warnings |
+
+Geocoding and TERYT results are cached in `~/.cache/prognosis/cache.json`.
+Forecast, warnings and pollen are fetched every run, so three HTTP calls.
+
+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](http://www.alergen.info.pl/Alergeny/Pylek_trawy))
+- **birch** — 80 provokes symptoms in over 95% of allergics ([mp.pl](https://www.mp.pl/pacjent/alergie/lista/105140,jakie-czynniki-wplywaja-na-stezenie-alergenow-wziewnych))
+- **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 34 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`.