# Overview – what the app does
There is a **Hilfe** menu with "Nach Update suchen" (self-update via `git`, see
[setup.md](setup.md#updating)) and "Version…".
The app is a single window: a **DRK-red header bar**, two tabs, and a
**colour-coded status bar** at the bottom (grey = neutral, green = success,
orange = warning, red = error; transient messages fade back to neutral after
~8 s). The window title shows the total entry count, and the window remembers
its size and position between sessions (`data/window.json`). The UI font is
enlarged for readability (`UI_FONT_SIZE`).
## 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.
The PLZ field only accepts digits.
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.
- Select a row and press Delete, use the **"Auswahl entfernen"**
button, or click the `✕` cell.
- Right-click a row for *Löschen* / *Koordinaten erneut suchen*.
3. **Action bar**
- **Alle speichern** – appends every queued row to `orte.csv`. If any row
looks like a duplicate (same date + city + PLZ), a **modal** lists them and
asks whether to save anyway.
- **Warteschlange leeren** – discards the queue (asks for confirmation).
- **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`.
- **Columns:** Datum, Ort, PLZ, and **Karte** – a status column showing `✓` when
the row has coordinates or a red `fehlt` when it doesn't (the whole row is red
too). The raw lat/lon numbers live in the edit dialog, not this table.
- **Search box** – live filter across date, city, and PLZ (substring match).
- **Sortable columns** – click a header to sort; clicking again reverses. The
active column shows a ▲/▼ arrow. Default sort is by date, newest first.
Sorting by **Karte** groups the rows without coordinates together.
- **"Nur ohne Koordinaten"** toggle – filters to entries that have no
coordinates yet. The hint line always shows the total count, plus how many are
missing coordinates.
- **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: **Esri "World Light Gray"** (`MAP_TILES` + a labels overlay) – a
light, muted basemap that needs no API key and no `Referer`. Initial view
centred on `[49.0, 9.0]`, zoom 8; auto-fits to the markers when there is more
than one.
- 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 dialog says so (and the status
bar shows a warning).
> The generated HTML pulls Leaflet, jQuery and Bootstrap from CDNs, and the map
> tiles from `server.arcgisonline.com`, so the map needs an internet connection
> to render even though the data is local. To change the look, edit `MAP_TILES` /
> `MAP_TILES_LABELS` near the top of `app.py` — the comment there lists other
> no-key Esri layers (Street, Topo). `tile.openstreetmap.org` was tried first
> but its tile-usage policy **403s** pages opened from `file://` (no `Referer`);
> `CartoDB positron` needs an API key.
## Geocoding behaviour
`geocode_city(city, postal_code)` tries a series of queries in order and returns
the first hit:
1. `" , Germany"` (only if a PLZ was given)
2. `", Baden-Württemberg, Germany"`
3. `", Hessen, Germany"`
4. `", 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.