gendesign/tradein-mvp/backend/app/services
bot-backend 850642548e fix(tradein): atomic conditional quota increment — close TOCTOU (#747)
check_and_raise (SELECT) and increment (unconditional UPSERT) were 5-40s apart
(the estimate scrapes run between them), so two concurrent /estimate at used=14
both passed the check and both incremented → used=16, pilot exceeds 15/mo.

Make increment atomic-conditional: ON CONFLICT DO UPDATE SET used=used+1
WHERE used < :lim RETURNING used → returns bool. The WHERE applies only to the
DO UPDATE branch, so a fresh insert (used=1) always succeeds; a conflicting row
already at the limit is not updated → RETURNING empty → False. trade_in raises
429 when increment returns False. check_and_raise kept as a fast pre-check.

Tests: 4 new (atomic refuse-at-limit incl. used stays == limit not +2; fresh
insert succeeds; returns bool; unlimited no-op) via an in-memory fake modeling
the conditional UPSERT (CI has no Postgres). pytest -k quota → 36 passed.

Refs #747
2026-05-30 18:49:41 +03:00
..
exporters chore(tradein): close pre-commit ruff gap + add CI pytest gate (#666) (#675) 2026-05-29 19:18:48 +00:00
matching feat(tradein): same-building матчинг аналогов по house_id (#6) 2026-05-28 13:55:51 +05:00
scrapers fix(scrapers): avito listing_date from sortTimeStamp JSON (#726) (#729) 2026-05-30 14:13:38 +00:00
__init__.py feat: add tradein-mvp subproject (Trade-In Estimator под /trade-in) 2026-05-21 00:25:39 +03:00
account_quota.py fix(tradein): atomic conditional quota increment — close TOCTOU (#747) 2026-05-30 18:49:41 +03:00
brand.py feat: add tradein-mvp subproject (Trade-In Estimator под /trade-in) 2026-05-21 00:25:39 +03:00
cache.py feat(tradein): Phase 3.2 — /api/v1/search endpoint + Redis cache (#479) 2026-05-23 14:44:22 +00:00
cian_session.py fix(tradein): CIAN_REQUIRED_COOKIES — add real auth cookies (DMIR_AUTH + Cian session) (#480) 2026-05-23 14:52:24 +00:00
dadata.py feat(tradein): DaData /suggest tier в /suggest endpoint (PR Q2) 2026-05-27 19:08:58 +05:00
estimator.py fix(tradein): sync n_analogs to anchor count when anchor drives headline (#695) (#739) 2026-05-30 15:25:35 +00:00
geocoder.py feat(tradein): DaData /suggest tier в /suggest endpoint (PR Q2) 2026-05-27 19:08:58 +05:00
house_metadata.py feat(tradein): postgres_fdw live read of gendesign.cad_buildings (replaces snapshot) (#493) 2026-05-24 08:57:30 +00:00
image_sanitizer.py fix(tradein-photos): sanitize uploaded images via Pillow re-encode (#511) 2026-05-24 11:32:04 +00:00
scheduler.py feat(scrapers): wire N1.ru city sweep — pipeline + scheduler + admin endpoint (#575) (#745) 2026-05-30 15:43:38 +00:00
scrape_pipeline.py feat(scrapers): wire N1.ru city sweep — pipeline + scheduler + admin endpoint (#575) (#745) 2026-05-30 15:43:38 +00:00
scrape_runs.py fix(tradein/avito): anti-bot hardening — sleep + abort + shared session (#487) 2026-05-23 20:08:40 +00:00
scraper_settings.py feat(tradein): global scraper delay setting (applies across all scrapers) (#485) 2026-05-23 15:43:28 +00:00
search_query.py feat(tradein): Phase 3.2 — /api/v1/search endpoint + Redis cache (#479) 2026-05-23 14:44:22 +00:00