All checks were successful
Deploy Trade-In / changes (push) Successful in 5s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 31s
Deploy Trade-In / build-backend (push) Successful in 44s
Deploy Trade-In / deploy (push) Successful in 36s
Co-authored-by: lekss361 <lekss361@gendsgn.local> Co-committed-by: lekss361 <lekss361@gendsgn.local>
481 lines
23 KiB
Python
481 lines
23 KiB
Python
"""Daily recompute of the asking→sold correction ratios (#648 Stage 4).
|
||
|
||
ПРОБЛЕМА: asking_to_sold_ratios (migration 080) засеяна один раз derivation-CTE
|
||
(медиана SOLD ДКП за 12 мес vs медиана ASKING активных listings, per-rooms + global -1
|
||
fallback). Оценщик (estimator.py, Stage 3) читает её per-estimate (cache 300s) и домножает
|
||
asking-median прогноз на sold/asking. По мере ночного импорта новых ДКП-сделок
|
||
(rosreestr_dkp_import) ratio устаревает — нужен периодический пересчёт по тому же derivation.
|
||
|
||
TRUE-MIRROR REFRESH (флаг database-expert): 080-seed использовал ON CONFLICT DO UPDATE,
|
||
который при повторном прогоне ОБНОВЛЯЕТ существующие строки, но НЕ удаляет per-rooms строки
|
||
бакетов, упавших ниже порога 30/30 на более позднем прогоне (StaLE rows). Поэтому refresh
|
||
сначала DELETE FROM asking_to_sold_ratios WHERE district = '' (все строки #648), затем
|
||
заново гоняет ту же 080-derivation INSERT...SELECT. DELETE+INSERT в ОДНОЙ транзакции
|
||
(атомарно — таблица никогда не пуста mid-refresh). После commit таблица == свежий re-seed.
|
||
|
||
Задача синхронная (DB-only, никаких внешних HTTP-вызовов) — запускается in-app
|
||
scheduler'ом через trigger_asking_to_sold_ratio_run() (scheduler.py), по образцу
|
||
snapshot_listing_sources / import_rosreestr_dkp (sync task в run_in_executor).
|
||
|
||
Окно расписания 06:00-07:00 UTC — ПОСЛЕ rosreestr_dkp_import (04:00-06:00 UTC), чтобы
|
||
refresh потреблял свежие ДКП-сделки того же дня.
|
||
|
||
SQL derivation ниже — БАЙТ-В-БАЙТ та же логика, что seed в data/sql/080_asking_to_sold_ratios.sql
|
||
(deal_side / ask_side / per_bucket + deal_global / ask_global / global_row: трейлинг-12мес
|
||
окно, ppm²-полоса [_PPM2_MIN, settings.asking_ratio_ppm2_max] (default [30000,1200000]),
|
||
бакет LEAST(GREATEST(rooms,0),4), порог n_deals>=30 AND n_listings>=30 для per_rooms,
|
||
global -1 строка всегда). ON CONFLICT убран — DELETE идёт первым,
|
||
конфликтов нет (повторный прогон в одной tx невозможен, refresh = re-seed по семантике).
|
||
"""
|
||
|
||
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
|
||
|
||
# Нижняя граница ppm² — отсекает нежилые/технические сделки; не меняется.
|
||
_PPM2_MIN: int = 30_000
|
||
# Верхняя граница берётся из settings.asking_ratio_ppm2_max (default 1_200_000).
|
||
# QA-note: точное значение сверить с `SELECT max(price_per_m2) FROM deals
|
||
# WHERE source='rosreestr'` на проде — ceiling должен быть > max(ppm²) premium-сделок.
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# ── True-mirror cleanup: drop all #648 rows before re-derivation ──────────────
|
||
# district = '' — все строки #648 (district зарезервирован под #647, пока всегда '').
|
||
# Удаляем ПЕРЕД re-derive, чтобы бакеты, упавшие ниже порога 30/30, не оставались
|
||
# stale (ON CONFLICT DO UPDATE такие строки бы не тронул). В одной транзакции с INSERT.
|
||
_DELETE_SQL = text(
|
||
"""
|
||
DELETE FROM asking_to_sold_ratios
|
||
WHERE district = ''
|
||
"""
|
||
)
|
||
|
||
# ── Derivation + re-seed (БАЙТ-В-БАЙТ из 080, ON CONFLICT убран — DELETE идёт первым) ──
|
||
# deal_side / ask_side / per_bucket + deal_global / ask_global / global_row:
|
||
# sold_median = percentile_cont(0.5) по deals.price_per_m2 (source='rosreestr',
|
||
# ppm² ∈ [_PPM2_MIN, settings.asking_ratio_ppm2_max], deal_date >= CURRENT_DATE − 12 months),
|
||
# бакет LEAST(GREATEST(rooms,0),4).
|
||
# ask_median = percentile_cont(0.5) по listings.price_per_m2
|
||
# (is_active, та же ppm²-полоса [_PPM2_MIN, asking_ratio_ppm2_max]).
|
||
# per_rooms строки — только при n_deals>=30 AND n_listings>=30 AND ask>0 AND sold>0.
|
||
# global -1 строка (basis='global_fallback') — всегда (если ask>0 AND sold>0). window_months=12.
|
||
# Порог/окно — литералы; ppm²-полоса передаётся bind-параметрами :ppm2_min/:ppm2_max
|
||
# (безопасно от SQL-инъекций; CAST не нужен — psycopg v3 передаёт int напрямую).
|
||
_REDERIVE_SQL = text(
|
||
"""
|
||
WITH
|
||
-- SOLD медианы по бакетам комнат за трейлинг-12мес (ДКП Росреестра).
|
||
deal_side AS (
|
||
SELECT
|
||
LEAST(GREATEST(rooms, 0), 4) AS rooms_bucket,
|
||
percentile_cont(0.5) WITHIN GROUP (ORDER BY price_per_m2) AS sold_median,
|
||
COUNT(*) AS n_deals
|
||
FROM deals
|
||
WHERE source = 'rosreestr'
|
||
AND rooms IS NOT NULL
|
||
AND price_per_m2 BETWEEN :ppm2_min AND :ppm2_max
|
||
AND deal_date >= CURRENT_DATE - INTERVAL '12 months'
|
||
GROUP BY LEAST(GREATEST(rooms, 0), 4)
|
||
),
|
||
-- ASKING медианы по бакетам комнат среди ТЕКУЩИХ активных объявлений.
|
||
ask_side AS (
|
||
SELECT
|
||
LEAST(GREATEST(rooms, 0), 4) AS rooms_bucket,
|
||
percentile_cont(0.5) WITHIN GROUP (ORDER BY price_per_m2) AS ask_median,
|
||
COUNT(*) AS n_listings
|
||
FROM listings
|
||
WHERE is_active
|
||
AND rooms IS NOT NULL
|
||
AND price_per_m2 BETWEEN :ppm2_min AND :ppm2_max
|
||
GROUP BY LEAST(GREATEST(rooms, 0), 4)
|
||
),
|
||
-- Per-rooms строки: только бакеты с обеими сторонами, прошедшие порог 30/30 и ask>0.
|
||
-- Тонкие бакеты (n<30) сюда НЕ попадают → estimator делает fallback на -1.
|
||
per_bucket AS (
|
||
SELECT
|
||
d.rooms_bucket,
|
||
''::text AS district,
|
||
(d.sold_median / a.ask_median)::numeric AS ratio,
|
||
round(d.sold_median)::bigint AS sold_median,
|
||
round(a.ask_median)::bigint AS ask_median,
|
||
d.n_deals::int AS n_deals,
|
||
a.n_listings::int AS n_listings,
|
||
12 AS window_months,
|
||
'per_rooms'::text AS basis
|
||
FROM deal_side d
|
||
JOIN ask_side a USING (rooms_bucket)
|
||
WHERE d.n_deals >= 30
|
||
AND a.n_listings >= 30
|
||
AND a.ask_median IS NOT NULL
|
||
AND a.ask_median > 0
|
||
AND d.sold_median IS NOT NULL -- divide-safety (порог n_deals>=30 гарантирует)
|
||
AND d.sold_median > 0
|
||
),
|
||
-- SOLD медиана по ВСЕМ комнатам (без бакет-фильтра) за трейлинг-12мес — для global row.
|
||
deal_global AS (
|
||
SELECT
|
||
percentile_cont(0.5) WITHIN GROUP (ORDER BY price_per_m2) AS sold_median,
|
||
COUNT(*) AS n_deals
|
||
FROM deals
|
||
WHERE source = 'rosreestr'
|
||
AND rooms IS NOT NULL
|
||
AND price_per_m2 BETWEEN :ppm2_min AND :ppm2_max
|
||
AND deal_date >= CURRENT_DATE - INTERVAL '12 months'
|
||
),
|
||
-- ASKING медиана по ВСЕМ активным listings (без бакет-фильтра) — для global row.
|
||
ask_global AS (
|
||
SELECT
|
||
percentile_cont(0.5) WITHIN GROUP (ORDER BY price_per_m2) AS ask_median,
|
||
COUNT(*) AS n_listings
|
||
FROM listings
|
||
WHERE is_active
|
||
AND rooms IS NOT NULL
|
||
AND price_per_m2 BETWEEN :ppm2_min AND :ppm2_max
|
||
),
|
||
-- Global fallback строка rooms_bucket=-1 (пишется всегда, если ask>0).
|
||
global_row AS (
|
||
SELECT
|
||
-1 AS rooms_bucket,
|
||
''::text AS district,
|
||
(d.sold_median / a.ask_median)::numeric AS ratio,
|
||
round(d.sold_median)::bigint AS sold_median,
|
||
round(a.ask_median)::bigint AS ask_median,
|
||
d.n_deals::int AS n_deals,
|
||
a.n_listings::int AS n_listings,
|
||
12 AS window_months,
|
||
'global_fallback'::text AS basis
|
||
FROM deal_global d
|
||
CROSS JOIN ask_global a
|
||
WHERE a.ask_median IS NOT NULL
|
||
AND a.ask_median > 0
|
||
AND d.sold_median IS NOT NULL -- защита от пустого окна сделок (иначе ratio=NULL)
|
||
AND d.sold_median > 0
|
||
)
|
||
INSERT INTO asking_to_sold_ratios (
|
||
rooms_bucket, district, ratio, sold_median, ask_median,
|
||
n_deals, n_listings, window_months, basis
|
||
)
|
||
SELECT rooms_bucket, district, ratio, sold_median, ask_median,
|
||
n_deals, n_listings, window_months, basis FROM global_row
|
||
UNION ALL
|
||
SELECT rooms_bucket, district, ratio, sold_median, ask_median,
|
||
n_deals, n_listings, window_months, basis FROM per_bucket
|
||
"""
|
||
)
|
||
|
||
# ── Post-insert counters ──────────────────────────────────────────────────────
|
||
# Считываем итог из таблицы (всё ещё в той же транзакции — до commit): сколько строк
|
||
# записано всего, сколько per_rooms, был ли использован global -1 fallback.
|
||
_COUNTERS_SQL = text(
|
||
"""
|
||
SELECT
|
||
COUNT(*) AS rows_written,
|
||
COUNT(*) FILTER (WHERE basis = 'per_rooms') AS per_rooms_rows,
|
||
COUNT(*) FILTER (WHERE rooms_bucket = -1) AS used_global_fallback
|
||
FROM asking_to_sold_ratios
|
||
WHERE district = ''
|
||
"""
|
||
)
|
||
|
||
|
||
# ── Tiered refresh (#928): DELETE + re-derive for tier tables ────────────────
|
||
# атомарно DELETE WHERE district='' ПЕРЕД re-derive INSERT (те же семантика, что
|
||
# для asking_to_sold_ratios выше: stale строки не оставляем).
|
||
_DELETE_TIERED_ROWS_SQL = text(
|
||
"""
|
||
DELETE FROM asking_to_sold_ratios_tiered WHERE district = ''
|
||
"""
|
||
)
|
||
_DELETE_TIER_BOUNDS_SQL = text(
|
||
"""
|
||
DELETE FROM asking_to_sold_tier_bounds WHERE district = ''
|
||
"""
|
||
)
|
||
|
||
# ── Tiered derivation (BYTE-CONSISTENT с migration 098) ──────────────────────
|
||
# Те же CTE что в 098: bounds -> deal_tiered/ask_tiered -> tier_rows + all_rows + global_row.
|
||
# Bind params: :ppm2_min / :ppm2_max (как в _REDERIVE_SQL выше).
|
||
# CAST не нужен -- psycopg v3 передаёт int напрямую.
|
||
_REDERIVE_BOUNDS_SQL = text(
|
||
"""
|
||
WITH bounds AS (
|
||
SELECT
|
||
LEAST(GREATEST(rooms, 0), 4) AS rooms_bucket,
|
||
percentile_cont(0.3333) WITHIN GROUP (ORDER BY price_per_m2)::bigint AS t33,
|
||
percentile_cont(0.6667) WITHIN GROUP (ORDER BY price_per_m2)::bigint AS t66,
|
||
COUNT(*)::int AS n_listings
|
||
FROM listings
|
||
WHERE is_active
|
||
AND rooms IS NOT NULL
|
||
AND price_per_m2 BETWEEN :ppm2_min AND :ppm2_max
|
||
GROUP BY LEAST(GREATEST(rooms, 0), 4)
|
||
HAVING COUNT(*) > 0
|
||
)
|
||
INSERT INTO asking_to_sold_tier_bounds (rooms_bucket, district, t33, t66, n_listings)
|
||
SELECT rooms_bucket, ''::text, t33, t66, n_listings FROM bounds
|
||
"""
|
||
)
|
||
|
||
_REDERIVE_TIERED_SQL = text(
|
||
"""
|
||
WITH
|
||
bnd AS (
|
||
SELECT rooms_bucket, t33, t66 FROM asking_to_sold_tier_bounds WHERE district = ''
|
||
),
|
||
deal_tiered AS (
|
||
SELECT
|
||
LEAST(GREATEST(d.rooms, 0), 4) AS rooms_bucket,
|
||
CASE
|
||
WHEN d.price_per_m2 < b.t33 THEN 'low'
|
||
WHEN d.price_per_m2 < b.t66 THEN 'mid'
|
||
ELSE 'high'
|
||
END AS ppm2_tier,
|
||
d.price_per_m2
|
||
FROM deals d
|
||
JOIN bnd b ON b.rooms_bucket = LEAST(GREATEST(d.rooms, 0), 4)
|
||
WHERE d.source = 'rosreestr'
|
||
AND d.rooms IS NOT NULL
|
||
AND d.price_per_m2 BETWEEN :ppm2_min AND :ppm2_max
|
||
AND d.deal_date >= CURRENT_DATE - INTERVAL '12 months'
|
||
),
|
||
ask_tiered AS (
|
||
SELECT
|
||
LEAST(GREATEST(l.rooms, 0), 4) AS rooms_bucket,
|
||
CASE
|
||
WHEN l.price_per_m2 < b.t33 THEN 'low'
|
||
WHEN l.price_per_m2 < b.t66 THEN 'mid'
|
||
ELSE 'high'
|
||
END AS ppm2_tier,
|
||
l.price_per_m2
|
||
FROM listings l
|
||
JOIN bnd b ON b.rooms_bucket = LEAST(GREATEST(l.rooms, 0), 4)
|
||
WHERE l.is_active
|
||
AND l.rooms IS NOT NULL
|
||
AND l.price_per_m2 BETWEEN :ppm2_min AND :ppm2_max
|
||
),
|
||
deal_side_tier AS (
|
||
SELECT
|
||
rooms_bucket,
|
||
ppm2_tier,
|
||
percentile_cont(0.5) WITHIN GROUP (ORDER BY price_per_m2) AS sold_median,
|
||
COUNT(*)::int AS n_deals
|
||
FROM deal_tiered
|
||
GROUP BY rooms_bucket, ppm2_tier
|
||
),
|
||
ask_side_tier AS (
|
||
SELECT
|
||
rooms_bucket,
|
||
ppm2_tier,
|
||
percentile_cont(0.5) WITHIN GROUP (ORDER BY price_per_m2) AS ask_median,
|
||
COUNT(*)::int AS n_listings
|
||
FROM ask_tiered
|
||
GROUP BY rooms_bucket, ppm2_tier
|
||
),
|
||
tier_rows AS (
|
||
SELECT
|
||
d.rooms_bucket,
|
||
''::text AS district,
|
||
d.ppm2_tier,
|
||
(d.sold_median / a.ask_median)::numeric AS ratio,
|
||
round(d.sold_median)::bigint AS sold_median,
|
||
round(a.ask_median)::bigint AS ask_median,
|
||
d.n_deals,
|
||
a.n_listings,
|
||
12 AS window_months,
|
||
'per_rooms_tier'::text AS basis
|
||
FROM deal_side_tier d
|
||
JOIN ask_side_tier a USING (rooms_bucket, ppm2_tier)
|
||
WHERE d.n_deals >= 30
|
||
AND a.n_listings >= 30
|
||
AND a.ask_median IS NOT NULL
|
||
AND a.ask_median > 0
|
||
AND d.sold_median IS NOT NULL
|
||
AND d.sold_median > 0
|
||
),
|
||
deal_side_all AS (
|
||
SELECT
|
||
LEAST(GREATEST(rooms, 0), 4) AS rooms_bucket,
|
||
percentile_cont(0.5) WITHIN GROUP (ORDER BY price_per_m2) AS sold_median,
|
||
COUNT(*)::int AS n_deals
|
||
FROM deals
|
||
WHERE source = 'rosreestr'
|
||
AND rooms IS NOT NULL
|
||
AND price_per_m2 BETWEEN :ppm2_min AND :ppm2_max
|
||
AND deal_date >= CURRENT_DATE - INTERVAL '12 months'
|
||
GROUP BY LEAST(GREATEST(rooms, 0), 4)
|
||
),
|
||
ask_side_all AS (
|
||
SELECT
|
||
LEAST(GREATEST(rooms, 0), 4) AS rooms_bucket,
|
||
percentile_cont(0.5) WITHIN GROUP (ORDER BY price_per_m2) AS ask_median,
|
||
COUNT(*)::int AS n_listings
|
||
FROM listings
|
||
WHERE is_active
|
||
AND rooms IS NOT NULL
|
||
AND price_per_m2 BETWEEN :ppm2_min AND :ppm2_max
|
||
GROUP BY LEAST(GREATEST(rooms, 0), 4)
|
||
),
|
||
all_rows AS (
|
||
SELECT
|
||
d.rooms_bucket,
|
||
''::text AS district,
|
||
'all'::text AS ppm2_tier,
|
||
(d.sold_median / a.ask_median)::numeric AS ratio,
|
||
round(d.sold_median)::bigint AS sold_median,
|
||
round(a.ask_median)::bigint AS ask_median,
|
||
d.n_deals,
|
||
a.n_listings,
|
||
12 AS window_months,
|
||
'per_rooms_all'::text AS basis
|
||
FROM deal_side_all d
|
||
JOIN ask_side_all a USING (rooms_bucket)
|
||
WHERE d.n_deals >= 30
|
||
AND a.n_listings >= 30
|
||
AND a.ask_median IS NOT NULL
|
||
AND a.ask_median > 0
|
||
AND d.sold_median IS NOT NULL
|
||
AND d.sold_median > 0
|
||
),
|
||
deal_global AS (
|
||
SELECT
|
||
percentile_cont(0.5) WITHIN GROUP (ORDER BY price_per_m2) AS sold_median,
|
||
COUNT(*)::int AS n_deals
|
||
FROM deals
|
||
WHERE source = 'rosreestr'
|
||
AND rooms IS NOT NULL
|
||
AND price_per_m2 BETWEEN :ppm2_min AND :ppm2_max
|
||
AND deal_date >= CURRENT_DATE - INTERVAL '12 months'
|
||
),
|
||
ask_global AS (
|
||
SELECT
|
||
percentile_cont(0.5) WITHIN GROUP (ORDER BY price_per_m2) AS ask_median,
|
||
COUNT(*)::int AS n_listings
|
||
FROM listings
|
||
WHERE is_active
|
||
AND rooms IS NOT NULL
|
||
AND price_per_m2 BETWEEN :ppm2_min AND :ppm2_max
|
||
),
|
||
global_row AS (
|
||
SELECT
|
||
-1 AS rooms_bucket,
|
||
''::text AS district,
|
||
'all'::text AS ppm2_tier,
|
||
(d.sold_median / a.ask_median)::numeric AS ratio,
|
||
round(d.sold_median)::bigint AS sold_median,
|
||
round(a.ask_median)::bigint AS ask_median,
|
||
d.n_deals,
|
||
a.n_listings,
|
||
12 AS window_months,
|
||
'global_all'::text AS basis
|
||
FROM deal_global d
|
||
CROSS JOIN ask_global a
|
||
WHERE a.ask_median IS NOT NULL
|
||
AND a.ask_median > 0
|
||
AND d.sold_median IS NOT NULL
|
||
AND d.sold_median > 0
|
||
)
|
||
INSERT INTO asking_to_sold_ratios_tiered (
|
||
rooms_bucket, district, ppm2_tier, ratio, sold_median, ask_median,
|
||
n_deals, n_listings, window_months, basis
|
||
)
|
||
SELECT rooms_bucket, district, ppm2_tier, ratio, sold_median, ask_median,
|
||
n_deals, n_listings, window_months, basis FROM global_row
|
||
UNION ALL
|
||
SELECT rooms_bucket, district, ppm2_tier, ratio, sold_median, ask_median,
|
||
n_deals, n_listings, window_months, basis FROM all_rows
|
||
UNION ALL
|
||
SELECT rooms_bucket, district, ppm2_tier, ratio, sold_median, ask_median,
|
||
n_deals, n_listings, window_months, basis FROM tier_rows
|
||
"""
|
||
)
|
||
|
||
# ── Post-insert counters (tiered) ─────────────────────────────────────────────
|
||
_TIERED_COUNTERS_SQL = text(
|
||
"""
|
||
SELECT
|
||
(SELECT COUNT(*) FROM asking_to_sold_ratios_tiered WHERE district = '')
|
||
AS tiered_rows_written,
|
||
(SELECT COUNT(*) FROM asking_to_sold_tier_bounds WHERE district = '')
|
||
AS tier_bounds_written
|
||
"""
|
||
)
|
||
|
||
|
||
def recompute_asking_to_sold_ratios(db: Session, run_id: int) -> dict[str, int]:
|
||
"""Пересчитать asking_to_sold_ratios (TRUE-MIRROR refresh, #648 Stage 4).
|
||
|
||
Sync (вызывается scheduler-триггером в executor, как snapshot_listing_sources).
|
||
В ОДНОЙ транзакции (атомарно — таблица никогда не пуста mid-refresh):
|
||
1. DELETE FROM asking_to_sold_ratios WHERE district = '' — снести stale-строки.
|
||
2. Заново прогнать 080-derivation INSERT...SELECT (per_rooms при 30/30 + global -1).
|
||
Затем counters из таблицы, commit, mark_done. Семантика == re-seed миграции 080.
|
||
|
||
Финализирует scrape_runs (mark_done / mark_failed) и пишет counters.
|
||
|
||
Returns {"rows_written": N, "per_rooms_rows": M, "used_global_fallback": 0|1}.
|
||
"""
|
||
counters: dict[str, int] = {
|
||
"rows_written": 0,
|
||
"per_rooms_rows": 0,
|
||
"used_global_fallback": 0,
|
||
"tiered_rows_written": 0,
|
||
"tier_bounds_written": 0,
|
||
}
|
||
try:
|
||
# DELETE + re-derive INSERT в одной транзакции (НЕ коммитим между ними —
|
||
# таблица не должна остаться пустой, если INSERT упадёт).
|
||
db.execute(_DELETE_SQL)
|
||
db.execute(
|
||
_REDERIVE_SQL,
|
||
{"ppm2_min": _PPM2_MIN, "ppm2_max": settings.asking_ratio_ppm2_max},
|
||
)
|
||
|
||
row = db.execute(_COUNTERS_SQL).mappings().first()
|
||
if row is not None:
|
||
counters["rows_written"] = int(row["rows_written"] or 0)
|
||
counters["per_rooms_rows"] = int(row["per_rooms_rows"] or 0)
|
||
counters["used_global_fallback"] = int(row["used_global_fallback"] or 0)
|
||
|
||
# Tiered refresh (#928): DELETE + re-derive bounds + tier rows в той же транзакции.
|
||
# Атомарно — таблицы никогда не пустые mid-refresh (всё в одном commit ниже).
|
||
db.execute(_DELETE_TIERED_ROWS_SQL)
|
||
db.execute(_DELETE_TIER_BOUNDS_SQL)
|
||
db.execute(
|
||
_REDERIVE_BOUNDS_SQL,
|
||
{"ppm2_min": _PPM2_MIN, "ppm2_max": settings.asking_ratio_ppm2_max},
|
||
)
|
||
db.execute(
|
||
_REDERIVE_TIERED_SQL,
|
||
{"ppm2_min": _PPM2_MIN, "ppm2_max": settings.asking_ratio_ppm2_max},
|
||
)
|
||
tier_row = db.execute(_TIERED_COUNTERS_SQL).mappings().first()
|
||
if tier_row is not None:
|
||
counters["tiered_rows_written"] = int(tier_row["tiered_rows_written"] or 0)
|
||
counters["tier_bounds_written"] = int(tier_row["tier_bounds_written"] or 0)
|
||
|
||
db.commit()
|
||
runs_mod.mark_done(db, run_id, counters)
|
||
logger.info(
|
||
"recompute_asking_to_sold_ratios run_id=%d done: "
|
||
"rows_written=%d per_rooms_rows=%d used_global_fallback=%d "
|
||
"tiered_rows_written=%d tier_bounds_written=%d",
|
||
run_id,
|
||
counters["rows_written"],
|
||
counters["per_rooms_rows"],
|
||
counters["used_global_fallback"],
|
||
counters["tiered_rows_written"],
|
||
counters["tier_bounds_written"],
|
||
)
|
||
return counters
|
||
except Exception as exc:
|
||
logger.exception("recompute_asking_to_sold_ratios run_id=%d failed", run_id)
|
||
db.rollback()
|
||
runs_mod.mark_failed(db, run_id, str(exc)[:1000], counters)
|
||
raise
|