# Improvement roadmap Review of the current code, prioritized for the actual deployment: **one non-technical user, Linux Mint laptop, offline some of the time, no one else to fix things when they break.** That ordering puts *data safety* and *robustness* above features. Priorities: πŸ”΄ do first Β· 🟑 worth doing Β· 🟒 nice to have **Status (2026-09-07):** items 1, 2 and 3 are implemented. Items 6 and 10 are partly done (logging added; shared autocomplete helpers extracted; map now `fit_bounds`; region list is now a constant). See [changelog.md](changelog.md). --- ## 1. Data safety πŸ”΄ β€” βœ… done Implemented: atomic writes (`tmp` + `fsync` + `os.replace`), rotating backups in `data/backups/` (startup + before every change, keep 20), `EmptyDataError` / corrupt-file fallback to an empty frame or the newest backup, and all writes (including append) routed through `_save_csv`. A "restore from backup" button in the UI is still open; for now it is a manual file copy.
Original finding `data/orte.csv` is the only copy of the data and there is no safety net. - **No backups.** A bad edit, a botched delete, or a disk hiccup loses history permanently. Delete is explicitly irreversible and only guarded by a yes/no dialog. - **Non-atomic full rewrites.** `update_row` / `delete_rows` do `df.to_csv(CSV_PATH)` directly. A crash or power loss mid-write can leave a truncated file. - **External-edit clobber.** The CSV is read once at startup. If it is opened in a spreadsheet and changed while the app runs, the next save silently overwrites those changes. - **Empty-file crash.** If `orte.csv` exists but is 0 bytes (e.g. an interrupted write), `pd.read_csv` raises `EmptyDataError` and the app won't start. **Suggested changes** - Write every full rewrite to a temp file in the same directory, then `os.replace()` it into place (atomic on POSIX). - On startup, and before each destructive write, copy the current file to `data/backups/orte-YYYYMMDD-HHMMSS.csv`; keep the last ~20 and prune the rest. - Add a "Backup jetzt erstellen" / "Aus Backup wiederherstellen" pair in Tab 2, or at least open the backups folder from a menu. - Handle `EmptyDataError` / missing columns by falling back to an empty frame and logging a warning.
## 2. Geocoder thread robustness πŸ”΄ β€” βœ… done Implemented: every queue item is wrapped in `try/except Exception` (both the geocode call and the callback), failures are logged and the loop continues. `geocode_city` now raises `GeocodingUnavailable` when every attempt hit a service/network error, and after 3 consecutive such failures the app shows a one-time "Standortdienst nicht erreichbar" dialog. `RateLimiter` was not adopted; the manual `sleep(1)` stays.
Original finding `GeocoderWorker.run` has no error boundary around the per-item work. If `geocode_city` or a callback raises anything other than `GeocoderTimedOut` / `GeocoderServiceError` (a network stack error, a bug in a callback, `GeocoderQuotaExceeded`), the **worker thread dies** and every subsequent entry stays stuck on `⏳ wird gesucht…` with no error shown. The user has no way to know geocoding stopped working. **Suggested changes** - Wrap each item's processing in `try/except Exception`, log the traceback, mark that row as failed, and keep the loop alive. - Consider `geopy.extra.rate_limiter.RateLimiter` (with `swallow_exceptions`) instead of the manual `sleep(1)`. - Surface a clear one-time status message when geocoding fails repeatedly ("Standortdienst nicht erreichbar – bitte Internetverbindung prΓΌfen").
## 3. Easier, safer input πŸ”΄ β€” βœ… done Implemented: the date field is now `ttkbootstrap.DateEntry` (calendar picker) in both the entry form and the edit dialog, with `parse_date()` validation on save (a bad date is rejected with a dialog). The edit dialog gained manual **Breitengrad / LΓ€ngengrad** fields. Tab 2 shows rows without coordinates in red, has a "Nur ohne Koordinaten" filter, a hint line with the count, and a right-click **Koordinaten suchen** action.
Original finding For the target user the free-text date field is the biggest usability risk. - **Date field is unvalidated free text** in both the entry form and `EditDialog`. A typo like `2026-13-05` or `05.02.2026` is stored verbatim, breaks date sorting, and never appears correctly on the timeline. - **No recourse when geocoding fails.** A row that comes back `⚠ nicht gefunden` saves with blank coordinates and then just silently never shows on the map. There is no way to type coordinates by hand or retry from Tab 2. **Suggested changes** - Replace the date `Entry` with `ttkbootstrap.DateEntry` (calendar picker) in both places. If keeping free text, validate on add/save and refuse invalid dates with a clear message. - In `EditDialog`, add optional `lat` / `lon` fields so a location can be fixed manually. - In Tab 2, highlight rows with missing coordinates (e.g. red text) and add a filter / right-click "Koordinaten suchen" so failed geocodes are visible and fixable after the fact.
## 4. Put it under version control 🟑 β€” partly done `git init` done and `.gitignore` added (covers `.venv/`, `__pycache__/`, `data/karte.html`, `data/backups/`, `data/app.log`). Still open: the initial commit (left to Patrick), and the decision on committing `data/orte.csv` vs. an example file. - Decide on `data/orte.csv`: for a personal tool it's reasonable to commit it, or commit a small `data/orte.example.csv` and ignore the real one. - This `docs/` folder. Gives you history, a rollback path, and a clean way to push updates to the laptop (`git pull`). ## 5. Packaging & distribution 🟑 β€” mostly done - βœ… `requirements.txt` fully pinned (direct + transitive), tested on 3.12. - βœ… `run.sh` + `install.sh` (generates the `.desktop` entry) shipped in the repo. - βœ… Bundled `icon.png`. - ⬜ Optional: a [PyInstaller](https://pyinstaller.org/) one-file bundle would drop the apt/venv step entirely, at the cost of building per release. Not needed while `install.sh` works. ## 6. Diagnostics / logging 🟑 β€” βœ… done `_setup_logging()` writes a `RotatingFileHandler` to `data/app.log` (512 KB Γ— 3). Saves, map generation, geocode failures and uncaught exceptions are logged. `_on_open_map` now logs and narrows its handling. Open: no in-app "open log folder" shortcut yet. ## 7. Stable row identity 🟑 Rows are keyed by the pandas index, which is renumbered after every delete. An in-flight geocode callback for one row can land on another row if the user deletes something in between. `_apply_edit_geocode` guards against a *deleted* index but not a *reused* one. **Suggested change:** add an immutable `id` column (uuid or incrementing int) written to the CSV, and match callbacks on that instead of the positional index. ## 8. Duplicate handling 🟑 `_on_save_all` detects likely duplicates but saves them anyway with only a transient status-bar line the user will probably miss. Consider a modal "Diese EintrΓ€ge existieren schon – trotzdem speichern?" with a per-row choice, or at least skip exact duplicates by default. ## 9. Offline map 🟒 `karte.html` loads Leaflet/jQuery/Bootstrap from CDNs and tiles from `server.arcgisonline.com` (Esri; `MAP_TILES` β€” Carto needs a key, OSM 403s `file://`). The map is blank without internet even though all the data is local. If offline use matters, vendor the Leaflet JS/CSS into `data/` and post-process the Folium output to point at local files (tiles would still need caching or an offline tile pack β€” larger effort). ## 10. Small code-quality items 🟒 β€” partly done - βœ… Shared autocomplete helpers (`filter_locations`, `split_city_plz`) extracted; `EditDialog` and `App` both use them. - βœ… Region list is now the `GEOCODE_REGIONS` constant; map centre/zoom are `MAP_DEFAULT_CENTER` / `MAP_DEFAULT_ZOOM`. - βœ… `generate_map` now `fit_bounds` to the markers. - ⬜ Add a `tests/` folder with pytest coverage for `DataStore` (append / update / delete / `find_duplicates`) and the input helpers. A non-GUI smoke script exists (used during development) but is not in the repo β€” worth formalizing. - ⬜ Add `ruff` / `mypy` config. --- ## UI/UX review (2026-09-07) Second review, focused on the interface. Items 1–12 implemented; 13 deferred. | # | Change | Status | |---|--------|--------| | 1 | Larger base font + taller Treeview rows (`UI_FONT_SIZE`) | βœ… | | 2 | Colour-coded status bar + 8 s auto-clear to a neutral state | βœ… | | 3 | "Warteschlange leeren" asks for confirmation; more form spacing | βœ… | | 4 | β–²/β–Ό arrow on the active sort column in Tab 2 | βœ… | | 5 | Dropped the now-redundant `(JJJJ-MM-TT)` hint | βœ… | | 6 | Tab 2: single **"Karte"** column (`βœ“` / red `fehlt`) instead of raw lat/lon | βœ… | | 7 | Explicit "Auswahl entfernen" button + Delete key for queue rows | βœ… | | 8 | Duplicate save shows a modal with the list, not a transient hint | βœ… | | 9 | Persistent entry count (window title + Tab 2 hint line) | βœ… | | 10 | "Karte ΓΆffnen" with no coordinates shows a dialog | βœ… | | 11 | Remember window size & position (`data/window.json`) | βœ… | | 12 | DRK-red header bar + runtime-drawn red-cross icon (`data/icon.png`) | βœ… | | 13 | One-click "HinzufΓΌgen & sofort speichern" | ⬜ deferred | Full custom DRK-red *theme* (recolouring `primary` etc.) was **not** done β€” the red header bar gives the branding without fighting ttkbootstrap's theme system. ## Self-update (2026-09-07) β€” βœ… done **Hilfe β–Έ Nach Update suchen** / quiet check on start / restart via `os.execv`. `git merge --ff-only` only, `pip install` if `requirements.txt` changed, `data/` untouched. Needs a git clone + `git` installed (`Updater.available`). Open follow-ups: no rollback if a pushed update is broken (mitigated by `--ff-only` from a branch Patrick controls + pip errors caught pre-restart); `run.sh` / `.desktop` still not committed (item 5). ## German date + street address (2026-09-12) β€” βœ… done, one idea deferred Date pickers display `TT.MM.JJJJ`; storage untouched. New optional `street` CSV column feeds a full-address geocode attempt before falling back to city/PLZ. See [changelog.md](changelog.md). **Deferred idea, not requested:** since the same person is often logged at the same city repeatedly, the address autocomplete could remember and suggest the last-used street for a selected city (the way selecting a city already auto-fills its PLZ). Would remove re-typing the same street each time, at the cost of a bit more state to reason about. Worth doing if re-typing the address turns out to be annoying in practice β€” not implemented now to keep the change minimal. ## Suggested order of remaining work 1. Deploy to the laptop: `git clone` + `./install.sh` (see [setup.md](setup.md)). 2. Stable `id` column (item 7 of the first review). 3. Formalize tests (item 10) β€” a non-GUI `tests/` suite already exists in spirit (dev smoke scripts); move it into the repo. 4. Offline map (item 9) only if offline use becomes real. 5. Optional PyInstaller bundle (item 5).