Files
drk-blutspende-orte/docs/overview.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

121 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Overview what the app does
There is a **Hilfe** menu with "Nach Update suchen" (self-update via `git`, see
[setup.md](setup.md#updating)) and "Version…".
The app is a single window: a **DRK-red header bar**, two tabs, and a
**colour-coded status bar** at the bottom (grey = neutral, green = success,
orange = warning, red = error; transient messages fade back to neutral after
~8 s). The window title shows the total entry count, and the window remembers
its size and position between sessions (`data/window.json`). The UI font is
enlarged for readability (`UI_FONT_SIZE`).
## Tab 1 "Neuer Eintrag" (new entry)
Workflow: build up a **queue** of entries, let them geocode in the background,
then save the whole batch at once.
1. **Entry form** date (a **calendar picker**, `ttkbootstrap.DateEntry`,
pre-filled with today and **displayed as `TT.MM.JJJJ`**, e.g. `12.09.2026`;
typed input also accepts `JJJJ-MM-TT` and is **validated** an invalid date
is rejected with a dialog), city (autocomplete combobox), postal code
(optional, digits only), and **Straße** (street + house number, optional,
second row of the form) — see [Geocoding behaviour](#geocoding-behaviour) for
why it's worth filling in. Pressing `Return` in any field, or the
**+ Hinzufügen** button, adds the row to the queue.
2. **Queue table** shows date, city, PLZ, street, and a live coordinate column
that updates from `⏳ wird gesucht…` to either `lat / lon` or
`⚠ nicht gefunden` as the background geocoder works through the queue.
- Select a row and press <kbd>Delete</kbd>, use the **"Auswahl entfernen"**
button, or click the `✕` cell.
- Right-click a row for *Entfernen* / *Koordinaten erneut suchen*.
3. **Action bar**
- **Alle speichern** appends every queued row to `orte.csv`. If any row
looks like a duplicate (same date + city + PLZ), a **modal** lists them and
asks whether to save anyway.
- **Warteschlange leeren** discards the queue (asks for confirmation).
- **Karte öffnen** regenerates `karte.html` and opens it in the browser.
## Tab 2 "Einträge verwalten" (manage entries)
A table view of everything in `orte.csv`.
- **Columns:** Datum, Ort, PLZ, Straße, and **Karte** a status column showing
`✓` when the row has coordinates or a red `fehlt` when it doesn't (the whole
row is red too). The raw lat/lon numbers live in the edit dialog, not this
table.
- **Search box** live filter across date, city, PLZ, **and street**
(substring match).
- **Sortable columns** click a header to sort; clicking again reverses. The
active column shows a ▲/▼ arrow. Default sort is by date, newest first.
Sorting by **Karte** groups the rows without coordinates together.
- **"Nur ohne Koordinaten"** toggle filters to entries that have no
coordinates yet. The hint line always shows the total count, plus how many are
missing coordinates.
- **Edit** double-click a row (or *Bearbeiten*) opens a modal dialog to change
date (calendar picker, `TT.MM.JJJJ`, validated) / city / PLZ / **Straße**. Two
ways to fix coordinates: a "Koordinaten automatisch neu suchen" toggle re-runs
geocoding after saving (now using the street if one is set), or the
**Breitengrad / Längengrad** fields let you type them in by hand (both empty =
no map marker).
- **Right-click → Koordinaten suchen** runs geocoding for that one row
(handy for rows that failed the first time).
- **Delete** *Löschen* removes the selected row(s) after a confirmation
dialog. This is **irreversible**, but a timestamped copy of the file is
written to `data/backups/` before every change (see
[data-model.md](data-model.md#backups)).
- **Karte öffnen** same as on Tab 1.
## The map (`karte.html`)
Generated by `generate_map()`:
- Base layer: **Esri "World Light Gray"** (`MAP_TILES` + a labels overlay) a
light, muted basemap that needs no API key and no `Referer`. Initial view
centred on `[49.0, 9.0]`, zoom 8; auto-fits to the markers when there is more
than one.
- One **`CircleMarker`** per unique `(city, lat, lon)` group. Radius scales
linearly with the visit count for that location
(`5 + count / max_count * 20` px), colour is DRK red (`#CC0000`).
- Popup shows the city and the visit count; tooltip shows the city.
- Rows with missing/blank coordinates are silently excluded.
- If there are no usable coordinates at all, a dialog says so (and the status
bar shows a warning).
> The generated HTML pulls Leaflet, jQuery and Bootstrap from CDNs, and the map
> tiles from `server.arcgisonline.com`, so the map needs an internet connection
> to render even though the data is local. To change the look, edit `MAP_TILES` /
> `MAP_TILES_LABELS` near the top of `app.py` — the comment there lists other
> no-key Esri layers (Street, Topo). `tile.openstreetmap.org` was tried first
> but its tile-usage policy **403s** pages opened from `file://` (no `Referer`);
> `CartoDB positron` needs an API key.
## Geocoding behaviour
`geocode_city(city, postal_code, street="")` tries a series of queries in order
and returns the **first hit**:
1. `"<street>, <PLZ> <city>, Germany"` — only if a **street** was given (with
PLZ if there is one, without it if not). This is what the optional Straße
field is for: a full address geocodes far more precisely than a city name
alone (a point in the right town vs. the right building).
2. `"<PLZ> <city>, Germany"` (only if a PLZ was given)
3. `"<city>, Baden-Württemberg, Germany"`
4. `"<city>, Hessen, Germany"`
5. `"<city>, Germany"`
If the street-level query (1) doesn't match anything in Nominatim (typo, house
number Nominatim doesn't know, etc.), it **falls back automatically** to the
same city/PLZ-level searches used when no street is given — a bad street never
makes geocoding *worse* than before, only better when it resolves.
The regional bias is the `GEOCODE_REGIONS` constant (`Baden-Württemberg`,
`Hessen`) near the top of `app.py`. Results are rounded to five decimal places.
Nominatim's usage policy (max 1 req/s, identifying `user_agent`) is respected by
a manual `time.sleep(1)` in the worker loop.
If the geocoding service is unreachable (no internet), entries still save
without coordinates and after a few consecutive failures a one-time dialog
explains that the coordinates can be added later via *Bearbeiten*. The worker
thread logs failures to `data/app.log` and keeps running.