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:
@@ -0,0 +1,85 @@
|
||||
# Overview – what the app does
|
||||
|
||||
The app is a single window with a status bar at the bottom and two tabs.
|
||||
|
||||
## 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; 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.
|
||||
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.
|
||||
- Left-click the `✕` cell to remove a row.
|
||||
- Right-click a row for *Löschen* / *Koordinaten erneut suchen*.
|
||||
3. **Action bar**
|
||||
- **Alle speichern** – appends every queued row to `orte.csv`. If a row looks
|
||||
like a duplicate of an existing entry (same date + city + PLZ) a hint is
|
||||
shown in the status bar, but the row is **still saved**.
|
||||
- **Leeren** – discards the queue without saving.
|
||||
- **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`.
|
||||
|
||||
- **Search box** – live filter across date, city, and PLZ (substring match).
|
||||
- **Sortable columns** – click a header to sort; clicking again reverses.
|
||||
Default sort is by date, newest first.
|
||||
- **"Nur ohne Koordinaten"** toggle – filters to entries that have no
|
||||
coordinates yet. Such rows are also shown in **red** in the full list, and the
|
||||
hint line reports how many there are.
|
||||
- **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).
|
||||
- **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: `CartoDB positron`, initial view centred on `[49.0, 9.0]`,
|
||||
zoom 8 (roughly Baden-Württemberg).
|
||||
- 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 `ValueError` is raised and shown
|
||||
in the status bar.
|
||||
|
||||
> The generated HTML pulls Leaflet, jQuery and Bootstrap from CDNs, so the map
|
||||
> needs an internet connection to render even though the data is local.
|
||||
|
||||
## Geocoding behaviour
|
||||
|
||||
`geocode_city(city, postal_code)` 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"`
|
||||
|
||||
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.
|
||||
Reference in New Issue
Block a user