These are the scripts behind the ~1550-place seed batches already
loaded into the DB (moscow_top1500_gen.yaml etc.) — keeping them
alongside the seed YAMLs for future re-runs/extensions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Five behavior changes from live testing feedback:
1. Narration no longer leads with the place title — the body text
already opens with the name, so it was read twice.
2. "Done" is now derived from a persisted set of listened place IDs
(UserPrefsDataStore.listenedPlaceIds), not an in-memory index set
that got wiped on every load. A place that's ever been narrated to
completion — in the guide list or the detail screen — is never
auto-narrated again, including across the 60s re-scan and app
restarts. Auto-advance now skips straight to the next unlistened
place instead of walking sequentially. Settings has a new "reset
listened places" button.
3. TtsManager is now keyed by place id: calling speak() for the place
that's already playing just re-attaches the onDone callback instead
of restarting via QUEUE_FLUSH. Fixes opening a place's detail
screen while the guide list is already narrating it restarting
playback from the beginning. Marking a place "listened" now also
lives in TtsManager itself (on natural onDone, not onError/onStop),
so it's correct regardless of which screen was driving playback.
4. Settings now shows "Listened: X of Y (Z%)" against the total place
count for the city.
5. Search radius changed from 10km to 2km (client default in
PlacesRepository/MapViewModel, and the backend's own default for
consistency) — but the result count is uncapped, same as before;
every place within the radius is returned regardless of how many
that is.
Caught a real bug while testing the "skip listened" change: the new
listenedPlaceIds collector ran in a separate coroutine that hadn't
necessarily delivered its first value before the initial loadNearby()
call, so freshly-loaded listened state could be missed on cold start.
Fixed by awaiting listenedPlaceIds.first() synchronously before the
first load, with a separate .drop(1) collector for later changes
(e.g. the reset button). Also hardened GuideViewModelTest with
try/finally around viewModelScope.cancel() — a failing assertion was
skipping cleanup and turning into a 5+ minute hang instead of a fast
failure, since the 60s re-scan loop was never cancelled.
Verified: testDebugUnitTest passes (4/4), assembleDebug produces a
working APK, sent to Telegram. Backend pytest still passes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Loaded and fixed four seed files the user generated with other tools:
- moscow_top1500_gen.yaml (1500 places): every single entry had a
YAML-breaking bug — the "long" variant's second paragraph was
emitted with zero indentation, breaking the block scalar in all
1500 cases identically. Also ~95% of entries had the literal string
"null" instead of a real null for empty optional fields. Fixed both
mechanically (reindent + string replace) and verified structurally
(categories valid, coords sane, no empty content, no dup slugs).
Content quality caveat (flagged to and accepted by the user): ~99%
of entries have identical short/medium text, and "long" is just
short + one bonus paragraph — shallow but broad coverage.
- moscow_top1500.yaml, moscow_batch1.yaml, moscow_churches.yaml
(15/18/25 places): these are genuinely well-researched, differentiated
content (Kremlin cathedrals/palaces, monasteries) comparable in
quality to the original hand-written seed files. moscow_top1500.yaml
had a different corruption — every line had had a stray "N|" or
"N|N|" row-number prefix baked in (looks like terminal output with
line numbers got saved as file content) — stripped mechanically.
These three files heavily overlap each other (~37 unique slugs
across 58 entries) and 8 slugs overlap the generic _gen file;
loaded in an order where the better-written content wins on upsert.
Database now holds 1556 places (was 27). Verified via direct DB count,
the local API, and the public https://guidetest.vrubel.xyz/ endpoint —
all agree. Backend pytest suite still passes. No Android changes, so
no APK rebuild needed; the existing installed app will see the new
data on its next request to the already-updated live backend.
Known follow-up (not fixed here): a handful of real-world landmarks
now have multiple near-duplicate entries under different slugs (e.g.
"Церковь Ризоположения" vs "Церковь Ризоположения Московского
Кремля"), since the source files independently slugified the same
places differently. Left for a dedicated dedup pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Documents the seed YAML format (fields, category enum, expected
short/medium/long narration length) with one real filled-in example
and a fill-in-the-blank placeholder block, so new locations can be
submitted in a format seed_loader.py already understands and loads by
upserting on slug.
Metro Solntsevo, the district's central park/pond (Bogdanova St),
Meshchersky forest park, the Levenson dacha (a Shekhtel-designed 1900
Art Nouveau landmark), and the Peredelkino writers' village — all
researched with real coordinates/history, in RU/EN x short/medium/long.
Verified against the live nearby endpoint: all 5 are found from the
user's actual coordinates (55.662303, 37.425486), radius expanding to
5100m to cover the farthest (Peredelkino village, ~5012m out).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Request-level logging middleware (method/path/status/duration),
per-module loggers in routes/services (nearby search radius
expansion, 404s, name search), LOG_LEVEL setting wired through
docker-compose, and seed_loader converted from print() to logging.
Verified: pytest passes, and manually confirmed log lines appear for
both successful and 404 requests via `docker compose logs api`.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Gorky Park (6), Red Square area (10), and Moscow-City (6) landmarks
with real historical/architectural facts, each with short/medium/long
narration text in Russian and English. Widened built_year to
varchar(128) since several entries need more than a bare year (e.g.
demolition/reconstruction ranges).
Verified end-to-end against the live backend: all 22 places load via
seed_loader, nearby search returns correctly distance-sorted results
with radius expansion in sparser areas (confirmed at Gorky Park,
900m), and place detail/search-fallback endpoints return full
bilingual content. Full pytest suite passes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
FastAPI service backed by PostgreSQL+PostGIS: cities/places/place_content
schema (normalized per-language, per-length content for easy future
re-ingestion), Alembic migration, an iterative-radius-expansion nearby
search endpoint, a name-search fallback for denied-location flows, and
a YAML seed loader CLI. Verified end-to-end against a live Docker
Postgres+PostGIS instance (migration, city seed, place/content CRUD,
and nearby radius expansion all confirmed working via curl).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>