Add materialized view aggregating objective_corpus_room_month JOIN objective_complex_mapping (matched via objective_complex_name) за last 24 months, grouped by (domrf_obj_id, room_bucket). Cyrillic 'студия' normalised к ASCII 'studio' для consistency с layout_signature.py (PR A). Tables / cardinality (verified live на проде): - objective_corpus_room_month: 15 738 EKB rows, monthly 2025-05..2026-05 - objective_complex_mapping: 114 mapped EKB objects (7.5% coverage) - Expected MV size: ~459 rows (114 objs × ~4 room_buckets) - EXPLAIN: Hash Join 19k×114 → 8k pre-agg → 459 groups, no extra indexes needed UNIQUE INDEX (obj_id, room_bucket) enables REFRESH CONCURRENTLY (used by Python helper layout_velocity_refresh.py — НЕ scheduled в этом PR, будет triggered manually или через celery beat в follow-up). Migration applied auto-через deploy.yml (see .claude/rules/sql.md). Smoke verification — qa-tester after deploy (test_layout_velocity_mv.py removed because conftest.py / db fixture infra не существует в проекте; проще smoke через прод SELECT COUNT после migration apply).
42 lines
1.5 KiB
Python
42 lines
1.5 KiB
Python
"""Refresh helper for mv_layout_velocity (Issue #113 PR B).
|
|
|
|
Not scheduled automatically in this PR — intended for manual invocation or
|
|
a Celery beat task in a follow-up issue.
|
|
|
|
Usage example (manual, via psql-connected shell or admin endpoint):
|
|
from sqlalchemy.orm import Session
|
|
from app.services.site_finder.layout_velocity_refresh import refresh_layout_velocity
|
|
|
|
count = refresh_layout_velocity(db)
|
|
# logs: "mv_layout_velocity refreshed: 459 rows"
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from sqlalchemy import text
|
|
from sqlalchemy.orm import Session
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def refresh_layout_velocity(db: Session, *, concurrently: bool = True) -> int:
|
|
"""REFRESH MATERIALIZED VIEW mv_layout_velocity.
|
|
|
|
Args:
|
|
db: SQLAlchemy Session (sync).
|
|
concurrently: When True, uses REFRESH CONCURRENTLY — non-blocking but
|
|
requires the unique index mv_layout_velocity_pk to exist (created
|
|
by 94_mv_layout_velocity.sql). Pass False only for the very first
|
|
populate or when the MV was just recreated.
|
|
|
|
Returns:
|
|
Row count in the MV after refresh (for observability / alerting).
|
|
"""
|
|
mode = "CONCURRENTLY" if concurrently else ""
|
|
db.execute(text(f"REFRESH MATERIALIZED VIEW {mode} mv_layout_velocity"))
|
|
row = db.execute(text("SELECT COUNT(*) FROM mv_layout_velocity")).first()
|
|
count = int(row[0]) if row else 0
|
|
logger.info("mv_layout_velocity refreshed: %d rows", count)
|
|
return count
|