gendesign/backend/tests/services
lekss361 990c109756 fix(#234): NSPD harvest ETA badge + SETNX dedupe (Sub-PR C)
Backend (quarter_dump_lookup.py):
- _acquire_harvest_lock: Redis SETNX TTL=120s на quarter, защищает от burst
  N concurrent analyze, ставящих N одинаковых harvest task в очередь
- _trigger_harvest: использует lock перед apply_async, возвращает False если
  lock уже взят (другой запрос триггернул раньше)
- make_empty_result/EMPTY_DUMP_RESULT: новое поле harvest_eta_seconds в
  nspd_dump dict, типичный harvest_quarter = 60с
- /analyze: пробрасывает поле через nspd_dump dict (нет typed schema —
  response_model=None для /analyze endpoint, dict уходит как есть)

Frontend (NspdFreshnessBadge, NspdZoningBlock):
- Countdown «НСПД: загрузка ~Nс» вместо бесконечного спиннера
- После остановки countdown (remaining=0) NspdZoningBlock показывает
  «загрузка дольше обычного» + ссылку на ПКК вместо infinite skeleton

Tests: 5 новых unit + 2 для empty_result schema (всего +7, pass)

Closes #234 (UX-side; data-side resolves когда Sub-PR B + D merged).
2026-05-17 09:13:06 +03:00
..
__init__.py feat(cadastre): bulk_harvest worker + grid-walker + admin API (#168 PR3/5) (#171) 2026-05-15 13:31:32 +03:00
test_cadastre_bulk.py test(deploy): validate Forgejo Actions end-to-end flow (#189) 2026-05-15 21:55:04 +00:00
test_ekburg_permits.py feat(permits): ЕКБ РНС/РВЭ xlsx parser + schema + ETL (#105 PR A) (#211) 2026-05-16 11:41:21 +00:00
test_nspd_denorm.py feat(nspd): denorm nspd_parcels + nspd_buildings tables (#94 PR3 of 4) 2026-05-16 20:20:03 +03:00
test_objective_backfill.py feat(etl): objective_complex_mapping backfill via pg_trgm (#203) (#214) 2026-05-16 12:47:09 +00:00
test_quarter_dump_lookup.py fix(#234): NSPD harvest ETA badge + SETNX dedupe (Sub-PR C) 2026-05-17 09:13:06 +03:00