# 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 4–5 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. ## `data/window.json` `{"geometry": "x++"}` – the window size and position, saved on close and restored on start. Git-ignored, per-machine. Safe to delete (window opens at its default size). ## `icon.png` (repo root) and `data/icon.png` `icon.png` in the repo root is the **bundled** launcher/window icon (committed). `data/icon.png` is only a runtime-drawn fallback the app writes if the bundled one is missing — git-ignored.