#647-2 (P0, инфра — клиентскую цену НЕ меняет). Делает mv_quarter_price_index (#760) читаемым estimator'ом за O(1) + периодический refresh. - tradein 085_quarter_price_index_fdw.sql: FOREIGN TABLE quarter_price_index над main public.mv_quarter_price_index через существующий server gendesign_remote (паттерн 060_postgres_fdw_extension.sql, per-table). Колонки 1:1 с MV (varchar(30)/float8/bigint/text/timestamptz). DROP IF EXISTS идемпот. - main 99b_grant_quarter_price_index_fdw.sql: GRANT SELECT на MV роли tradein_fdw_reader (иначе foreign table падает; зеркало 122_grant_rosreestr). - main refresh: Celery beat (pg_cron нет). quarter_price_index_refresh.py — chained REFRESH mv_quarter_price_per_m2 → mv_quarter_price_index CONCURRENTLY; task refresh_quarter_price_index.py; beat-entry 'refresh-quarter-price-index' cron '0 2 5 * *' (05:00 MSK 5-го, после ekb-districts-medians). Lookup-индекс: MV уже несёт UNIQUE mv_quarter_price_index_uq (O(1) remote); foreign table не нуждается в локальном. Валидация (postgres-gendesign): MV 1972 строки, 0 NULL, UNIQUE indisunique=true, REFRESH CONCURRENTLY валиден. Разблокирует #647-3 (estimator integration, за гейтом #763).
45 lines
1.5 KiB
Python
45 lines
1.5 KiB
Python
"""Celery task: refresh mv_quarter_price_per_m2 → mv_quarter_price_index chain.
|
|
|
|
Scheduled via hardcoded beat entry in workers/beat_schedule.py:
|
|
'refresh-quarter-price-index' — monthly on the 5th at 05:00 MSK (02:00 UTC).
|
|
Runs after 'refresh-ekb-districts-medians' (04:00 MSK same day) so that
|
|
deals data is settled before the price index is recomputed.
|
|
|
|
Issue: #762.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from app.core.db import SessionLocal
|
|
from app.services.site_finder.quarter_price_index_refresh import refresh_quarter_price_index
|
|
from app.workers.celery_app import celery_app
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@celery_app.task(
|
|
bind=True,
|
|
name="tasks.refresh_quarter_price_index.refresh_quarter_price_index_chain",
|
|
max_retries=2,
|
|
)
|
|
def refresh_quarter_price_index_chain(self: Any) -> dict[str, Any]:
|
|
"""Refresh mv_quarter_price_per_m2 then mv_quarter_price_index in sequence.
|
|
|
|
Both MVs are refreshed CONCURRENTLY (non-blocking). Falls back to
|
|
non-concurrent if the MV is found unpopulated (edge case on first run).
|
|
|
|
Returns result dict for Celery task result store / logging.
|
|
"""
|
|
db = SessionLocal()
|
|
try:
|
|
count = refresh_quarter_price_index(db, concurrently=True)
|
|
logger.info("refresh_quarter_price_index_chain: completed, index rows=%d", count)
|
|
return {"status": "ok", "mv_quarter_price_index_rows": count}
|
|
except Exception as e:
|
|
logger.exception("refresh_quarter_price_index_chain failed: %s", e)
|
|
raise
|
|
finally:
|
|
db.close()
|