Merge pull request 'подсказки адреса: пустые части адреса выпадают, а не печатаются' (#3276) from fix/mera-suggest-formatting into main
Some checks failed
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 16s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been cancelled
Deploy Trade-In / test (push) Has been cancelled
Some checks failed
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 16s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been cancelled
Deploy Trade-In / test (push) Has been cancelled
This commit is contained in:
commit
c53a967f49
2 changed files with 103 additions and 0 deletions
|
|
@ -810,6 +810,33 @@ async def _nominatim_lookup(address: str, city_hint: str | None = None) -> Geoco
|
||||||
# ── Suggest (автокомплит) ───────────────────────────────────────────────────
|
# ── Suggest (автокомплит) ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
# Части адреса приходят из реестров УЖЕ склеенными — и склеенными грязно:
|
||||||
|
# ЕГРН `readable_address` печатает маркер без значения и оставляет лишний
|
||||||
|
# пробел («г Екатеринбург, ул Фролова , д. 29, корп.»). Чиним склейку, а не
|
||||||
|
# конкретный случай: один проход по частям — схлопнуть пробелы, выбросить
|
||||||
|
# пустые и маркер-без-номера (корп./стр./лит./кв. и пр.), склеить обратно.
|
||||||
|
_RE_ADDR_WS = re.compile(r"\s+")
|
||||||
|
_RE_ADDR_BARE_MARKER = re.compile(
|
||||||
|
r"^(?:д|дом|корп|корпус|к|стр|строение|соор|сооружение|лит|литера|литер"
|
||||||
|
r"|кв|квартира|оф|офис|пом|помещение|вл|владение|уч|участок)\.?$",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tidy_address(s: str) -> str:
|
||||||
|
"""«г Екатеринбург, ул Фролова , д. 29, корп.» → «г Екатеринбург, ул Фролова, д. 29».
|
||||||
|
|
||||||
|
Часть без значения ВЫПАДАЕТ, а не печатается пустым маркером.
|
||||||
|
"""
|
||||||
|
parts = []
|
||||||
|
for raw in s.split(","):
|
||||||
|
part = _RE_ADDR_WS.sub(" ", raw).strip()
|
||||||
|
if not part or _RE_ADDR_BARE_MARKER.match(part):
|
||||||
|
continue
|
||||||
|
parts.append(part)
|
||||||
|
return ", ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class GeocodeSuggestion:
|
class GeocodeSuggestion:
|
||||||
label: str # формат "Малышева 30, Октябрьский район"
|
label: str # формат "Малышева 30, Октябрьский район"
|
||||||
|
|
@ -822,6 +849,12 @@ class GeocodeSuggestion:
|
||||||
# street/locality-кандидатов остаётся None (нет стабильного house-fias).
|
# street/locality-кандидатов остаётся None (нет стабильного house-fias).
|
||||||
fias_id: str | None = None
|
fias_id: str | None = None
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
# Единственная точка, через которую проходят ВСЕ тиры (cadastral/
|
||||||
|
# geoportal/houses/DaData/Nominatim) — чистим здесь, а не у каждого.
|
||||||
|
object.__setattr__(self, "label", tidy_address(self.label))
|
||||||
|
object.__setattr__(self, "full_address", tidy_address(self.full_address))
|
||||||
|
|
||||||
|
|
||||||
# ── DaData suggest wrapper ──────────────────────────────────────────────────
|
# ── DaData suggest wrapper ──────────────────────────────────────────────────
|
||||||
# Маппинг DaData kind → GeocodeSuggestion.kind (внутри geocoder используется
|
# Маппинг DaData kind → GeocodeSuggestion.kind (внутри geocoder используется
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
"""Склейка адреса в подсказках: пустые части ВЫПАДАЮТ, а не печатаются.
|
||||||
|
|
||||||
|
Прод-дефект: «г Екатеринбург, ул Фролова , д. 29, корп.» — двойной пробел
|
||||||
|
перед запятой и висячее «корп.» без номера (ЕГРН `readable_address` отдаёт
|
||||||
|
маркер даже когда значения нет, и все тиры /suggest пропускали строку насквозь).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.services.geocoder import GeocodeSuggestion, tidy_address
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("raw", "expected"),
|
||||||
|
[
|
||||||
|
# Прод-случай целиком.
|
||||||
|
(
|
||||||
|
"г Екатеринбург, ул Фролова , д. 29, корп.",
|
||||||
|
"г Екатеринбург, ул Фролова, д. 29",
|
||||||
|
),
|
||||||
|
# Непустой корпус ОБЯЗАН остаться.
|
||||||
|
(
|
||||||
|
"г Екатеринбург, ул Фролова, д. 29, корп. 2",
|
||||||
|
"г Екатеринбург, ул Фролова, д. 29, корп. 2",
|
||||||
|
),
|
||||||
|
# Соседние типы частей — та же болезнь, тот же механизм.
|
||||||
|
("г Екатеринбург, ул Мира, д. 1, стр.", "г Екатеринбург, ул Мира, д. 1"),
|
||||||
|
("г Екатеринбург, ул Мира, д. 1, лит.", "г Екатеринбург, ул Мира, д. 1"),
|
||||||
|
("г Екатеринбург, ул Мира, д. 1, кв.", "г Екатеринбург, ул Мира, д. 1"),
|
||||||
|
("г Екатеринбург, ул Мира, д. 1, стр. 5", "г Екатеринбург, ул Мира, д. 1, стр. 5"),
|
||||||
|
("г Екатеринбург, ул Мира, д. 1, лит. А", "г Екатеринбург, ул Мира, д. 1, лит. А"),
|
||||||
|
# Пустая часть посередине и подряд идущие запятые.
|
||||||
|
("г Екатеринбург, , ул Мира, корп. , д. 1", "г Екатеринбург, ул Мира, д. 1"),
|
||||||
|
# Маркер без точки и без номера.
|
||||||
|
("г Екатеринбург, ул Мира, д 1, корпус", "г Екатеринбург, ул Мира, д 1"),
|
||||||
|
# Чистая строка не меняется.
|
||||||
|
("г Екатеринбург, ул Малышева, д 30", "г Екатеринбург, ул Малышева, д 30"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_tidy_address(raw: str, expected: str) -> None:
|
||||||
|
assert tidy_address(raw) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"raw",
|
||||||
|
[
|
||||||
|
"г Екатеринбург, ул Фролова , д. 29, корп.",
|
||||||
|
"г Екатеринбург, , ул Мира, корп. , д. 1",
|
||||||
|
"г Екатеринбург, ул Мира, д. 1, стр. , лит.",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_no_double_spaces_or_dangling_parts(raw: str) -> None:
|
||||||
|
out = tidy_address(raw)
|
||||||
|
assert " " not in out
|
||||||
|
assert " ," not in out
|
||||||
|
assert not out.endswith(",")
|
||||||
|
assert out.split(", ")[-1] not in {"корп.", "стр.", "лит.", "кв.", "д."}
|
||||||
|
|
||||||
|
|
||||||
|
def test_suggestion_cleans_both_label_and_full_address() -> None:
|
||||||
|
"""Чистка стоит в единой точке — её проходят ВСЕ тиры /suggest."""
|
||||||
|
s = GeocodeSuggestion(
|
||||||
|
label="ул Фролова , д. 29, корп.",
|
||||||
|
full_address="г Екатеринбург, ул Фролова , д. 29, корп.",
|
||||||
|
lat=56.8,
|
||||||
|
lon=60.6,
|
||||||
|
kind="house",
|
||||||
|
)
|
||||||
|
assert s.label == "ул Фролова, д. 29"
|
||||||
|
assert s.full_address == "г Екатеринбург, ул Фролова, д. 29"
|
||||||
Loading…
Add table
Reference in a new issue