Files
drk-blutspende-orte/docs/dev-notes.md
T
Paddy d418f156fa German date display + optional street address for precise geocoding
- Date pickers (entry form + edit dialog) now display TT.MM.JJJJ instead of
  ISO (DATE_DISPLAY_FORMAT). Storage stays YYYY-MM-DD; parse_date() already
  accepted both formats, so existing data and the self-updater are unaffected.
- New optional "Straße" field (street + house number) in the entry form and
  edit dialog, backed by a new `street` CSV column. geocode_city() and
  GeocoderWorker.enqueue() gained a street parameter: when set, a full-address
  query is tried first for a much more precise map point, falling back
  automatically to the existing city/PLZ search if it doesn't resolve.
- Tab 2 and the entry queue show a Straße column; Tab 2 search now also
  matches on street.
- Fix: pandas turns a blank CSV cell into NaN even for a dtype=str column, so
  every existing (blank-street) row would have shown literal "nan" in Tab 2.
  DataStore._load now does street.fillna("") after every read.

Verified with a non-GUI test suite (date parsing, query construction, CSV
round-trip incl. the NaN case) and a full GUI build/drive test on Python
3.12/Tk 9. Docs updated (changelog, overview, architecture, data-model,
dev-notes, improvements).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 11:15:37 +02:00

4.8 KiB

Dev notes — assumptions that turned out wrong

Running list so the same mistakes aren't repeated. Add to it whenever reality contradicts an assumption.

The macOS system Python can't run the GUI; use a Homebrew Python (2026-09-07)

The macOS CommandLineTools Python 3.9 (/Library/Developer/CommandLineTools/ .../python3.9) ships Tk 8.5.9. ttkbootstrap 1.10.1 requires Tk 8.6+. Symptoms: TclError: couldn't recognize image data (window icon) and TclError: unknown option "-style" on ttk.Scrollbar.

Fix for local GUI testing: brew install python-tk@3.12 (pulls tcl-tk, gives Python 3.12 + Tk 9.0), then:

/opt/homebrew/bin/python3.12 -m venv venv
venv/bin/pip install -r requirements.txt
venv/bin/python app.py

ttkbootstrap 1.10.1 runs fine on Tk 9.0 in practice (verified: window builds, Treeview sort, DateEntry, LabelFrame, custom styles all work).

  • The Linux Mint target uses its own python3-tk (Tk 8.6+) and is unaffected either way.
  • screencapture from a non-GUI shell fails ("could not create image from display") without Screen-Recording permission — automated screenshots of the running app aren't available here; verify by building the widget tree and driving it programmatically instead.

pandas reads everything as strings by design

DataStore._load uses dtype={...: str} for postal_code, lat, lon. Numeric parsing is deliberately deferred to get_map_data (pd.to_numeric(..., errors="coerce")). Don't "fix" columns to float on load — blank coordinates and leading-zero PLZ both depend on the string representation.

Map tiles: Carto needs a key, OSM 403s file:// — use Esri (2026-09-07)

Two dead ends before landing on Esri:

  1. folium.Map(tiles="CartoDB positron") → "API KEY REQUIRED" watermark; Carto moved basemaps behind a key.
  2. tiles="OpenStreetMap" → worked in curl from the dev machine but the user's browser got HTTP 403 "referer is required by tile usage policy". OSM's tile CDN graylists refererless requests, and a karte.html opened as a local file:// sends no Referer. A browser can't add one. Dead end for this app's "open a local HTML file" model.

Now: Esri "World Light Gray" base + reference (labels) overlay (server.arcgisonline.com/ArcGIS/rest/services/Canvas/…). No key, no Referer check, light/muted look close to the old positron. MAP_TILES / MAP_TILES_LABELS constants; comment lists Esri Street/Topo as swaps.

Lesson: a tile source that works from curl or an http:// page can still fail from file://. Test the map by opening the generated karte.html directly, not just by checking the URL in the HTML.

pandas turns blank CSV cells into NaN even with dtype=str (2026-09-12)

Assumption that turned out wrong: forcing a column's dtype (e.g. dtype={"street": str}) does not stop pd.read_csv from parsing a truly empty field as NaN (a float) instead of "". Verified directly:

>>> pd.read_csv(io.StringIO("a\n\n"), dtype={"a": str})["a"][0]
nan          # not ''
>>> str(pd.read_csv(io.StringIO("a\n\n"), dtype={"a": str})["a"][0])
'nan'        # !!

This is why lat/lon already needed _is_blank() / _coord_str() helpers — but the new street column (added 2026-09-12) hit it immediately and much harder: it's optional, so every existing row has it blank, and without a fix every one of them would show the literal text nan in Tab 2. Fix: after pd.read_csv (all three load paths — normal, empty-file, backup-recovery), DataStore._load runs self.df["street"] = self.df["street"].fillna("").

Rule for this codebase: any column that can legitimately be blank needs an explicit .fillna("") (or the _is_blank/_coord_str treatment) right after pd.read_csvdtype=str alone is not enough. city/postal_code haven't needed this only because they're rarely actually blank in practice, not because they're immune.

Tk popup menus: bind <ButtonRelease-3>, not <Button-3> (2026-09-07)

widget.bind("<Button-3>", …) + menu.tk_popup(x, y) + finally: menu.grab_release() is the Windows idiom and misfires on X11: tk_popup returns immediately, grab_release() drops the menu's grab, and the pending <ButtonRelease-3> then activates whatever entry is under the cursor (the first one). Symptom here: right-click a history row → menu flashes → edit dialog opens.

Rules for this codebase:

  • bind the release event: RIGHT_CLICK constant (<ButtonRelease-3>, or <ButtonRelease-2> on macOS),
  • build each context menu once (store on self), don't recreate per click,
  • just call menu.tk_popup(...) — no grab_release().

Nominatim query order matters

geocode_city returns the first hit, trying PLZ-qualified first, then each region in GEOCODE_REGIONS, then a bare ", Germany". Reordering changes which coordinates ambiguous town names resolve to.