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
+11 -3
View File
@@ -7,14 +7,19 @@ just the header if it does not exist.
| Column | Type on disk | Meaning | Notes |
|--------|--------------|---------|-------|
| `date` | string | Assignment date | Always stored as `YYYY-MM-DD`. Input is validated and normalized by `parse_date()` (accepts `YYYY-MM-DD`, `DD.MM.YYYY`, `DD.MM.YY`, `YYYY/MM/DD`); invalid input is rejected before saving. Rows written before this change may still hold non-ISO strings. |
| `date` | string | Assignment date | Always **stored as `YYYY-MM-DD`**, regardless of display. Input is validated and normalized by `parse_date()` (accepts `YYYY-MM-DD`, `DD.MM.YYYY`, `DD.MM.YY`, `YYYY/MM/DD`); invalid input is rejected before saving. The date pickers *display* `TT.MM.JJJJ` (`DATE_DISPLAY_FORMAT`) since the 2026-09-12 change, but that's cosmetic — parsing/storage is unchanged, so old and new rows are identical on disk. |
| `city` | string | Place name | Free text. Used for autocomplete and duplicate detection (trimmed, case-insensitive). |
| `postal_code` | string | German PLZ | Optional. Kept as a string so leading zeros survive. Regex elsewhere accepts 45 digits. |
| `street` | string | Street + house number | Optional (e.g. `"Hauptstraße 12"`). Added 2026-09-12 for more precise geocoding — see [overview.md](overview.md#geocoding-behaviour). Rows written before that date have it blank. |
| `lat` | string | Latitude | Blank until geocoded. Rounded to 5 dp. Parsed with `pd.to_numeric(errors="coerce")` when building the map. |
| `lon` | string | Longitude | As above. |
Every column is read as a string (`dtype={"postal_code": str, "lat": str, "lon": str}`
plus `city`/`date` default object). Numeric conversion happens only where needed.
plus `city`/`date` default object) — **except this doesn't stop a blank cell from
coming back as `NaN`** (a float), even for a column forced to `dtype=str`. This
bit `street` immediately (all pre-existing rows have it blank): `_load()` now
does `self.df["street"] = self.df["street"].fillna("")` right after reading.
See [dev-notes.md](dev-notes.md#pandas-turns-blank-csv-cells-into-nan-even-with-dtypestr).
### Row identity
@@ -33,7 +38,10 @@ that ends up wrong.
`find_duplicates` flags a queued row when an existing row matches on all three
of: `date` (string-equal), `city` (trimmed, lower-cased), `postal_code`
(trimmed). It only produces a status-bar hint; duplicates are still written.
(trimmed). `street` is **not** part of the match (unchanged by the 2026-09-12
address feature) — two visits to the same city/PLZ on the same day are flagged
as possible duplicates even with different streets. A modal now asks whether to
save anyway (see [changelog.md](changelog.md)).
### Map aggregation