PRJ

Vilnius Sunshine Report

26 July 2026
SPECPROJECT
RoleDesign & build (personal project)
Date26 July 2026
Stack
PythontkinterChart.jsmatplotlibCalibrePowerShell
26 July 2026 ·Design & build (personal project)
PythontkinterChart.jsmatplotlibCalibrePowerShell

Background

Lithuania’s national weather service doesn’t expose a clean public API for historical sunshine-duration data. ogimet.com, an independent aggregator of SYNOP station reports, does the closest thing: it publishes daily summaries — including the “Sun D-1” column (sunshine hours for the previous day) — for WMO station 26730 (Vilnius) through a queryable CGI endpoint that returns an HTML results table rather than JSON.

That’s enough to work with. The project reverse-engineers that endpoint into a de facto API: build the right query parameters, parse the returned HTML table with Python’s standard-library parser, and cache the results locally so the same day is never re-fetched twice. Two properties of the source data shaped the design from the start:

  • ogimet throttles rapid requests, so fetching has to be deliberately slow and retry-tolerant rather than a single bulk pull.
  • A day’s sunshine total isn’t final until the day is over — querying for “today” reliably fails. The fetch logic caps every request at yesterday and treats today as expected-but-not-yet-available, not as an error state.

What I built

Data layer. A fetch/cache module pulls monthly tables from ogimet, parses them with html.parser, and stores daily values in a flat CSV. An incremental “missing days” mode diffs the cache against the calendar and only re-fetches genuine gaps, so routine updates are fast and polite to the source.

Desktop GUI. A tkinter app (stdlib only, no install step) wraps the whole workflow for non-technical daily use: one-click buttons for a fast missing-days update, a full re-fetch, a current-year-only refresh, and a no-network report rebuild. Fetches run on a background thread with output streamed live into an activity log, so the UI never freezes mid-fetch. A status line always shows the latest cached date and the running year-over-year totals.

HTML report. The primary output is a single self-contained vilnius_sunshine_report.html: cumulative and daily sunshine curves, monthly totals, and a full day-by-day comparison table, charted with Chart.js and toggle-able between smoothed and raw daily views. It’s a static file — double-click to open, no server required.

Kindle integration. The interactive report is built on Chart.js/canvas, which doesn’t render on an e-reader’s browser. Rather than force-fit it, the project builds a second, purpose-made edition: a dashboard is composited entirely as one image, laid out in matplotlib using absolute figure coordinates (headline stat, metric tiles, three grayscale charts) and rendered at the exact pixel resolution of the target Kindle. A second page does the same for a “this month vs. the same number of days last year” view, with larger tiles and a single day-by-day bar chart. Calibre’s ebook-convert turns the static HTML into a real .azw3 e-book (reflowable metadata, proper cover, correct margins for the device profile), and the tool auto-detects a connected Kindle by USB volume label and copies the book straight into its library — one GUI button, no manual file wrangling.

Outcome

  • A single “Send to Kindle” click now takes raw meteorological data all the way to a book sitting in the device’s library, with no manual conversion or file transfer step.
  • The cache is safe to refresh daily: incremental fetching means routine runs touch only the last day or two of new data instead of re-pulling months of history.
  • Two real integration bugs were found and fixed by treating the tool as a system to be debugged, not just written: an undocumented “reject queries for today” behavior in ogimet that was silently truncating fetches, and a multi-Python-install PATH ambiguity on the host machine that made a dependency appear “installed” for one interpreter and missing for the one actually running the GUI.
  • The project is in ongoing daily use tracking 2025-vs-2026 sunshine for Vilnius, viewable equally well in a browser or on an e-ink screen.

Stack rationale

ConcernChoiceWhy
Core app (fetch, cache, GUI)Python standard library only (urllib, html.parser, argparse, tkinter)Zero install friction — runs on any stock python.org install, nothing to package or distribute for a single-user tool.
Interactive reportStatic HTML + Chart.js via CDNNo build step, no server; the whole dashboard is one portable file that opens in any browser.
Kindle chartsmatplotlib (lazy-imported, only needed for the Kindle path)E-ink needs pre-rendered raster images, not JS. Figure-fraction coordinates give pixel-exact control for hand-composing a dashboard layout rather than relying on a generic chart grid.
HTML → e-book conversionCalibre’s ebook-convert CLICorrectly handles the AZW3/MOBI binary format, device profiles, and metadata instead of reimplementing an e-book format from scratch.
Device detectionPowerShell (Get-Volume)Reliable, native way to find a USB volume by label on Windows without adding a Python dependency just for that.
GUItkinterShips with the standard Windows Python installer — the app requires nothing beyond “install Python” to run.

The tkinter desktop app: a headline stat line (latest data date, 2025 vs 2026 year-to-date hours, and the difference), one-click update/report/Kindle buttons, a progress bar, and an activity log.

The Chart.js HTML report: a headline year-over-year comparison, a this-month stat tile, a row of metric cards (sunny days, days with any sun, sunniest day per year), and a cumulative-sunshine line chart for 2025 versus 2026.

A Kindle e-reader showing the matplotlib-composited digest: a this-month headline percentage change, four stat tiles, and a grayscale day-by-day bar chart comparing July 2025 and July 2026.
← All projects