All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m54s
CI / backend-tests (pull_request) Successful in 17m0s
11 тасок объявляли `max_retries=2`, но ретраи не реализовывали: ни `autoretry_for` в декораторе, ни вызова `self.retry()` в теле. Celery в таком виде параметр не применяет — при исключении таска падает с первой попытки. Читающий код видит «до 3 попыток», а их одна. Убран `max_retries` у: cbr_macro_sync, rosstat_macro_sync, developer_registry_refresh, location_refresh, mv_sales_tracker_refresh, refresh_analytics, refresh_layout_velocity, refresh_quarter_price_index, scrape_objective.sync_objective_group, supply_layers_refresh, scrape_kn.scrape_kn_region. Заодно убран `bind=True` там, где `self` не использовался вовсе; в `scrape_kn_region` он оставлен — `self.request.id` пишется в kn_scrape_log. Не тронуты и не должны быть: `resume_kn_run` (max_retries=12 + настоящий self.retry()), `nspd_sync`/`scrape_cadastre` (autoretry_for), `nspd_geo`/`objective_etl` (max_retries=0 — честное «ретраев нет»). Гейт `test_2464_retry_config_is_real.py` разбирает AST всех модулей `app/workers/tasks/` и требует: если декоратор объявляет ненулевой max_retries, в нём есть autoretry_for либо в теле функции есть self.retry(). Три таски из одиннадцати гейт нашёл сверх списка эпика. Проверка гейта: с фиксом зелено, при возврате `max_retries=2` в supply_layers_refresh — красно с указанием на эту таску. Плюс два контроля: гейт видит ≥20 тасок (не молчит из-за пустой выборки) и признаёт обе законные формы ретраев. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
64 lines
3.3 KiB
Python
64 lines
3.3 KiB
Python
"""Celery task: пересчёт district-level индексов локаций → `location` (#948 Part B, §8.2).
|
|
|
|
Прогоняет refresh_locations (app/services/site_finder/locations.py): итерирует 8
|
|
админ-районов ЕКБ, для каждого считает индексы инфра/конкуренция/спрос/будущее-
|
|
предложение (reuse per-district forecast-функций, #1129-кэшированы) и апсертит в
|
|
`location`. ON CONFLICT (district_name) делает прогон идемпотентным.
|
|
|
|
Расписание — еженедельно (district-индексы меняются медленно: рынок поглощает сток
|
|
неделями, будущее предложение — слой supply-layers, который сам обновляется
|
|
еженедельно по понедельникам). Регистрируется в beat_schedule.py ПОСЛЕ supply-layers
|
|
(Mon 06:00) — чтобы future_supply-индекс считался по СВЕЖЕМУ складу предложения.
|
|
|
|
Детерминированно, без LLM. SAVEPOINT per-row внутри refresh_locations (backend.md):
|
|
сбойный район не валит остальные.
|
|
|
|
Mirror conventions (cbr_macro_sync / supply_layers_refresh): SessionLocal() +
|
|
try/finally close, logger (не print), задача техническая — не управляется через
|
|
job_settings, добавляется в beat_schedule hardcoded-блок.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from app.core.db import SessionLocal
|
|
from app.services.site_finder.locations import refresh_locations
|
|
from app.workers.celery_app import celery_app
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# Ретраев здесь нет, и параметров, обещающих их, быть не должно (#2464). Стояло
|
|
# `bind=True, max_retries=2`, но self не использовался, self.retry() не вызывался и
|
|
# autoretry_for задан не был — конфигурация не имела эффекта. Соседи, где ретраи
|
|
# нужны, задают их явно: autoretry_for (nspd_sync, scrape_cadastre) или self.retry()
|
|
# (scrape_kn).
|
|
@celery_app.task(
|
|
name="tasks.location_refresh.location_refresh",
|
|
)
|
|
def location_refresh(region: str | None = None) -> dict[str, Any]:
|
|
"""Пересчитать + upsert-нуть district-level индексы по всем районам в `location`.
|
|
|
|
Идемпотентно (ON CONFLICT по district_name). Graceful: сбойный район
|
|
откатывается изолированно (SAVEPOINT), не роняя прогон.
|
|
|
|
Args:
|
|
region: метка региона в колонку location.region (None = ЕКБ/Свердл.).
|
|
|
|
Returns:
|
|
Счётчики refresh_locations: {"districts": N, "upserted": M, "failed": K}.
|
|
"""
|
|
db = SessionLocal()
|
|
try:
|
|
result = refresh_locations(db, region=region)
|
|
logger.info(
|
|
"location_refresh: districts=%s upserted=%s failed=%s",
|
|
result["districts"],
|
|
result["upserted"],
|
|
result["failed"],
|
|
)
|
|
return result
|
|
finally:
|
|
db.close()
|