Черновик регулярного сбора по Москве и области: сетка якорей, ID площадок, гео-профили ДомКлика #3533
2 changed files with 113 additions and 24 deletions
|
|
@ -21,6 +21,9 @@ from scraper_kit.orchestration.pipeline import (
|
|||
MOSCOW_ANCHOR_RADIUS_M,
|
||||
MOSCOW_ANCHORS,
|
||||
MOSCOW_CORE_BBOX_DEG,
|
||||
MOSCOW_OBLAST_ANCHOR_RADIUS_M,
|
||||
MOSCOW_OBLAST_CITY_ANCHORS,
|
||||
MOSCOW_OBLAST_CLUSTER_ANCHORS,
|
||||
MULTI_CITY_SLUGS,
|
||||
get_city_anchor_point,
|
||||
get_city_anchors,
|
||||
|
|
@ -179,6 +182,36 @@ def test_moscow_oblast_anchors_are_satellite_cities() -> None:
|
|||
assert len(combined) == len(MOSCOW_ANCHORS) + len(anchors)
|
||||
|
||||
|
||||
def test_moscow_oblast_anchors_are_two_measured_layers() -> None:
|
||||
"""Оба слоя на месте и не перепутаны: города-спутники + кластеры лот-массы."""
|
||||
anchors = get_city_anchors("moskovskaya_oblast")
|
||||
assert anchors is not None
|
||||
assert anchors == [*MOSCOW_OBLAST_CITY_ANCHORS, *MOSCOW_OBLAST_CLUSTER_ANCHORS]
|
||||
# Слой 2 добирает то, чего не дают города: без него покрытие лот-массы 73.8%.
|
||||
assert len(MOSCOW_OBLAST_CLUSTER_ANCHORS) > 0
|
||||
cities = {name for _lat, _lon, name in MOSCOW_OBLAST_CITY_ANCHORS}
|
||||
clusters = {name for _lat, _lon, name in MOSCOW_OBLAST_CLUSTER_ANCHORS}
|
||||
assert not (cities & clusters), "подписи слоёв пересеклись — якорь задублирован"
|
||||
|
||||
|
||||
def test_moscow_oblast_anchors_lie_inside_region_50_bbox() -> None:
|
||||
"""Якорь вне области собрал бы чужой регион и проштамповал бы его как 50.
|
||||
|
||||
Границы — bbox_region региона 50 из backend/app/services/regions.py (дубль:
|
||||
kit не имеет права импортировать app.*).
|
||||
"""
|
||||
lat_min, lat_max, lon_min, lon_max = 54.20, 56.96, 35.14, 40.21
|
||||
for lat, lon, name in [*MOSCOW_OBLAST_CITY_ANCHORS, *MOSCOW_OBLAST_CLUSTER_ANCHORS]:
|
||||
assert lat_min <= lat <= lat_max, f"{name}: широта вне области"
|
||||
assert lon_min <= lon <= lon_max, f"{name}: долгота вне области"
|
||||
|
||||
|
||||
def test_moscow_oblast_radius_is_the_one_coverage_was_measured_at() -> None:
|
||||
"""95.2% лот-массы посчитаны при 10 км; меньший radius_m в расписании даст дыры."""
|
||||
assert MOSCOW_OBLAST_ANCHOR_RADIUS_M == 10000
|
||||
assert MOSCOW_OBLAST_ANCHOR_RADIUS_M > MOSCOW_ANCHOR_RADIUS_M
|
||||
|
||||
|
||||
def test_moscow_stamp_radius_covers_core_diagonal() -> None:
|
||||
"""15 км по умолчанию отрезали бы легитимную Москву — у ядра полудиагональ 29.1 км."""
|
||||
_lat_min, lat_max, _lon_min, lon_max = MOSCOW_CORE_BBOX_DEG
|
||||
|
|
|
|||
|
|
@ -338,32 +338,88 @@ def _build_moscow_anchor_grid() -> list[tuple[float, float, str]]:
|
|||
|
||||
MOSCOW_ANCHORS: list[tuple[float, float, str]] = _build_moscow_anchor_grid()
|
||||
|
||||
# Московская область: по якорю на крупный город-спутник. Координаты — WGS84-центры
|
||||
# НП, взятые из ЕДИНСТВЕННОГО места репозитория, где они уже есть и перепроверены
|
||||
# замером: `_COVERAGE_NEGATIVE_CENTROIDS_DEG` в backend/app/api/v1/trade_in.py
|
||||
# (тот же запрет на импорт app.* — значения продублированы, править нужно оба места).
|
||||
# Московская область. Двухслойная сетка якорей; оба слоя измерены, ни одна координата
|
||||
# не выдумана.
|
||||
#
|
||||
# TODO(msk): порядок — по величине города, а НЕ по числу лотов: замера «сколько
|
||||
# лотов у какого города области» (в области всего 60 231 лот, cian region=4593) нет
|
||||
# ни в репозитории, ни в прод-корпусе, а гадать ранжирование нечем. Крупные города
|
||||
# области, которых нет в источнике координат (Домодедово, Раменское, Щёлково,
|
||||
# Сергиев Посад, Ногинск, Электросталь, Серпухов, Коломна, Орехово-Зуево,
|
||||
# Наро-Фоминск), СОЗНАТЕЛЬНО не заведены — выдуманные координаты хуже отсутствующих;
|
||||
# добавлять их следует вместе с записью в _COVERAGE_NEGATIVE_CENTROIDS_DEG по общему
|
||||
# замеру.
|
||||
# Слой 1 — города-спутники (22). Координаты первых 12 продублированы из
|
||||
# `_COVERAGE_NEGATIVE_CENTROIDS_DEG` в backend/app/api/v1/trade_in.py (тот же запрет
|
||||
# на импорт app.* — править нужно оба места); остальные 10 (Домодедово … Наро-Фоминск)
|
||||
# взяты прямым запросом к Nominatim /search (city=<НП>, state=Московская область)
|
||||
# 15.09.2026, все вернулись как place=city/town.
|
||||
#
|
||||
# Слой 2 — кластеры лот-массы (22). Это НЕ центры НП, а центроиды активных объявлений
|
||||
# области в ячейках 0.05°, добранные жадным покрытием поверх слоя 1; подпись — ближайший
|
||||
# НП по обратному геокодированию (zoom=14), она справочная. Слой нужен потому, что одних
|
||||
# городов мало: замер на проде 15.09.2026 по 70 996 активным объявлениям region_code=50
|
||||
# показал, что 22 городских якоря радиусом 10 км накрывают лишь 73.8% лот-массы, а вместе
|
||||
# со вторым слоем — 95.2%.
|
||||
#
|
||||
# Порядок в обоих слоях — по УБЫВАНИЮ измеренной лот-массы (в скобках у каждой строки:
|
||||
# слой 1 — активных лотов в 10 км от якоря, слой 2 — прирост покрытия, который этот якорь
|
||||
# дал жадному алгоритму). Это и есть замер, которого раньше не было.
|
||||
#
|
||||
# ВАЖНО: покрытие считано при радиусе MOSCOW_OBLAST_ANCHOR_RADIUS_M. Расписание, которое
|
||||
# передаст меньший radius_m, получит дыры — 95.2% относятся именно к 10 км.
|
||||
#
|
||||
# Оговорка о смещении: лот-масса взята из УЖЕ собранного корпуса (разовая загрузка
|
||||
# 10-12.09.2026). Там, где разовый сбор не добрал, масса занижена, поэтому 95.2% —
|
||||
# оценка сверху по тому, что видно, а не доказательство полноты по генеральной
|
||||
# совокупности. Первый регулярный прогон обязан пересчитать покрытие по своему улову.
|
||||
MOSCOW_OBLAST_ANCHOR_RADIUS_M = 10000
|
||||
|
||||
MOSCOW_OBLAST_CITY_ANCHORS: list[tuple[float, float, str]] = [
|
||||
(55.6767, 37.8931, "Люберцы"), # 7429
|
||||
(55.7614, 37.8564, "Реутов"), # 6824
|
||||
(55.7444, 38.0128, "Железнодорожный"), # 6465
|
||||
(55.7969, 37.9386, "Балашиха"), # 6429
|
||||
(55.8317, 37.3300, "Красногорск"), # 5556
|
||||
(55.9142, 37.8256, "Королёв"), # 5208
|
||||
(55.9116, 37.7308, "Мытищи"), # 4805
|
||||
(55.8894, 37.4450, "Химки"), # 4701
|
||||
(55.5519, 37.7133, "Видное"), # 4574
|
||||
(55.6789, 37.2639, "Одинцово"), # 4077
|
||||
(55.9206, 37.9916, "Щёлково"), # 4031
|
||||
(55.9386, 37.5100, "Долгопрудный"), # 3506
|
||||
(55.8554, 38.4412, "Ногинск"), # 2820
|
||||
(55.7845, 38.4449, "Электросталь"), # 2462
|
||||
(55.4312, 37.5450, "Подольск"), # 2453
|
||||
(55.4368, 37.7681, "Домодедово"), # 2059
|
||||
(56.3153, 38.1360, "Сергиев Посад"), # 1889
|
||||
(55.5709, 38.2282, "Раменское"), # 1344
|
||||
(55.0939, 38.7670, "Коломна"), # 1118
|
||||
(54.9155, 37.4196, "Серпухов"), # 1059
|
||||
(55.8062, 38.9810, "Орехово-Зуево"), # 824
|
||||
(55.3843, 36.7235, "Наро-Фоминск"), # 802
|
||||
]
|
||||
|
||||
MOSCOW_OBLAST_CLUSTER_ANCHORS: list[tuple[float, float, str]] = [
|
||||
(55.9395, 37.1805, "Рузино"), # +1528
|
||||
(56.0837, 37.9229, "Лесной"), # +1253
|
||||
(55.2779, 38.7415, "Воскресенск"), # +1002
|
||||
(55.5588, 37.0775, "Апрелевка"), # +974
|
||||
(55.7860, 36.9874, "Писково"), # +928
|
||||
(56.2883, 37.4825, "Яхрома"), # +887
|
||||
(56.3704, 36.6872, "Клин"), # +865
|
||||
(55.6113, 37.9765, "Октябрьский"), # +825
|
||||
(56.1460, 37.0592, "Солнечногорск"), # +616
|
||||
(55.8732, 38.1862, "Осеево"), # +597
|
||||
(55.3705, 39.0622, "Егорьевск"), # +587
|
||||
(54.8393, 38.1760, "Кашира"), # +585
|
||||
(55.7768, 38.7096, "Павловский Посад"), # +494
|
||||
(55.1608, 37.4732, "Чехов"), # +493
|
||||
(56.0920, 37.4972, "Некрасовский"), # +440
|
||||
(55.5773, 39.5299, "Шатура"), # +397
|
||||
(56.7358, 37.1643, "Дубна"), # +392
|
||||
(55.9134, 36.8293, "Истра"), # +389
|
||||
(56.4933, 38.1792, "Богородское"), # +312
|
||||
(54.9619, 39.0273, "Луховицы"), # +228
|
||||
(55.7715, 37.2308, "Ильинское-Усово"), # +224
|
||||
(56.2552, 37.9754, "Хотьково"), # +207
|
||||
]
|
||||
|
||||
MOSCOW_OBLAST_ANCHORS: list[tuple[float, float, str]] = [
|
||||
(55.7969, 37.9386, "Балашиха"),
|
||||
(55.8894, 37.4450, "Химки"),
|
||||
(55.4312, 37.5450, "Подольск"),
|
||||
(55.9116, 37.7308, "Мытищи"),
|
||||
(55.9142, 37.8256, "Королёв"),
|
||||
(55.6767, 37.8931, "Люберцы"),
|
||||
(55.8317, 37.3300, "Красногорск"),
|
||||
(55.6789, 37.2639, "Одинцово"),
|
||||
(55.9386, 37.5100, "Долгопрудный"),
|
||||
(55.5519, 37.7133, "Видное"),
|
||||
(55.7614, 37.8564, "Реутов"),
|
||||
(55.7444, 38.0128, "Железнодорожный"),
|
||||
*MOSCOW_OBLAST_CITY_ANCHORS,
|
||||
*MOSCOW_OBLAST_CLUSTER_ANCHORS,
|
||||
]
|
||||
|
||||
# Anchors для oblast-городов вне ЕКБ (B1 rollout — Свердловская область, region 66).
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue