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>
8.7 KiB
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.
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_rowsdodf.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.csvexists but is 0 bytes (e.g. an interrupted write),pd.read_csvraisesEmptyDataErrorand 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(withswallow_exceptions) instead of the manualsleep(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 like2026-13-05or05.02.2026is stored verbatim, breaks date sorting, and never appears correctly on the timeline. - No recourse when geocoding fails. A row that comes back
⚠ nicht gefundensaves 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
Entrywithttkbootstrap.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 optionallat/lonfields 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 smalldata/orte.example.csvand 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 🟡
Today install is: apt install python3-tk, create a venv, pip install. That's
a one-time terminal session, which is acceptable but fragile (loose version
pins mean a future pip install could pull an incompatible pandas/folium).
Suggested changes
- Pin all four dependencies to exact versions and regenerate deliberately.
- Ship the
run.sh+.desktoplauncher from setup.md in the repo. - Optional: build a PyInstaller one-file bundle on a matching Linux box. It bundles Python, Tk, and all deps, so install becomes "copy one file + double-click" with no apt/venv step. Trade-off: you build it, and rebuild on dependency updates.
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 CartoDB, so
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;EditDialogandAppboth use them. - ✅ Region list is now the
GEOCODE_REGIONSconstant; map centre/zoom areMAP_DEFAULT_CENTER/MAP_DEFAULT_ZOOM. - ✅
generate_mapnowfit_boundsto the markers. - ⬜ Add a
tests/folder with pytest coverage forDataStore(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/mypyconfig.
Suggested order of remaining work
- Initial git commit (item 4), then decide on committing
orte.csv. - Pinned deps +
run.sh/.desktoplauncher in the repo (item 5). - Stable
idcolumn (item 7) and a modal duplicate prompt (item 8). - Formalize tests (item 10); offline map (item 9) only if offline use becomes real.