d418f156fa
- 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>
105 lines
4.8 KiB
Markdown
105 lines
4.8 KiB
Markdown
# 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:
|
|
|
|
```bash
|
|
/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:
|
|
|
|
```python
|
|
>>> 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_csv`** — `dtype=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.
|