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>
This commit is contained in:
2026-09-12 11:15:37 +02:00
parent 0b6ed854dc
commit d418f156fa
7 changed files with 185 additions and 61 deletions
+36 -22
View File
@@ -16,14 +16,16 @@ 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; typed input is accepted in `JJJJ-MM-TT` or
`TT.MM.JJJJ` and **validated** an invalid date is rejected with a dialog),
city (autocomplete combobox), postal code (optional). Pressing `Return` in any
field, or the **+ Hinzufügen** button, adds the row to the queue.
The PLZ field only accepts digits.
2. **Queue table** shows date, city, PLZ, 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.
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*.
@@ -38,10 +40,12 @@ then save the whole batch at once.
A table view of everything in `orte.csv`.
- **Columns:** Datum, Ort, PLZ, 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, and PLZ (substring match).
- **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.
@@ -49,10 +53,11 @@ A table view of everything in `orte.csv`.
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, validated) / city / PLZ. Two ways to fix coordinates:
a "Koordinaten automatisch neu suchen" toggle re-runs geocoding after saving,
or the **Breitengrad / Längengrad** fields let you type them in by hand
(both empty = no map marker).
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
@@ -87,13 +92,22 @@ Generated by `generate_map()`:
## Geocoding behaviour
`geocode_city(city, postal_code)` tries a series of queries in order and returns
the first hit:
`geocode_city(city, postal_code, street="")` tries a series of queries in order
and returns the **first hit**:
1. `"<PLZ> <city>, Germany"` (only if a PLZ was given)
2. `"<city>, Baden-Württemberg, Germany"`
3. `"<city>, Hessen, Germany"`
4. `"<city>, Germany"`
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.