Initial commit: Arbeitsorte-Logger + data-safety/robustness/input work

Existing app (single-file Tkinter/ttkbootstrap desktop tool for logging
blood-drive work assignments and mapping them) plus the first round of
improvements:

- Data safety: atomic CSV writes (tmp + fsync + os.replace), rotating
  backups in data/backups/ (startup + before every change, keep 20),
  fallback to empty/backup on missing/empty/corrupt orte.csv.
- Geocoder robustness: per-item try/except so the worker thread survives
  failures; GeocodingUnavailable + one-time "service unreachable" dialog.
- Input: DateEntry calendar picker with parse_date() validation; manual
  lat/lon fields in the edit dialog; Tab 2 highlights/filters rows without
  coordinates and adds a right-click "Koordinaten suchen".
- Logging to data/app.log; shared autocomplete helpers; config constants;
  map fit_bounds.

docs/ describes current state, architecture, data model, setup (Linux Mint),
and the full improvement roadmap. data/orte.csv is gitignored for now.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 18:28:28 +02:00
commit 37c9642877
11 changed files with 1809 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
# Data model
## `data/orte.csv`
Plain CSV, comma-separated, UTF-8, with a header row. Created automatically with
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. |
| `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. |
| `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.
### Row identity
There is **no ID column**. Rows are identified by their pandas `DataFrame`
index, which is reset to `0..n-1` after every delete. UI tables use that index
as the Treeview `iid`.
Implication: a geocode callback that is in flight while the user deletes a
different row can land on the wrong row, because indices shift. `update_row` /
`_apply_edit_geocode` guard against a *missing* index but not against a
*reused* one. Still open — see [improvements.md](improvements.md#7-stable-row-identity-).
The manual lat/lon fields in the edit dialog give a way to correct any row
that ends up wrong.
### Duplicate detection
`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.
### Map aggregation
`get_map_data` groups by `(city, lat, lon)` and counts rows. Two entries for the
same city with different coordinates (e.g. Karlsruhe geocoded once to `76133`
and once to `76185`) produce **two separate markers**.
## `data/karte.html`
Regenerated from scratch every time the user opens the map. Safe to delete; it
is a build artefact, not data. Should be git-ignored if the project is ever put
under version control.
## `data/backups/`
Automatic timestamped copies of `orte.csv`, named `orte-YYYYMMDD-HHMMSS.csv`.
One is written at startup and one before every change (skipped when nothing
changed since the last backup). The newest 20 are kept; older ones are pruned.
Git-ignored. To restore, copy a backup over `data/orte.csv` while the app is
closed.
## `data/app.log`
Rotating log file (512 KB × 3 generations). Git-ignored. Records saves, map
generation, geocoding failures, and uncaught exceptions.