feat(scrapers): segment-aware generic deactivate_stale for yandex/cian #1558
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#1558
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/deactivate-stale-generic-segment-aware"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Generalizes the avito-only stale-listing deactivation (#759) into a per-source, segment-aware task and enables it for yandex/cian vtorichka only.
Why segment-aware (not a blanket TTL like avito)
Verified on prod (2026-06-16):
avito_city_sweepruns daily and works →deactivate_stale_avitoTTL=10 is safe (251 active, all fresh).yandex_city_sweepis narrow (5-anchor nightly sweep,pages_per_anchor=2) — only 48 of 3969 active rows refreshed in 14d.cian_city_sweepis disabled (enabled=false); onlycian_history_backfilldrips ~200/day — only 28 of 15067 active cian rows refreshed in 14d.So for yandex/cian, "not seen in N days" means "our scraper stopped covering it," not "it sold." A blanket TTL would deactivate ~19k rows including 9659 live first-party novostroyki. Therefore yandex/cian deactivate only
listing_segment='vtorichka'at TTL=30; novostroyki and NULL-segment are protected. avito behaviour unchanged (all segments, TTL=10).TTL=30 immediate blast radius is 0 rows (oldest active vtorichka last_seen is ~26d) — it only catches genuinely-aged resale going forward.
Changes
deactivate_stale_listings(db, run_id, *, listing_source, ttl_days, segments)generic fn (two SQL variants: all-segments /= ANY(CAST(:segments AS text[]))).deactivate_stale_avito_listingskept as a back-compat wrapper.source.startswith("deactivate_stale_")→trigger_deactivate_stale_run, readinglisting_source/ttl_days/segmentsfromdefault_paramsjsonb.114_scrape_schedules_seed_deactivate_stale_yandex_cian.sql: seedsdeactivate_stale_yandex+deactivate_stale_cian(vtorichka, TTL=30, window 07-08 UTC), idempotentON CONFLICT DO NOTHING.segments is None⇒ all segments;segments=[]⇒ none (ANY(ARRAY[])matches nothing) — guards against an accidental empty-list wiping a whole source.Test plan
uv run pytestfull gate: 1834 passed, 2 deselecteduv run ruff checkcleantrigger_deactivate_stale_avito_runleft as a post-merge cleanup)Refs #759
Generalize the avito-only stale-listing deactivation into a per-source, segment-aware task and enable it for yandex/cian vtorichka. Why segment-aware (not blanket TTL like avito): yandex/cian city sweeps do not maintain full inventory coverage (cian_city_sweep disabled, yandex narrow 5-anchor nightly sweep refreshes ~48/day of 3969 active). A blanket TTL would deactivate live inventory — including 9659 active first-party novostroyki — because 'not seen in N days' means 'not re-covered', not 'sold'. So yandex/cian deactivate ONLY listing_segment='vtorichka' at TTL=30; novostroyki and NULL-segment are protected. avito unchanged (all segments, TTL=10). - deactivate_stale_listings(db, run_id, *, listing_source, ttl_days, segments) generic fn; deactivate_stale_avito_listings kept as back-compat wrapper. - scheduler dispatch: source.startswith('deactivate_stale_') -> generic trigger reading listing_source/ttl_days/segments from default_params jsonb. - 114 seed: deactivate_stale_yandex + deactivate_stale_cian (vtorichka, TTL=30). - segments is None => all segments; [] => none (ANY(ARRAY[]) matches nothing). Refs #759Deep review — APPROVE + merged (squash).
Verified at head
0267d517:if segments is not Noneguard correct — empty list [] matches nothing (not all-segments), None = all-segments. avito compat wrapper passes segments=None.Optional post-merge cleanup (non-blocking): trigger_deactivate_stale_avito_run is now dead code, kept only for a test pin; and consider
git merge maininto future branches to avoid the stale-base diff artifact.