gendesign/tradein-mvp/backend/app/tasks/deactivate_stale_avito.py
bot-backend 42007e9b04
Some checks failed
Deploy Trade-In / changes (push) Successful in 5s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 30s
Deploy Trade-In / build-backend (push) Successful in 40s
Deploy Trade-In / deploy (push) Has been cancelled
feat(tradein): nightly task to deactivate stale avito listings (#759) (#862)
Co-authored-by: bot-backend <bot-backend@gendsgn.local>
Co-committed-by: bot-backend <bot-backend@gendsgn.local>
2026-05-31 07:19:37 +00:00

70 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Deactivate stale avito listings that have not been seen in >TTL days (#759).
Нечасто обновляемые объявления на Avito (last_seen_at < NOW() - TTL) помечаются
is_active=false, чтобы coverage/estimator denominators не раздувались мёртвыми
объявлениями. Строки НЕ удаляются — история нужна для бэктеста (#667).
Только source='avito'. Cian/Yandex не затрагиваются.
Задача синхронная (DB-only, никаких внешних HTTP-вызовов) — запускается in-app
scheduler'ом через trigger_deactivate_stale_avito_run() (scheduler.py),
по образцу snapshot_listing_sources / recompute_asking_to_sold_ratios
(sync task в run_in_executor).
TTL берётся из settings.avito_stale_ttl_days (env AVITO_STALE_TTL_DAYS, default 10).
"""
from __future__ import annotations
import logging
from sqlalchemy import text
from sqlalchemy.orm import Session
from app.core.config import settings
from app.services import scrape_runs as runs_mod
logger = logging.getLogger(__name__)
# UPDATE: помечаем is_active=false для avito-объявлений, не виденных >TTL дней.
# CAST(:ttl_days || ' days' AS interval) — psycopg v3 safe (никаких :param::type).
# Только source='avito' и is_active=true — чтобы не трогать уже неактивные строки
# и не затрагивать cian/yandex.
_DEACTIVATE_SQL = text(
"""
UPDATE listings
SET is_active = false
WHERE source = 'avito'
AND is_active = true
AND last_seen_at < NOW() - CAST(:ttl_days || ' days' AS interval)
"""
)
def deactivate_stale_avito_listings(db: Session, run_id: int) -> dict[str, int]:
"""Пометить is_active=false все avito-объявления с last_seen_at > TTL дней назад.
Sync (вызывается scheduler-триггером в executor, как snapshot_listing_sources).
Один UPDATE в транзакции. Финализирует scrape_runs (mark_done / mark_failed).
Returns {"deactivated": N} — количество обновлённых строк.
"""
counters: dict[str, int] = {"deactivated": 0}
try:
result = db.execute(_DEACTIVATE_SQL, {"ttl_days": settings.avito_stale_ttl_days})
counters["deactivated"] = result.rowcount or 0
db.commit()
runs_mod.mark_done(db, run_id, counters)
logger.info(
"deactivate_stale_avito run_id=%d done: deactivated=%d (ttl_days=%d)",
run_id,
counters["deactivated"],
settings.avito_stale_ttl_days,
)
return counters
except Exception as exc:
logger.exception("deactivate_stale_avito run_id=%d failed", run_id)
db.rollback()
runs_mod.mark_failed(db, run_id, str(exc)[:1000], counters)
raise