diff --git a/backend/app/schemas/own_project.py b/backend/app/schemas/own_project.py
index 3d878d7e..e3e2c661 100644
--- a/backend/app/schemas/own_project.py
+++ b/backend/app/schemas/own_project.py
@@ -49,9 +49,7 @@ class OwnPlannedProjectCreate(BaseModel):
planned_release_month: date | None = Field(
None, description="Планируемый месяц выхода в продажу (нормализуется к 1-му числу)"
)
- price_min_per_m2: float | None = Field(
- None, ge=0, description="Нижняя граница цены, ₽/м² (≥0)"
- )
+ price_min_per_m2: float | None = Field(None, ge=0, description="Нижняя граница цены, ₽/м² (≥0)")
price_max_per_m2: float | None = Field(
None, ge=0, description="Верхняя граница цены, ₽/м² (≥0)"
)
diff --git a/backend/app/services/analytics/ddu_price_indicator.py b/backend/app/services/analytics/ddu_price_indicator.py
index cf7268e8..df503d0e 100644
--- a/backend/app/services/analytics/ddu_price_indicator.py
+++ b/backend/app/services/analytics/ddu_price_indicator.py
@@ -316,9 +316,7 @@ def refresh_ddu_price_indicator(db: Session, *, concurrently: bool = True) -> in
db.commit()
except OperationalError as e:
if concurrently and "cannot refresh materialized view" in str(e).lower():
- logger.warning(
- "ddu_indicator CONCURRENTLY failed (MV not populated), falling back"
- )
+ logger.warning("ddu_indicator CONCURRENTLY failed (MV not populated), falling back")
db.rollback()
db.execute(text("REFRESH MATERIALIZED VIEW mv_ddu_price_indicator"))
db.commit()
diff --git a/backend/app/services/analytics_queries.py b/backend/app/services/analytics_queries.py
index 9e6ef0ab..8393b48e 100644
--- a/backend/app/services/analytics_queries.py
+++ b/backend/app/services/analytics_queries.py
@@ -665,8 +665,7 @@ def prinzip_insights() -> dict[str, Any]:
{
"district": "Чкаловский / Железнодорожный",
"why": (
- "Растущие районы, 0% PRINZIP, низкая конкуренция. "
- "Тест 60-80 м² без премиума."
+ "Растущие районы, 0% PRINZIP, низкая конкуренция. Тест 60-80 м² без премиума."
),
},
],
@@ -688,7 +687,7 @@ def prinzip_insights() -> dict[str, Any]:
{
"name": "Холдинг Форум-групп",
"model": (
- "113 тыс м² × sold 54% × Δ +21пп лидер velocity. " "3-к доля 21.5%, ср. 61 м²."
+ "113 тыс м² × sold 54% × Δ +21пп лидер velocity. 3-к доля 21.5%, ср. 61 м²."
),
},
],
@@ -1874,7 +1873,7 @@ def _active_competitors_count(
# #38: реальный obj_class в приоритете, иначе obj_class_fallback.
if target_class:
n = _q(
- "AND district_name = :dn" " AND COALESCE(obj_class, obj_class_fallback) = :cls",
+ "AND district_name = :dn AND COALESCE(obj_class, obj_class_fallback) = :cls",
{"rc": region_code, "dn": district_name, "cls": target_class},
)
if n >= 2:
diff --git a/backend/app/services/chat/intents.py b/backend/app/services/chat/intents.py
index d821c7e9..7d66c009 100644
--- a/backend/app/services/chat/intents.py
+++ b/backend/app/services/chat/intents.py
@@ -251,9 +251,7 @@ def _render_what_to_build(report: dict[str, Any]) -> tuple[str, list[str]]:
if summary:
lines.append(str(summary))
- if not any(
- section.get(k) for k in ("obj_class", "mix", "commercial", "usp", "summary")
- ):
+ if not any(section.get(k) for k in ("obj_class", "mix", "commercial", "usp", "summary")):
lines.append("Раздел рекомендации продукта в отчёте пуст.")
return _assemble(lines), sections_used
diff --git a/backend/app/services/etl/newbuilding_crossload.py b/backend/app/services/etl/newbuilding_crossload.py
index ab5ed376..ad5ef66d 100644
--- a/backend/app/services/etl/newbuilding_crossload.py
+++ b/backend/app/services/etl/newbuilding_crossload.py
@@ -229,8 +229,7 @@ def run_crossload(db: Session | None = None) -> dict[str, Any]:
except Exception as exc:
skipped += 1
logger.warning(
- "etl_newbuilding_crossload: upsert failed "
- "source=%s ext_id=%s: %s",
+ "etl_newbuilding_crossload: upsert failed source=%s ext_id=%s: %s",
params.get("source"),
params.get("ext_house_id"),
exc,
diff --git a/backend/app/services/exporters/report_docx.py b/backend/app/services/exporters/report_docx.py
index 96663941..6c47930f 100644
--- a/backend/app/services/exporters/report_docx.py
+++ b/backend/app/services/exporters/report_docx.py
@@ -270,9 +270,7 @@ def _build_scenarios(doc: _DocxDocument, report: dict[str, Any]) -> None:
for name, payload in by_scenario.items():
data = _as_dict(payload)
rate_path = _as_dict(data.get("rate_path"))
- rate_str = (
- ", ".join(f"{k}: {_fmt(v)}" for k, v in rate_path.items()) if rate_path else None
- )
+ rate_str = ", ".join(f"{k}: {_fmt(v)}" for k, v in rate_path.items()) if rate_path else None
rows.append([name, _scenario_deficit_cell(data), rate_str, data.get("advisory")])
headers = [
diff --git a/backend/app/services/exporters/report_maps.py b/backend/app/services/exporters/report_maps.py
index 44fe62db..3f9e3cbd 100644
--- a/backend/app/services/exporters/report_maps.py
+++ b/backend/app/services/exporters/report_maps.py
@@ -85,8 +85,7 @@ _CONCEPT_FOOTPRINT_STYLE = {
}
_MAP_UNAVAILABLE_HTML = (
- '
Карта недоступна — геоданные участка отсутствуют '
- "в отчёте
"
+ 'Карта недоступна — геоданные участка отсутствуют в отчёте
'
)
diff --git a/backend/app/services/forecasting/affordability.py b/backend/app/services/forecasting/affordability.py
index 04235f7d..ac865265 100644
--- a/backend/app/services/forecasting/affordability.py
+++ b/backend/app/services/forecasting/affordability.py
@@ -348,9 +348,7 @@ def compute_affordability(
# Иначе сценарный платёж считался бы по «голой» key_rate (≈ на 4.5 п.п.
# ниже базовой ставки) и был бы НЕсопоставим с monthly_payment_rub (#1639).
market_scenario_rate = (
- scenario_rate + _KEY_RATE_MARKET_SPREAD_PP
- if scenario_rate is not None
- else None
+ scenario_rate + _KEY_RATE_MARKET_SPREAD_PP if scenario_rate is not None else None
)
payment = _annuity(principal, market_scenario_rate, _ANNUITY_TERM_MONTHS)
if payment is not None:
diff --git a/backend/app/services/generative/catalog.py b/backend/app/services/generative/catalog.py
index 2ed0c21c..45f0fb93 100644
--- a/backend/app/services/generative/catalog.py
+++ b/backend/app/services/generative/catalog.py
@@ -123,7 +123,7 @@ def get_house_type(section_type: str) -> HouseType:
return _BY_KEY[section_type]
except KeyError as exc:
raise KeyError(
- f"unknown house type {section_type!r}; " f"available: {', '.join(sorted(_BY_KEY))}"
+ f"unknown house type {section_type!r}; available: {', '.join(sorted(_BY_KEY))}"
) from exc
diff --git a/backend/app/services/generative/geometry.py b/backend/app/services/generative/geometry.py
index 930ce619..c4052623 100644
--- a/backend/app/services/generative/geometry.py
+++ b/backend/app/services/generative/geometry.py
@@ -316,8 +316,7 @@ def parse_parcel(
raise ParcelGeometryError("buildable area degenerated after setback")
if buildable.area < MIN_BUILDABLE_AREA_SQM:
raise ParcelGeometryError(
- f"buildable area {buildable.area:.1f} sqm below minimum "
- f"{MIN_BUILDABLE_AREA_SQM} sqm"
+ f"buildable area {buildable.area:.1f} sqm below minimum {MIN_BUILDABLE_AREA_SQM} sqm"
)
effective_step = _coarsen_step_for_budget(buildable, grid_step_m)
diff --git a/backend/app/services/llm/client.py b/backend/app/services/llm/client.py
index 772db0b3..c27fdec8 100644
--- a/backend/app/services/llm/client.py
+++ b/backend/app/services/llm/client.py
@@ -231,9 +231,7 @@ def _call_with_retries(
# #1209: cap И серверное Retry-After (раньше min(...,30) применялся
# только к exp.backoff). _MAX_BACKOFF_S — единый потолок для обеих
# веток, защищает anyio-threadpool от blocking на часы.
- raw_wait = float(
- e.retry_after if e.retry_after is not None else 2**attempt
- )
+ raw_wait = float(e.retry_after if e.retry_after is not None else 2**attempt)
wait = min(raw_wait, _MAX_BACKOFF_S)
logger.warning(
"llm: HTTP %s (attempt %d/%d), backing off %.1fs (raw=%.1fs)",
diff --git a/backend/app/services/site_finder/eias_heat_loader.py b/backend/app/services/site_finder/eias_heat_loader.py
index 6b340fa9..02febce0 100644
--- a/backend/app/services/site_finder/eias_heat_loader.py
+++ b/backend/app/services/site_finder/eias_heat_loader.py
@@ -156,9 +156,7 @@ def _quarter_from_text(row_text: str) -> tuple[int, int] | None:
def build_card_url(org_id: int) -> str:
"""URL карточки организации в реестре ФАС (грид публикаций форм 14 / 4_6)."""
- return (
- f"{_CARD_URL}?reg={_REG}&orgId={org_id}" f"&sphere=WARM&razdel=QUARTER&form={_CARD_FORMS}"
- )
+ return f"{_CARD_URL}?reg={_REG}&orgId={org_id}&sphere=WARM&razdel=QUARTER&form={_CARD_FORMS}"
def build_template_url(guid: str, pub_id: str) -> str:
diff --git a/backend/app/services/site_finder/gate_verdict.py b/backend/app/services/site_finder/gate_verdict.py
index 4b3a8726..bff1dd4c 100644
--- a/backend/app/services/site_finder/gate_verdict.py
+++ b/backend/app/services/site_finder/gate_verdict.py
@@ -357,9 +357,7 @@ def compute_gate_verdict(
warnings.append(
Warning(
code="ZOUIT_CAD_SZZ",
- detail=(
- f"СЗЗ ({overlap.get('type_zone', '')}): " f"{overlap.get('name', '')}"
- ),
+ detail=(f"СЗЗ ({overlap.get('type_zone', '')}): {overlap.get('name', '')}"),
)
)
elif net_kind is not None or any(
@@ -376,8 +374,7 @@ def compute_gate_verdict(
Warning(
code="ZOUIT_CAD_OTHER",
detail=(
- f"ЗОУИТ cad ({overlap.get('type_zone', '')}): "
- f"{overlap.get('name', '')}"
+ f"ЗОУИТ cad ({overlap.get('type_zone', '')}): {overlap.get('name', '')}"
),
)
)
diff --git a/backend/app/services/site_finder/market_metrics.py b/backend/app/services/site_finder/market_metrics.py
index d6347644..8ead9006 100644
--- a/backend/app/services/site_finder/market_metrics.py
+++ b/backend/app/services/site_finder/market_metrics.py
@@ -943,8 +943,7 @@ def compute_offer_price_trend(
delta_pct = (last_median - first_median) / first_median * 100.0
logger.info(
- "offer_price_trend: lat=%.5f lon=%.5f radius=%d snapshots=%d "
- "lots_latest=%s delta_pct=%s",
+ "offer_price_trend: lat=%.5f lon=%.5f radius=%d snapshots=%d lots_latest=%s delta_pct=%s",
center_lat,
center_lon,
radius_m,
diff --git a/backend/app/services/site_finder/noise_loader.py b/backend/app/services/site_finder/noise_loader.py
index 32755c83..627997a4 100644
--- a/backend/app/services/site_finder/noise_loader.py
+++ b/backend/app/services/site_finder/noise_loader.py
@@ -91,10 +91,10 @@ def _build_overpass_query(key: str, value: str, el_type: str) -> str:
bbox = f"({south},{west},{north},{east})"
if el_type == "nwr":
# node + way: точки подключения бывают и точкой, и площадкой
- return f"[out:json][timeout:30];" f'nwr["{key}"="{value}"]{bbox};' f"out geom;"
+ return f'[out:json][timeout:30];nwr["{key}"="{value}"]{bbox};out geom;'
if el_type == "way":
- return f"[out:json][timeout:30];" f'way["{key}"="{value}"]{bbox};' f"out geom;"
- return f"[out:json][timeout:30];" f'node["{key}"="{value}"]{bbox};' f"out body;"
+ return f'[out:json][timeout:30];way["{key}"="{value}"]{bbox};out geom;'
+ return f'[out:json][timeout:30];node["{key}"="{value}"]{bbox};out body;'
async def fetch_overpass_noise() -> list[dict]:
diff --git a/backend/app/services/site_finder/pzz_loader.py b/backend/app/services/site_finder/pzz_loader.py
index 7d3c3a12..87e25c29 100644
--- a/backend/app/services/site_finder/pzz_loader.py
+++ b/backend/app/services/site_finder/pzz_loader.py
@@ -11,7 +11,7 @@ from app.core.db import SessionLocal
logger = logging.getLogger(__name__)
-PKK6_URL = "https://pkk.rosreestr.ru/arcgis/rest/services/PKK6/ZONES/" "MapServer/5/query"
+PKK6_URL = "https://pkk.rosreestr.ru/arcgis/rest/services/PKK6/ZONES/MapServer/5/query"
# bbox ЕКБ: (xmin, ymin, xmax, ymax) в WGS84
EKB_BBOX = (60.5, 56.7, 60.75, 56.95)
diff --git a/backend/app/workers/tasks/scrape_objective.py b/backend/app/workers/tasks/scrape_objective.py
index 7ab5506d..76270c40 100644
--- a/backend/app/workers/tasks/scrape_objective.py
+++ b/backend/app/workers/tasks/scrape_objective.py
@@ -352,7 +352,7 @@ def sync_objective_group(
db.rollback()
reports_failed += 1
logger.exception(
- "sync_objective_group: parser failed for %s/%s/%s " "raw_id=%s: %s",
+ "sync_objective_group: parser failed for %s/%s/%s raw_id=%s: %s",
section,
rtype,
rname,
diff --git a/backend/scripts/spike_plan_vectorize.py b/backend/scripts/spike_plan_vectorize.py
index 2ffd2a77..1f4b3cee 100644
--- a/backend/scripts/spike_plan_vectorize.py
+++ b/backend/scripts/spike_plan_vectorize.py
@@ -219,9 +219,7 @@ def summarise(results: list[VectorizeResult]) -> None:
total_raster = sum(r.raster_bytes for r in results)
total_svg = sum(r.svg_bytes for r in results)
agg_ratio = total_raster / total_svg if total_svg else float("inf")
- print(
- f"aggregate : {total_raster}B raster -> {total_svg}B svg " f"({agg_ratio:.2f}x overall)"
- )
+ print(f"aggregate : {total_raster}B raster -> {total_svg}B svg ({agg_ratio:.2f}x overall)")
def build_parser() -> argparse.ArgumentParser:
diff --git a/backend/tests/api/v1/test_2464_confidence_zoning_source.py b/backend/tests/api/v1/test_2464_confidence_zoning_source.py
index 6878d6e4..71cd130a 100644
--- a/backend/tests/api/v1/test_2464_confidence_zoning_source.py
+++ b/backend/tests/api/v1/test_2464_confidence_zoning_source.py
@@ -58,9 +58,9 @@ def test_nspd_zone_counts_as_known() -> None:
res = _confidence(nspd_zoning={"zone_code": "Ж-5"})
assert res["breakdown"]["zoning"] == 1.0
- assert not any(
- _CAVEAT in c for c in res["caveats"]
- ), "оговорка «зона неизвестна» при известной зоне Ж-5 — ровно то, что видел прод"
+ assert not any(_CAVEAT in c for c in res["caveats"]), (
+ "оговорка «зона неизвестна» при известной зоне Ж-5 — ровно то, что видел прод"
+ )
def test_regulation_zone_index_also_counts() -> None:
@@ -137,11 +137,11 @@ def test_analyze_does_not_claim_unknown_zone_when_nspd_resolved_it() -> None:
app.dependency_overrides.clear()
_stop_patches()
- assert (body.get("nspd_zoning") or {}).get(
- "zone_code"
- ) == "Ж-5", "предусловие теста не выполнено: зона не доехала до ответа"
+ assert (body.get("nspd_zoning") or {}).get("zone_code") == "Ж-5", (
+ "предусловие теста не выполнено: зона не доехала до ответа"
+ )
caveats = " ".join(body.get("confidence_caveats") or [])
- assert (
- _CAVEAT not in caveats
- ), "ответ показывает зону Ж-5 и одновременно заявляет, что зона неизвестна"
+ assert _CAVEAT not in caveats, (
+ "ответ показывает зону Ж-5 и одновременно заявляет, что зона неизвестна"
+ )
assert (body.get("confidence_breakdown") or {}).get("zoning") == 1.0
diff --git a/backend/tests/api/v1/test_2464g_noise_source_filter.py b/backend/tests/api/v1/test_2464g_noise_source_filter.py
index af948cdc..cfd60e80 100644
--- a/backend/tests/api/v1/test_2464g_noise_source_filter.py
+++ b/backend/tests/api/v1/test_2464g_noise_source_filter.py
@@ -136,9 +136,9 @@ def test_water_not_reported_as_noise_source() -> None:
noise = body.get("noise") or {}
sources = noise.get("nearby_sources") or noise.get("sources") or []
types = {s.get("source_type") for s in sources}
- assert (
- "water" not in types and "utility" not in types
- ), f"нешумовой слой попал в источники шума: {sources}"
+ assert "water" not in types and "utility" not in types, (
+ f"нешумовой слой попал в источники шума: {sources}"
+ )
def test_no_false_map_not_loaded_caveat_when_only_water_nearby() -> None:
diff --git a/backend/tests/api/v1/test_2934_no_fake_geology_label.py b/backend/tests/api/v1/test_2934_no_fake_geology_label.py
index 96f49925..17ccd476 100644
--- a/backend/tests/api/v1/test_2934_no_fake_geology_label.py
+++ b/backend/tests/api/v1/test_2934_no_fake_geology_label.py
@@ -53,9 +53,9 @@ def test_noise_no_longer_feeds_the_risk_label() -> None:
"""
блок = _risks_block_source()
assert "noise_db_max" not in блок, f"шум по-прежнему участвует в риск-блоке:\n{блок[:400]}"
- assert not re.search(
- r'"(high|medium|low)"', блок
- ), f"в риск-блоке остались словесные градации риска:\n{блок[:400]}"
+ assert not re.search(r'"(high|medium|low)"', блок), (
+ f"в риск-блоке остались словесные градации риска:\n{блок[:400]}"
+ )
def test_noise_score_itself_is_preserved() -> None:
diff --git a/backend/tests/api/v1/test_analyze_competitors_status.py b/backend/tests/api/v1/test_analyze_competitors_status.py
index 055a2d34..7174b6dd 100644
--- a/backend/tests/api/v1/test_analyze_competitors_status.py
+++ b/backend/tests/api/v1/test_analyze_competitors_status.py
@@ -90,9 +90,9 @@ class TestCompetitorsHaveStatusFields:
competitors = [dict(r.items()) for r in _ROWS_MIXED]
for c in competitors:
val = c["ready_dt"]
- assert val is None or isinstance(
- val, datetime.date
- ), f"ready_dt имеет неожиданный тип {type(val)}: {val}"
+ assert val is None or isinstance(val, datetime.date), (
+ f"ready_dt имеет неожиданный тип {type(val)}: {val}"
+ )
class TestCompetitorsSortOrder:
@@ -110,9 +110,9 @@ class TestCompetitorsSortOrder:
sorted_rows = sorted(_ROWS_MIXED, key=_sort_key)
first = dict(sorted_rows[0].items())
- assert (
- first["site_status"] == "Строящиеся"
- ), f"Первый конкурент должен быть 'Строящиеся', но получили '{first['site_status']}'"
+ assert first["site_status"] == "Строящиеся", (
+ f"Первый конкурент должен быть 'Строящиеся', но получили '{first['site_status']}'"
+ )
def test_flat_count_desc_would_break_order(self) -> None:
"""Демонстрирует, что старый ORDER BY flat_count DESC ставил сданные первыми."""
@@ -201,22 +201,22 @@ class TestObjPricingPushdown:
"""
sql = self._competitor_sql()
bounds = "WHERE oll.price_per_m2_rub BETWEEN 30000 AND 600000"
- assert (
- f"AVG(oll.price_per_m2_rub) FILTER ( {bounds} )" in sql
- ), "среднее цены должно фильтроваться границами правдоподобия (#2464-D)"
+ assert f"AVG(oll.price_per_m2_rub) FILTER ( {bounds} )" in sql, (
+ "среднее цены должно фильтроваться границами правдоподобия (#2464-D)"
+ )
# Тот же набор кормит счётчик выборки — иначе счётчик обещает шире, чем
# реально участвовало в среднем.
- assert (
- f"COUNT(*) FILTER ( {bounds} ) AS lots_with_price" in sql
- ), "lots_with_price должен считать ту же популяцию, что и среднее"
+ assert f"COUNT(*) FILTER ( {bounds} ) AS lots_with_price" in sql, (
+ "lots_with_price должен считать ту же популяцию, что и среднее"
+ )
# FILTER, а не WHERE на CTE: строки нужны целиком, иначе границы цены
# молча урежут счётчики продаж/остатка, которые считают ВСЕ лоты.
- assert (
- "COUNT(*) FILTER (WHERE oll.is_sold) AS units_sold" in sql
- ), "units_sold не должен зависеть от границ цены"
- assert (
- "COUNT(*) FILTER (WHERE NOT oll.is_sold) AS units_available" in sql
- ), "units_available не должен зависеть от границ цены"
+ assert "COUNT(*) FILTER (WHERE oll.is_sold) AS units_sold" in sql, (
+ "units_sold не должен зависеть от границ цены"
+ )
+ assert "COUNT(*) FILTER (WHERE NOT oll.is_sold) AS units_available" in sql, (
+ "units_available не должен зависеть от границ цены"
+ )
def test_obj_pricing_dedups_physflat_inline(self) -> None:
"""#1964: obj_pricing агрегирует physflat-дедуп набор (DISTINCT ON), НЕ сырой.
@@ -234,9 +234,9 @@ class TestObjPricingPushdown:
"\n", " "
), "obj_lots_latest должен дедупить по physflat-ключу"
assert "snapshot_date DESC, ol.id DESC" in sql, "берём последний снапшот физлота"
- assert (
- "v_objective_lots_latest" not in sql
- ), "request-path: view материализует всю таблицу — нужен inline DISTINCT ON (#1964)"
+ assert "v_objective_lots_latest" not in sql, (
+ "request-path: view материализует всю таблицу — нужен inline DISTINCT ON (#1964)"
+ )
class TestCompetitorAvgAreaPd:
diff --git a/backend/tests/api/v1/test_analyze_inline_weights.py b/backend/tests/api/v1/test_analyze_inline_weights.py
index cc61f0ef..35ffb40e 100644
--- a/backend/tests/api/v1/test_analyze_inline_weights.py
+++ b/backend/tests/api/v1/test_analyze_inline_weights.py
@@ -285,9 +285,9 @@ def test_inline_weights_rejects_nan() -> None:
content=raw_body,
headers={"Content-Type": "application/json"},
)
- assert (
- resp.status_code == 422
- ), f"Ожидали 422 для NaN-weight, получили {resp.status_code}: {resp.text}"
+ assert resp.status_code == 422, (
+ f"Ожидали 422 для NaN-weight, получили {resp.status_code}: {resp.text}"
+ )
finally:
app.dependency_overrides.clear()
_stop_patches()
diff --git a/backend/tests/api/v1/test_insights.py b/backend/tests/api/v1/test_insights.py
index ad202afd..c84cac60 100644
--- a/backend/tests/api/v1/test_insights.py
+++ b/backend/tests/api/v1/test_insights.py
@@ -215,9 +215,7 @@ def test_list_insights_filter_confidential_false() -> None:
listing = InsightList(total=0, limit=50, offset=0, rows=[])
with patch("app.api.v1.insights.list_insights", return_value=listing) as mock_list:
client = TestClient(app)
- resp = client.get(
- "/api/v1/insights", params={"is_confidential": "false"}, headers=_AUTH
- )
+ resp = client.get("/api/v1/insights", params={"is_confidential": "false"}, headers=_AUTH)
assert resp.status_code == 200, resp.text
assert mock_list.call_args.kwargs["is_confidential"] is False
@@ -261,9 +259,7 @@ def test_put_insight_returns_updated() -> None:
def test_put_insight_not_found_returns_404() -> None:
with patch("app.api.v1.insights.update_insight", return_value=None):
client = TestClient(app)
- resp = client.put(
- "/api/v1/insights/999", json={"title": "x"}, headers=_AUTH
- )
+ resp = client.put("/api/v1/insights/999", json={"title": "x"}, headers=_AUTH)
assert resp.status_code == 404, resp.text
diff --git a/backend/tests/api/v1/test_market_pulse_and_neighbors_honesty.py b/backend/tests/api/v1/test_market_pulse_and_neighbors_honesty.py
index f435dc5a..ae211c57 100644
--- a/backend/tests/api/v1/test_market_pulse_and_neighbors_honesty.py
+++ b/backend/tests/api/v1/test_market_pulse_and_neighbors_honesty.py
@@ -65,9 +65,9 @@ class TestBuildMarketPulseHonesty:
)
assert pulse["competitors_total"] == true_total
- assert pulse["competitors_total"] != len(
- rows
- ), "regression guard: competitors_total НЕ должен деградировать до len(competitor_rows)"
+ assert pulse["competitors_total"] != len(rows), (
+ "regression guard: competitors_total НЕ должен деградировать до len(competitor_rows)"
+ )
def test_coverage_pct_computed_against_true_total_not_capped_list(self) -> None:
"""coverage_pct = priced / TRUE total — раньше делилось на len(rows) (капнутый
@@ -182,9 +182,9 @@ class TestNeighborsSummaryHonesty:
summary = parcels_module._neighbors_summary(db, "POINT(60.6 56.8)", "66:41:0000000:999")
assert summary["count_buildings_100m"] == true_total
- assert summary["count_buildings_100m"] != len(
- neighbors
- ), "regression guard: count_buildings_100m НЕ должен деградировать до len(neighbor_rows)"
+ assert summary["count_buildings_100m"] != len(neighbors), (
+ "regression guard: count_buildings_100m НЕ должен деградировать до len(neighbor_rows)"
+ )
assert summary["neighbors_truncated"] is True
def test_neighbors_list_itself_unaffected_by_count_fix(self) -> None:
diff --git a/backend/tests/api/v1/test_own_projects.py b/backend/tests/api/v1/test_own_projects.py
index 06e67314..1a791839 100644
--- a/backend/tests/api/v1/test_own_projects.py
+++ b/backend/tests/api/v1/test_own_projects.py
@@ -77,9 +77,7 @@ def _make_out(
def test_create_own_project_returns_201_and_sets_created_by() -> None:
"""POST → 201; created_by берётся из X-Authenticated-User, не из тела."""
expected = _make_out()
- with patch(
- "app.api.v1.own_projects.create_own_project", return_value=expected
- ) as mock_create:
+ with patch("app.api.v1.own_projects.create_own_project", return_value=expected) as mock_create:
client = TestClient(app)
resp = client.post(
"/api/v1/own-projects",
@@ -106,9 +104,7 @@ def test_create_own_project_with_unit_mix() -> None:
"""unit_mix в теле → пробрасывается в payload сервиса."""
mix = {"studio": 0.3, "1k": 0.4, "2k": 0.2, "3k": 0.1}
expected = _make_out(unit_mix=mix)
- with patch(
- "app.api.v1.own_projects.create_own_project", return_value=expected
- ) as mock_create:
+ with patch("app.api.v1.own_projects.create_own_project", return_value=expected) as mock_create:
client = TestClient(app)
resp = client.post(
"/api/v1/own-projects",
@@ -183,9 +179,7 @@ def test_create_own_project_without_auth_header_returns_401() -> None:
def test_list_own_projects_returns_envelope() -> None:
"""GET → OwnPlannedProjectList {total, limit, offset, rows}."""
- listing = OwnPlannedProjectList(
- total=2, limit=50, offset=0, rows=[_make_out(1), _make_out(2)]
- )
+ listing = OwnPlannedProjectList(total=2, limit=50, offset=0, rows=[_make_out(1), _make_out(2)])
with patch("app.api.v1.own_projects.list_own_projects", return_value=listing):
client = TestClient(app)
resp = client.get("/api/v1/own-projects", headers=_AUTH)
@@ -199,9 +193,7 @@ def test_list_own_projects_returns_envelope() -> None:
def test_list_own_projects_passes_filters_to_service() -> None:
"""Фильтры district/obj_class/created_by → в сервис как kwargs."""
listing = OwnPlannedProjectList(total=0, limit=50, offset=0, rows=[])
- with patch(
- "app.api.v1.own_projects.list_own_projects", return_value=listing
- ) as mock_list:
+ with patch("app.api.v1.own_projects.list_own_projects", return_value=listing) as mock_list:
client = TestClient(app)
resp = client.get(
"/api/v1/own-projects",
@@ -240,9 +232,7 @@ def test_put_own_project_returns_updated() -> None:
updated = _make_out(name="Переименовано")
with patch("app.api.v1.own_projects.update_own_project", return_value=updated):
client = TestClient(app)
- resp = client.put(
- "/api/v1/own-projects/1", json={"name": "Переименовано"}, headers=_AUTH
- )
+ resp = client.put("/api/v1/own-projects/1", json={"name": "Переименовано"}, headers=_AUTH)
assert resp.status_code == 200, resp.text
assert resp.json()["name"] == "Переименовано"
diff --git a/backend/tests/api/v1/test_parcel_competitors.py b/backend/tests/api/v1/test_parcel_competitors.py
index d49138ab..1e3c0bfd 100644
--- a/backend/tests/api/v1/test_parcel_competitors.py
+++ b/backend/tests/api/v1/test_parcel_competitors.py
@@ -449,9 +449,9 @@ def test_competitors_avg_price_populated() -> None:
)
assert resp.status_code == 200, resp.text
comp = resp.json()["competitors"][0]
- assert comp["avg_price_per_m2"] == pytest.approx(
- 150_000.0
- ), "avg_price_per_m2 должен быть не None — регрессия #227 status='sold' filter"
+ assert comp["avg_price_per_m2"] == pytest.approx(150_000.0), (
+ "avg_price_per_m2 должен быть не None — регрессия #227 status='sold' filter"
+ )
# OBJ-3 #307: domrf-hit → price_source='domrf'.
assert comp["price_source"] == "domrf"
finally:
@@ -705,9 +705,9 @@ def test_sold_count_sql_is_fanout_safe() -> None:
objective_lot_id).
"""
sql = _sold_sql_text()
- assert (
- "COUNT(DISTINCT objective_lot_id)" in sql
- ), "fan-out guard: маппинг не unique по domrf_obj_id — нужен COUNT(DISTINCT lot)"
+ assert "COUNT(DISTINCT objective_lot_id)" in sql, (
+ "fan-out guard: маппинг не unique по domrf_obj_id — нужен COUNT(DISTINCT lot)"
+ )
# COUNT(*) допустим внутри как агрегат? нет — sold-count агрегирует только distinct lot.
assert "COUNT(*)" not in sql, "COUNT(*) задвоит лоты при 1:N маппинге"
diff --git a/backend/tests/integration/test_analyze_parcels_sql.py b/backend/tests/integration/test_analyze_parcels_sql.py
index b50d0577..49dde2cc 100644
--- a/backend/tests/integration/test_analyze_parcels_sql.py
+++ b/backend/tests/integration/test_analyze_parcels_sql.py
@@ -104,9 +104,9 @@ class TestNeighborsSummarySql:
for kw in forbidden_aliases:
# ищем паттерн ``WITH AS (`` или ``, AS (`` — оба
# формы CTE-биндинга.
- assert (
- f"with {kw} as (" not in raw_sql and f", {kw} as (" not in raw_sql
- ), f"CTE alias '{kw}' пересекается с PG keyword (см. incident #1195)"
+ assert f"with {kw} as (" not in raw_sql and f", {kw} as (" not in raw_sql, (
+ f"CTE alias '{kw}' пересекается с PG keyword (см. incident #1195)"
+ )
# ── parcel_ird_overlaps SQL ──────────────────────────────────────────────────
diff --git a/backend/tests/ops/test_2950_deploy_concurrency_group.py b/backend/tests/ops/test_2950_deploy_concurrency_group.py
index b8fa59b2..a2e8b519 100644
--- a/backend/tests/ops/test_2950_deploy_concurrency_group.py
+++ b/backend/tests/ops/test_2950_deploy_concurrency_group.py
@@ -121,9 +121,9 @@ def test_prod_deploy_declares_shared_concurrency_group(name: str) -> None:
группы снова разрешат параллельный запуск.
"""
conc = yaml.safe_load(_text(name)).get("concurrency") or {}
- assert (
- conc.get("group") == "deploy-prod"
- ), f"{name}: группа concurrency = {conc.get('group')!r}, ожидалась общая 'deploy-prod'"
+ assert conc.get("group") == "deploy-prod", (
+ f"{name}: группа concurrency = {conc.get('group')!r}, ожидалась общая 'deploy-prod'"
+ )
assert conc.get("cancel-in-progress") is False, (
f"{name}: cancel-in-progress должен быть false — отменённый деплой оставляет "
"прод на старом коде ровно так же, как упавший"
diff --git a/backend/tests/scrapers/test_cbr_macro.py b/backend/tests/scrapers/test_cbr_macro.py
index 5b03f0c3..ffffc1e3 100644
--- a/backend/tests/scrapers/test_cbr_macro.py
+++ b/backend/tests/scrapers/test_cbr_macro.py
@@ -41,7 +41,7 @@ FIXTURE_KEYRATE_XML = (
# где угодно), несмотря на schema-блок и namespace на KR.
FIXTURE_KEYRATE_DIFFGRAM = (
''
- ""
+ ''
''
''
diff --git a/backend/tests/scrapers/test_emiss_sdmx.py b/backend/tests/scrapers/test_emiss_sdmx.py
index 8b356e1d..6cba33ba 100644
--- a/backend/tests/scrapers/test_emiss_sdmx.py
+++ b/backend/tests/scrapers/test_emiss_sdmx.py
@@ -129,9 +129,9 @@ def test_income_extracts_sverdlovsk_only() -> None:
assert all(r.indicator_type == "income_per_capita" for r in rows)
assert all(r.unit == "руб" for r in rows)
assert all(r.frequency == "quarterly" for r in rows)
- assert all(
- r.period_type == "quarter" for r in rows
- ), "квартальные строки должны иметь period_type='quarter'"
+ assert all(r.period_type == "quarter" for r in rows), (
+ "квартальные строки должны иметь period_type='quarter'"
+ )
def test_income_concrete_values_and_dates() -> None:
diff --git a/backend/tests/scrapers/test_nspd_bulk_client.py b/backend/tests/scrapers/test_nspd_bulk_client.py
index bc57857a..ef93c852 100644
--- a/backend/tests/scrapers/test_nspd_bulk_client.py
+++ b/backend/tests/scrapers/test_nspd_bulk_client.py
@@ -602,6 +602,6 @@ async def test_list_objects_in_building_real() -> None:
assert listing.objdoc_id == 42065602
assert listing.flats_count > 150, f"Ожидали >150 помещений, получили {listing.flats_count}"
- assert (
- len(listing.flats_cad_nums) > 150
- ), f"Ожидали >150 cad_nums помещений, получили {len(listing.flats_cad_nums)}"
+ assert len(listing.flats_cad_nums) > 150, (
+ f"Ожидали >150 cad_nums помещений, получили {len(listing.flats_cad_nums)}"
+ )
diff --git a/backend/tests/scrapers/test_nspd_grid_walk.py b/backend/tests/scrapers/test_nspd_grid_walk.py
index 26d8e625..a34bceac 100644
--- a/backend/tests/scrapers/test_nspd_grid_walk.py
+++ b/backend/tests/scrapers/test_nspd_grid_walk.py
@@ -250,9 +250,9 @@ class TestGetFeaturesInBboxGrid:
return [good_feat]
result = self._grid(_wms)
- assert any(
- f.feature_id == "feat-ok" for f in result
- ), "успешные ячейки должны попасть в результат, даже если часть слоя упала"
+ assert any(f.feature_id == "feat-ok" for f in result), (
+ "успешные ячейки должны попасть в результат, даже если часть слоя упала"
+ )
def test_returns_nspd_feature_instances(self) -> None:
"""Метод возвращает list[NSPDFeature] а не NSPDBulkFeature."""
@@ -346,9 +346,9 @@ class TestClassifyEngineeringKind:
],
)
def test_classify(self, props: dict[str, Any], expected: str) -> None:
- assert (
- classify_engineering_kind(props) == expected
- ), f"props={props!r} → expected {expected!r}"
+ assert classify_engineering_kind(props) == expected, (
+ f"props={props!r} → expected {expected!r}"
+ )
def test_field_priority_params_name_over_purpose(self) -> None:
"""params_name проверяется раньше purpose."""
@@ -422,13 +422,13 @@ class TestFetchLayerDispatch:
called_layer_ids = [call.args[0] for call in mock_grid.call_args_list]
from app.services.scrapers.nspd_client import LAYERS
- assert (
- LAYERS["territorial_zones"] in called_layer_ids
- ), "territorial_zones должен использовать grid-walk"
+ assert LAYERS["territorial_zones"] in called_layer_ids, (
+ "territorial_zones должен использовать grid-walk"
+ )
assert LAYERS["red_lines"] in called_layer_ids, "red_lines должен использовать grid-walk"
- assert (
- LAYERS["engineering_structures"] in called_layer_ids
- ), "engineering_structures должен использовать grid-walk"
+ assert LAYERS["engineering_structures"] in called_layer_ids, (
+ "engineering_structures должен использовать grid-walk"
+ )
# parcels и buildings — legacy, не grid
called_legacy_ids = [call.args[0] for call in mock_legacy.call_args_list]
assert LAYERS["parcels"] in called_legacy_ids, "parcels должен идти через legacy"
diff --git a/backend/tests/scrapers/test_rosstat_emiss.py b/backend/tests/scrapers/test_rosstat_emiss.py
index bb3c2c30..525a2c13 100644
--- a/backend/tests/scrapers/test_rosstat_emiss.py
+++ b/backend/tests/scrapers/test_rosstat_emiss.py
@@ -42,7 +42,7 @@ CONSTRUCTION_XLSX = (_FIXTURES / "rosstat_construction_stroitel.xlsx").read_byte
FIXTURE_META = (
"property,value\n"
"standardversion,https://rosstat.gov.ru/storage/.../versiya_4.0.pdf\n"
- 'identifier,7708234640-population\n'
+ "identifier,7708234640-population\n"
'title,"Численность постоянного населения"\n'
"created,20180905\n"
"modified,20140919\n"
@@ -58,7 +58,7 @@ FIXTURE_META = (
# значение в 'total', год в 'year'. Запятая — разделитель полей; значения — целые.
FIXTURE_POPULATION_DATA = (
"number,year,kode,region,municipalities,total,urban,rural\n"
- '1,2014,7900000000,Республика Адыгея,Муниципальные образования Республики Адыгеи,'
+ "1,2014,7900000000,Республика Адыгея,Муниципальные образования Республики Адыгеи,"
"446406,209929,236477\n"
"2,2014,6500000000,Свердловская область,Муниципальные образования Свердловской области,"
"4320677,3500000,820677\n"
diff --git a/backend/tests/services/analysis_runs/test_repository_run_history.py b/backend/tests/services/analysis_runs/test_repository_run_history.py
index f5201e01..fe54cb16 100644
--- a/backend/tests/services/analysis_runs/test_repository_run_history.py
+++ b/backend/tests/services/analysis_runs/test_repository_run_history.py
@@ -60,8 +60,16 @@ def test_list_runs_for_light_projection_base_table_order_limit() -> None:
# LIGHT: result-блоб НЕ выбирается
assert "RESULT" not in upper, "list_runs_for НЕ должен тянуть тяжёлый result"
# выбраны именно метаданные
- for col in ("ID", "CAD_NUM", "CREATED_AT", "STATUS", "SCHEMA_VERSION",
- "DISTRICT", "CONFIDENCE", "CREATED_BY"):
+ for col in (
+ "ID",
+ "CAD_NUM",
+ "CREATED_AT",
+ "STATUS",
+ "SCHEMA_VERSION",
+ "DISTRICT",
+ "CONFIDENCE",
+ "CREATED_BY",
+ ):
assert col in upper, f"ожидали колонку {col} в LIGHT-проекции"
assert "ORDER BY CREATED_AT DESC" in flat
assert "LIMIT CAST(:LIMIT AS INTEGER)" in upper
diff --git a/backend/tests/services/cadastre/test_2464_backfill_waf_aborts.py b/backend/tests/services/cadastre/test_2464_backfill_waf_aborts.py
index e9d7a146..733ca4df 100644
--- a/backend/tests/services/cadastre/test_2464_backfill_waf_aborts.py
+++ b/backend/tests/services/cadastre/test_2464_backfill_waf_aborts.py
@@ -102,9 +102,9 @@ def test_ordinary_error_still_skips_only_that_quarter() -> None:
"""
outcome, touched = _run(RuntimeError("битый ответ одного квартала"))
- assert not isinstance(
- outcome, BaseException
- ), f"обычная ошибка обрушила весь прогон: {outcome!r}"
+ assert not isinstance(outcome, BaseException), (
+ f"обычная ошибка обрушила весь прогон: {outcome!r}"
+ )
assert len(touched) == 3, f"тронуто кварталов {len(touched)}, ожидалось 3: {touched}"
diff --git a/backend/tests/services/exporters/test_2464_concept_pdf_sales_window.py b/backend/tests/services/exporters/test_2464_concept_pdf_sales_window.py
index 7104d93a..711dec1b 100644
--- a/backend/tests/services/exporters/test_2464_concept_pdf_sales_window.py
+++ b/backend/tests/services/exporters/test_2464_concept_pdf_sales_window.py
@@ -118,7 +118,7 @@ def test_document_carries_the_measured_window_not_the_norm() -> None:
html = _build_html([_variant(schedule_is_default=False, sales_months=54.0)])
- assert (
- "распродажа 54 мес" in html
- ), "в методической сноске стоит не тот срок, по которому посчитан NPV"
+ assert "распродажа 54 мес" in html, (
+ "в методической сноске стоит не тот срок, по которому посчитан NPV"
+ )
assert "распродажа 30 мес," not in html
diff --git a/backend/tests/services/exporters/test_2464_zouit_count_label.py b/backend/tests/services/exporters/test_2464_zouit_count_label.py
index ec17ce91..f2c46189 100644
--- a/backend/tests/services/exporters/test_2464_zouit_count_label.py
+++ b/backend/tests/services/exporters/test_2464_zouit_count_label.py
@@ -101,9 +101,9 @@ def test_record_count_is_still_shown(pairs_fn: Any, fmt: str) -> None:
"""Контроль: само число записей из отчёта не пропало — подпись правится, не значение."""
pairs = pairs_fn()
counters = [(k, v) for k, v in pairs if re.fullmatch(r"\d+", v.strip())]
- assert any(
- int(v) == _RECORDS for _, v in counters
- ), f"{fmt}: число ЗОУИТ-записей ({_RECORDS}) исчезло из сводки: {counters}"
+ assert any(int(v) == _RECORDS for _, v in counters), (
+ f"{fmt}: число ЗОУИТ-записей ({_RECORDS}) исчезло из сводки: {counters}"
+ )
@pytest.mark.parametrize("pairs_fn,fmt", [(_html_pairs, "html"), (_docx_pairs, "docx")])
@@ -111,6 +111,6 @@ def test_types_are_still_listed(pairs_fn: Any, fmt: str) -> None:
"""Контроль: перечисление типов на месте — читатель по-прежнему видит, какие они."""
pairs = pairs_fn()
joined = " ".join(f"{k} {v}" for k, v in pairs)
- assert (
- "Охранная зона ЛЭП" in joined and "Приаэродромная территория" in joined
- ), f"{fmt}: типы ЗОУИТ пропали из сводки"
+ assert "Охранная зона ЛЭП" in joined and "Приаэродромная территория" in joined, (
+ f"{fmt}: типы ЗОУИТ пропали из сводки"
+ )
diff --git a/backend/tests/services/exporters/test_2934_flood_row_honesty.py b/backend/tests/services/exporters/test_2934_flood_row_honesty.py
index 03fda10c..3ae8b247 100644
--- a/backend/tests/services/exporters/test_2934_flood_row_honesty.py
+++ b/backend/tests/services/exporters/test_2934_flood_row_honesty.py
@@ -48,9 +48,9 @@ def test_label_no_longer_claims_a_flood_verdict() -> None:
"""Метка называет измеренное, а не вывод, которого не делали."""
html = build_full_report_html_part_a(_result(flag=False), cad="00:00:0000000:0000")
- assert (
- _OLD_LABEL not in html
- ), "метка утверждает результат проверки зон затопления, которой не было"
+ assert _OLD_LABEL not in html, (
+ "метка утверждает результат проверки зон затопления, которой не было"
+ )
assert "ближе 200 м" in html, "метка должна называть измеренное — близость водотока"
@@ -75,6 +75,6 @@ def test_docx_twin_uses_the_same_label() -> None:
"""
from app.services.exporters import full_report_docx, full_report_html
- assert (
- full_report_docx.FLOOD_PROXIMITY_LABEL is full_report_html.FLOOD_PROXIMITY_LABEL
- ), "DOCX держит свою копию метки — форматы разъедутся при следующей правке"
+ assert full_report_docx.FLOOD_PROXIMITY_LABEL is full_report_html.FLOOD_PROXIMITY_LABEL, (
+ "DOCX держит свою копию метки — форматы разъедутся при следующей правке"
+ )
diff --git a/backend/tests/services/exporters/test_excel.py b/backend/tests/services/exporters/test_excel.py
index b639775f..549a075d 100644
--- a/backend/tests/services/exporters/test_excel.py
+++ b/backend/tests/services/exporters/test_excel.py
@@ -356,9 +356,9 @@ class TestContractKeysWritten:
payload_12mo = _scenario("base", deficit_12mo=0.34)
cell_value = _scenario_deficit_cell(payload_12mo)
# Для основного горизонта — голое число, не строка с «(гор. N мес)».
- assert not isinstance(
- cell_value, str
- ), f"для 12-мес горизонта ожидается скаляр, получено '{cell_value}'"
+ assert not isinstance(cell_value, str), (
+ f"для 12-мес горизонта ожидается скаляр, получено '{cell_value}'"
+ )
assert cell_value == 0.34
def test_overall_score_in_cells(self) -> None:
diff --git a/backend/tests/services/forecasting/test_confidence_engine.py b/backend/tests/services/forecasting/test_confidence_engine.py
index b946e28f..572f0566 100644
--- a/backend/tests/services/forecasting/test_confidence_engine.py
+++ b/backend/tests/services/forecasting/test_confidence_engine.py
@@ -157,9 +157,9 @@ class TestCoverageFactor:
f = _coverage_factor(0.4)
assert "ближних ЖК" in f.note, f.note
assert "Objective" in f.note, f.note
- assert (
- "будущ" not in f.note
- ), "нота обещала «будущие проекты», хотя мерится покрытие ближних ЖК ценами"
+ assert "будущ" not in f.note, (
+ "нота обещала «будущие проекты», хотя мерится покрытие ближних ЖК ценами"
+ )
def test_high_coverage(self) -> None:
f = _coverage_factor(0.75)
diff --git a/backend/tests/services/forecasting/test_normalize.py b/backend/tests/services/forecasting/test_normalize.py
index f85229b3..be5cc9d7 100644
--- a/backend/tests/services/forecasting/test_normalize.py
+++ b/backend/tests/services/forecasting/test_normalize.py
@@ -150,9 +150,9 @@ class TestSeasonalFactors:
"""
months = _months(36)
adj = seasonal_factors(months, [0] * 36)
- assert (
- adj.n_full_years == 0
- ), f"expected 0 full years on all-zero series, got {adj.n_full_years}"
+ assert adj.n_full_years == 0, (
+ f"expected 0 full years on all-zero series, got {adj.n_full_years}"
+ )
assert adj.applied is False
assert all(f == 1.0 for f in adj.factors.values())
@@ -166,9 +166,9 @@ class TestSeasonalFactors:
# Только январь-июнь каждого года ненулевые → нет полного покрытия 12 мес.
units = [10 if d.month <= 6 else 0 for d in months]
adj = seasonal_factors(months, units)
- assert (
- adj.n_full_years == 0
- ), f"partial-coverage years should not count as full, got {adj.n_full_years}"
+ assert adj.n_full_years == 0, (
+ f"partial-coverage years should not count as full, got {adj.n_full_years}"
+ )
assert adj.applied is False
def test_real_nonzero_series_passes_guard_and_applies(self) -> None:
diff --git a/backend/tests/services/forecasting/test_scenarios.py b/backend/tests/services/forecasting/test_scenarios.py
index 94999a10..2588cb99 100644
--- a/backend/tests/services/forecasting/test_scenarios.py
+++ b/backend/tests/services/forecasting/test_scenarios.py
@@ -609,9 +609,7 @@ class TestDetectCollapsed:
def test_empty_forecasts_not_collapsed(self) -> None:
# Нет данных → нет вердикта «схлопнулось» (не помечаем пустой отчёт collapsed).
cons = _scenario("conservative", forecasts=[])
- base = _scenario(
- "base", forecasts=[_forecast_stub(horizon=12)]
- )
+ base = _scenario("base", forecasts=[_forecast_stub(horizon=12)])
aggr = _scenario("aggressive", forecasts=[])
assert _detect_collapsed([cons, base, aggr]) is False
@@ -750,9 +748,7 @@ class TestComputeScenariosCollapseDetection:
# demand aggressive на 1e-3 от cons/base — за пределами abs_tol=1e-6.
offset = 1e-3 if call_count["n"] == 3 else 0.0
return [
- _forecast_stub(
- horizon=h, deficit_index=0.05, projected_demand_units=100.0 + offset
- )
+ _forecast_stub(horizon=h, deficit_index=0.05, projected_demand_units=100.0 + offset)
for h in horizons
]
diff --git a/backend/tests/services/generative/test_market_price_lookup.py b/backend/tests/services/generative/test_market_price_lookup.py
index 5667f38e..abdd5fe3 100644
--- a/backend/tests/services/generative/test_market_price_lookup.py
+++ b/backend/tests/services/generative/test_market_price_lookup.py
@@ -61,9 +61,9 @@ def test_objective_median_sql_dedups_inline_not_via_view() -> None:
sql = str(concepts._OBJECTIVE_MEDIAN_SQL)
assert "DISTINCT ON (" in sql, "должен дедупить физлоты inline"
assert "snapshot_date DESC" in sql, "берём последний снапшот физлота"
- assert (
- "v_objective_lots_latest" not in sql
- ), "request-path: view материализует всю таблицу — нужен inline DISTINCT ON (#1964)"
+ assert "v_objective_lots_latest" not in sql, (
+ "request-path: view материализует всю таблицу — нужен inline DISTINCT ON (#1964)"
+ )
def test_objective_median_selected_when_sample_large_enough() -> None:
diff --git a/backend/tests/services/generative/test_teap_financial.py b/backend/tests/services/generative/test_teap_financial.py
index a8757ff4..0909f1f9 100644
--- a/backend/tests/services/generative/test_teap_financial.py
+++ b/backend/tests/services/generative/test_teap_financial.py
@@ -82,9 +82,7 @@ def _teap(residential: float, gfa: float, parking: int = 10) -> TEAP:
def test_financial_revenue_includes_parking() -> None:
t = _teap(residential=1000.0, gfa=1300.0, parking=10)
- model = financial.compute_financial(
- teap=t, housing_class="comfort", land_cost_rub=50_000_000.0
- )
+ model = financial.compute_financial(teap=t, housing_class="comfort", land_cost_rub=50_000_000.0)
# revenue = жильё (1000 * 145_000) + паркинг comfort (10 * 1_300_000).
assert model.revenue_residential_rub == 1000.0 * 145_000.0
assert model.revenue_parking_rub == 10 * 1_300_000.0
@@ -93,9 +91,7 @@ def test_financial_revenue_includes_parking() -> None:
def test_financial_cost_cascade_includes_all_lines() -> None:
t = _teap(residential=1000.0, gfa=1300.0, parking=10)
- model = financial.compute_financial(
- teap=t, housing_class="comfort", land_cost_rub=50_000_000.0
- )
+ model = financial.compute_financial(teap=t, housing_class="comfort", land_cost_rub=50_000_000.0)
# СМР = GFA*СМР + паркинг comfort*себест (10 * 1_000_000).
assert model.construction_rub == 1300.0 * 88_000.0 + 10 * 1_000_000.0
# Каждая статья каскада > 0 при ненулевых вводных.
@@ -299,15 +295,18 @@ def test_financial_parking_margin_positive_for_all_classes() -> None:
cost = financial._PARKING_COST_PER_SPOT[hc] # type: ignore[index]
assert price > cost, hc
# Конкретные маржи из спецификации: econom +450k, comfort +300k, business +100k.
- assert financial._PARKING_PRICE_PER_SPOT["econom"] - financial._PARKING_COST_PER_SPOT[
- "econom"
- ] == 450_000.0
- assert financial._PARKING_PRICE_PER_SPOT["comfort"] - financial._PARKING_COST_PER_SPOT[
- "comfort"
- ] == 300_000.0
- assert financial._PARKING_PRICE_PER_SPOT["business"] - financial._PARKING_COST_PER_SPOT[
- "business"
- ] == 100_000.0
+ assert (
+ financial._PARKING_PRICE_PER_SPOT["econom"] - financial._PARKING_COST_PER_SPOT["econom"]
+ == 450_000.0
+ )
+ assert (
+ financial._PARKING_PRICE_PER_SPOT["comfort"] - financial._PARKING_COST_PER_SPOT["comfort"]
+ == 300_000.0
+ )
+ assert (
+ financial._PARKING_PRICE_PER_SPOT["business"] - financial._PARKING_COST_PER_SPOT["business"]
+ == 100_000.0
+ )
def test_financial_econom_parking_cheaper_than_business() -> None:
@@ -399,12 +398,18 @@ def test_synthesize_program_zero_site_area_no_division_error() -> None:
def test_synthesize_program_sections_do_not_affect_teap() -> None:
# sections — метаданные программы; площади уже свёрнуты → ТЭП от них не зависит.
one = teap.synthesize_teap_from_program(
- total_footprint_sqm=2000.0, floors=10, site_area_sqm=5000.0,
- housing_class="comfort", sections=1,
+ total_footprint_sqm=2000.0,
+ floors=10,
+ site_area_sqm=5000.0,
+ housing_class="comfort",
+ sections=1,
)
six = teap.synthesize_teap_from_program(
- total_footprint_sqm=2000.0, floors=10, site_area_sqm=5000.0,
- housing_class="comfort", sections=6,
+ total_footprint_sqm=2000.0,
+ floors=10,
+ site_area_sqm=5000.0,
+ housing_class="comfort",
+ sections=6,
)
assert one == six
diff --git a/backend/tests/services/llm/test_client.py b/backend/tests/services/llm/test_client.py
index 838165a2..63c56f60 100644
--- a/backend/tests/services/llm/test_client.py
+++ b/backend/tests/services/llm/test_client.py
@@ -127,9 +127,7 @@ def test_call_cap_returns_fallback(_enabled: None, monkeypatch: pytest.MonkeyPat
"""call_index >= llm_max_calls_per_request → fallback, провайдер не вызывается."""
monkeypatch.setattr(settings, "llm_max_calls_per_request", 2)
prov = _FakeOpenAILike()
- res = complete(
- system_prompt="sys", payload=SafePayload(text="hi"), provider=prov, call_index=2
- )
+ res = complete(system_prompt="sys", payload=SafePayload(text="hi"), provider=prov, call_index=2)
assert res.reason == "call_cap"
assert prov.calls == 0
@@ -241,9 +239,7 @@ def test_rate_limited_retries_then_fallback(
assert prov.calls == 3
-def test_rate_limited_retry_after_capped(
- _enabled: None, monkeypatch: pytest.MonkeyPatch
-) -> None:
+def test_rate_limited_retry_after_capped(_enabled: None, monkeypatch: pytest.MonkeyPatch) -> None:
"""#1209: серверный Retry-After (86400с при quota-exhaustion) должен капаться
_MAX_BACKOFF_S — иначе time.sleep блокирует anyio-threadpool на часы.
"""
diff --git a/backend/tests/services/llm/test_provider.py b/backend/tests/services/llm/test_provider.py
index eccf3434..18ba9f5b 100644
--- a/backend/tests/services/llm/test_provider.py
+++ b/backend/tests/services/llm/test_provider.py
@@ -88,9 +88,7 @@ def test_complete_builds_request_body(monkeypatch: pytest.MonkeyPatch) -> None:
return _chat_response()
monkeypatch.setattr(OpenAIProvider, "_post", fake_post)
- resp = _provider().complete(
- [{"role": "user", "content": "привет"}], max_output_tokens=256
- )
+ resp = _provider().complete([{"role": "user", "content": "привет"}], max_output_tokens=256)
assert captured["body"]["model"] == "gpt-4o-mini"
assert captured["body"]["max_tokens"] == 256
diff --git a/backend/tests/services/scrapers/test_2464_catalog_waf_breaker.py b/backend/tests/services/scrapers/test_2464_catalog_waf_breaker.py
index ad58d072..151be295 100644
--- a/backend/tests/services/scrapers/test_2464_catalog_waf_breaker.py
+++ b/backend/tests/services/scrapers/test_2464_catalog_waf_breaker.py
@@ -103,7 +103,7 @@ def test_single_waf_does_not_abort() -> None:
Ловит «починку» через abort-on-first: тогда один переходный блок стоил бы
всей ночной докачки.
"""
- stats, touched = _run(lambda oid: (_waf(oid) if oid == 5 else True))
+ stats, touched = _run(lambda oid: _waf(oid) if oid == 5 else True)
assert len(touched) == len(_OBJ_IDS), f"прогон оборвался на одиночном блоке: {len(touched)}"
assert "aborted_on_waf" not in stats
@@ -116,7 +116,7 @@ def test_streak_resets_after_success() -> None:
Без сброса два разрозненных блока плюс третий где-то в конце оборвали бы прогон.
"""
blocked = {2, 4, 6, 8, 10}
- stats, touched = _run(lambda oid: (_waf(oid) if oid in blocked else True))
+ stats, touched = _run(lambda oid: _waf(oid) if oid in blocked else True)
assert len(touched) == len(_OBJ_IDS), f"прогон оборвался: тронуто {len(touched)}"
assert "aborted_on_waf" not in stats
diff --git a/backend/tests/services/scrapers/test_2464_detect_kind_position.py b/backend/tests/services/scrapers/test_2464_detect_kind_position.py
index 647ea28f..2fb124a1 100644
--- a/backend/tests/services/scrapers/test_2464_detect_kind_position.py
+++ b/backend/tests/services/scrapers/test_2464_detect_kind_position.py
@@ -38,9 +38,9 @@ def test_izyatie_in_title_wins_over_later_rezervirovanie() -> None:
f"{_ЗАГОЛОВОК_ИЗЪЯТИЕ}. Изъятию подлежат участки, ранее зарезервированные "
"постановлением о резервировании земель от 12.03.2019."
)
- assert (
- _detect_kind(text, "изъятие") == "изъятие"
- ), "документ об изъятии классифицирован по случайному упоминанию в теле"
+ assert _detect_kind(text, "изъятие") == "изъятие", (
+ "документ об изъятии классифицирован по случайному упоминанию в теле"
+ )
def test_all_parcels_of_the_document_are_affected() -> None:
diff --git a/backend/tests/services/scrapers/test_2464_status_negation.py b/backend/tests/services/scrapers/test_2464_status_negation.py
index c2a6fbcb..d54591ce 100644
--- a/backend/tests/services/scrapers/test_2464_status_negation.py
+++ b/backend/tests/services/scrapers/test_2464_status_negation.py
@@ -98,14 +98,14 @@ def test_negation_does_not_swallow_a_real_status_in_same_block() -> None:
Разбор по первому совпадению вернул бы None и потерял бы его.
"""
result = parse_catalog_flat(_html_with_badge("Квартира не продана. Статус: в продаже"))
- assert (
- result.get("status") == STATUS_FREE
- ), f"настоящий статус в блоке потерян: {result.get('status')!r}"
+ assert result.get("status") == STATUS_FREE, (
+ f"настоящий статус в блоке потерян: {result.get('status')!r}"
+ )
def test_ne_inside_another_word_is_not_a_negation() -> None:
"""Контроль границы слова: «не» внутри другого слова не считается отрицанием."""
result = parse_catalog_flat(_html_with_badge("Цене снижена, квартира продана"))
- assert (
- result.get("status") == STATUS_SOLD
- ), f"«не» в хвосте слова «Цене» съело настоящий статус: {result.get('status')!r}"
+ assert result.get("status") == STATUS_SOLD, (
+ f"«не» в хвосте слова «Цене» съело настоящий статус: {result.get('status')!r}"
+ )
diff --git a/backend/tests/services/scrapers/test_2464_tep_docstring_truth.py b/backend/tests/services/scrapers/test_2464_tep_docstring_truth.py
index 1394c84b..fc809698 100644
--- a/backend/tests/services/scrapers/test_2464_tep_docstring_truth.py
+++ b/backend/tests/services/scrapers/test_2464_tep_docstring_truth.py
@@ -58,9 +58,9 @@ def test_docstring_names_the_actual_behaviour() -> None:
хорошо». Ловит «починку» через вычёркивание неудобной фразы.
"""
doc = (inspect.getdoc(mod._page_contains_table) or "").lower()
- assert (
- "false positive" in doc or "оглавлен" in doc
- ), "докстрока не предупреждает о ложных срабатываниях на оглавлении"
+ assert "false positive" in doc or "оглавлен" in doc, (
+ "докстрока не предупреждает о ложных срабатываниях на оглавлении"
+ )
def test_toc_line_really_is_a_false_positive() -> None:
@@ -97,7 +97,7 @@ def test_seed_comment_does_not_send_to_a_dead_host() -> None:
src = Path(inspect.getsourcefile(mod)).resolve().parents[3]
текст = (src / "app" / "workers" / "tasks" / "ekb_ppt_tep_sync.py").read_text(encoding="utf-8")
- assert (
- "НЕ СУЩЕСТВУЕТ" in текст or "не резолв" in текст.lower()
- ), "комментарий сида не предупреждает, что gisogd.ekburg.ru мёртв"
+ assert "НЕ СУЩЕСТВУЕТ" in текст or "не резолв" in текст.lower(), (
+ "комментарий сида не предупреждает, что gisogd.ekburg.ru мёртв"
+ )
assert "gisogd66.midural.ru" in текст, "не назван живой портал ГИСОГД-СО"
diff --git a/backend/tests/services/scrapers/test_domrf_catalog_object.py b/backend/tests/services/scrapers/test_domrf_catalog_object.py
index 34012edd..30da2a2d 100644
--- a/backend/tests/services/scrapers/test_domrf_catalog_object.py
+++ b/backend/tests/services/scrapers/test_domrf_catalog_object.py
@@ -42,7 +42,7 @@ def test_extract_next_data_from_html() -> None:
def test_extract_next_data_single_quotes() -> None:
"""Тег с одинарными кавычками тоже должен парситься."""
- html = ""
+ html = ''
result = extract_next_data(html)
assert "props" in result
diff --git a/backend/tests/services/scrapers/test_domrf_catalog_parse.py b/backend/tests/services/scrapers/test_domrf_catalog_parse.py
index a9214814..786aec07 100644
--- a/backend/tests/services/scrapers/test_domrf_catalog_parse.py
+++ b/backend/tests/services/scrapers/test_domrf_catalog_parse.py
@@ -76,13 +76,7 @@ def _html_with_badge(badge_text: str, nav_text: str = "другие кварти
def _html_with_label(label: str, value: str, nav_text: str = "") -> str:
"""HTML с лейблом «Статус» и значением в следующем блоке."""
nav = f"{nav_text}" if nav_text else ""
- return (
- ""
- f"{nav}"
- f"{label}"
- f"{value}"
- ""
- )
+ return f"{nav}{label}{value}"
def _html_blocks_only(blocks: list[tuple[str, str]]) -> str:
diff --git a/backend/tests/services/scrapers/test_domrf_flat_plan_url.py b/backend/tests/services/scrapers/test_domrf_flat_plan_url.py
index ef613b7b..acdb2e0f 100644
--- a/backend/tests/services/scrapers/test_domrf_flat_plan_url.py
+++ b/backend/tests/services/scrapers/test_domrf_flat_plan_url.py
@@ -115,10 +115,7 @@ def test_plan_from_img_proximity_to_label() -> None:
"""
без plan-hint в атрибутах, но рядом с блоком «Планировка»."""
plain = f"{BASE_URL}/api/ext/file/imgabc.png"
html = (
- ""
- "Планировка
"
- f'
'
- ""
+ f'Планировка
'
)
got = extract_plan_image_url(html, _collector_for(html))
assert got == plain
diff --git a/backend/tests/services/scrapers/test_domrf_kn_upsert_sql.py b/backend/tests/services/scrapers/test_domrf_kn_upsert_sql.py
index c0f7de00..f31b97bf 100644
--- a/backend/tests/services/scrapers/test_domrf_kn_upsert_sql.py
+++ b/backend/tests/services/scrapers/test_domrf_kn_upsert_sql.py
@@ -60,14 +60,14 @@ class TestUpsertObjectSqlDoUpdateSet:
# clear back to NULL, not get stuck "still problematic" forever).
sql = " ".join(str(UPSERT_OBJECT_SQL).split())
for col in _OBJECT_PREVIOUSLY_OMITTED:
- assert (
- f"{col} = EXCLUDED.{col}" in sql
- ), f"{col} should be direct `= EXCLUDED.{col}` (not COALESCE):\n{sql}"
+ assert f"{col} = EXCLUDED.{col}" in sql, (
+ f"{col} should be direct `= EXCLUDED.{col}` (not COALESCE):\n{sql}"
+ )
# guard against a COALESCE(EXCLUDED.col, ...) formulation, which would
# permanently pin a stale value once ever set instead of refreshing it.
- assert (
- f"COALESCE(EXCLUDED.{col}," not in sql
- ), f"{col} must not be wrapped in COALESCE (needs to be able to clear to NULL)"
+ assert f"COALESCE(EXCLUDED.{col}," not in sql, (
+ f"{col} must not be wrapped in COALESCE (needs to be able to clear to NULL)"
+ )
def test_all_insert_columns_covered_by_do_update_set_or_conflict_target(self) -> None:
# Locks in the class of bug (INSERT populates a column, DO UPDATE SET
diff --git a/backend/tests/services/scrapers/test_okn_egrkn_client.py b/backend/tests/services/scrapers/test_okn_egrkn_client.py
index 6d1740c6..fb7a6d87 100644
--- a/backend/tests/services/scrapers/test_okn_egrkn_client.py
+++ b/backend/tests/services/scrapers/test_okn_egrkn_client.py
@@ -77,9 +77,9 @@ def test_coord_swap_ekb_point_inside_bbox() -> None:
wrong_lat = float(coords[1]) # 60.61 → это вне ЕКБ если использовать как lat
wrong_lon = float(coords[0]) # 56.83 → это вне ЕКБ если использовать как lon
# Финляндия/Норвегия: lat=60.61 формально в диапазоне 55–58.5 FALSE → lat перепутан
- assert not (
- 55.0 <= wrong_lat <= 58.5 and 58.0 <= wrong_lon <= 63.0
- ), f"Тест ошибочно принял перепутанные координаты: wrong_lat={wrong_lat} wrong_lon={wrong_lon}"
+ assert not (55.0 <= wrong_lat <= 58.5 and 58.0 <= wrong_lon <= 63.0), (
+ f"Тест ошибочно принял перепутанные координаты: wrong_lat={wrong_lat} wrong_lon={wrong_lon}"
+ )
def test_coord_swap_inverted_would_fail_bbox_check() -> None:
diff --git a/backend/tests/services/site_finder/test_2464_capacity_savepoints.py b/backend/tests/services/site_finder/test_2464_capacity_savepoints.py
index c43e1d77..760d448e 100644
--- a/backend/tests/services/site_finder/test_2464_capacity_savepoints.py
+++ b/backend/tests/services/site_finder/test_2464_capacity_savepoints.py
@@ -66,9 +66,9 @@ def test_savepoint_is_actually_entered(name: str) -> None:
"""
db = PostgresLikeSession(fail_on=(), rows=[]) # здоровый путь
_call(name, db)
- assert (
- db.savepoints_entered >= 1
- ), f"{name}: запрос выполнен вне SAVEPOINT — при сбое сессия останется aborted"
+ assert db.savepoints_entered >= 1, (
+ f"{name}: запрос выполнен вне SAVEPOINT — при сбое сессия останется aborted"
+ )
assert db.calls >= 1, f"{name}: запрос вообще не выполнялся"
diff --git a/backend/tests/services/site_finder/test_2464_default_profile_order.py b/backend/tests/services/site_finder/test_2464_default_profile_order.py
index b01e3352..6d23bb7a 100644
--- a/backend/tests/services/site_finder/test_2464_default_profile_order.py
+++ b/backend/tests/services/site_finder/test_2464_default_profile_order.py
@@ -27,6 +27,6 @@ def test_select_default_is_ordered() -> None:
"""
from app.services.site_finder.weight_profiles import _SELECT_DEFAULT
- assert re.search(
- r"ORDER BY\s+id\s+ASC", _SELECT_DEFAULT
- ), f"в _SELECT_DEFAULT нет тай-брейка по id:\n{_SELECT_DEFAULT}"
+ assert re.search(r"ORDER BY\s+id\s+ASC", _SELECT_DEFAULT), (
+ f"в _SELECT_DEFAULT нет тай-брейка по id:\n{_SELECT_DEFAULT}"
+ )
diff --git a/backend/tests/services/site_finder/test_2464_heat_loader_tx.py b/backend/tests/services/site_finder/test_2464_heat_loader_tx.py
index 304e6218..cb4cfad1 100644
--- a/backend/tests/services/site_finder/test_2464_heat_loader_tx.py
+++ b/backend/tests/services/site_finder/test_2464_heat_loader_tx.py
@@ -85,9 +85,9 @@ def test_borrowed_session_is_not_committed_per_organization() -> None:
db, visited, n_orgs = _run(own=False)
assert len(visited) == n_orgs
- assert (
- db.commits == 1
- ), f"на чужой сессии {db.commits} коммитов — транзакцией распоряжается вызывающий"
+ assert db.commits == 1, (
+ f"на чужой сессии {db.commits} коммитов — транзакцией распоряжается вызывающий"
+ )
assert not db.closed, "чужая сессия закрыта — её закрывает вызывающий"
diff --git a/backend/tests/services/site_finder/test_2464_teap_input_sanity.py b/backend/tests/services/site_finder/test_2464_teap_input_sanity.py
index 16a86ff1..e885f4a8 100644
--- a/backend/tests/services/site_finder/test_2464_teap_input_sanity.py
+++ b/backend/tests/services/site_finder/test_2464_teap_input_sanity.py
@@ -54,18 +54,18 @@ def test_far_is_used_when_pct_is_impossible() -> None:
"""Отбрасываем только испорченный параметр, остальной расчёт остаётся верным."""
t = _teap(max_building_pct=150.0, max_far=2.0)
assert t is not None
- assert t.total_floor_area_sqm == pytest.approx(
- _ПЛОЩАДЬ * 2.0
- ), f"GFA посчитана не по КСИТ: {t.total_floor_area_sqm}"
+ assert t.total_floor_area_sqm == pytest.approx(_ПЛОЩАДЬ * 2.0), (
+ f"GFA посчитана не по КСИТ: {t.total_floor_area_sqm}"
+ )
def test_impossible_far_is_dropped() -> None:
"""КСИТ 500 — признак порчи разбора, а не сверхплотной застройки."""
t = _teap(max_far=500.0, max_building_pct=40.0, max_floors=10)
assert t is not None
- assert t.total_floor_area_sqm == pytest.approx(
- _ПЛОЩАДЬ * 0.4 * 10
- ), f"использован невозможный КСИТ: GFA={t.total_floor_area_sqm}"
+ assert t.total_floor_area_sqm == pytest.approx(_ПЛОЩАДЬ * 0.4 * 10), (
+ f"использован невозможный КСИТ: GFA={t.total_floor_area_sqm}"
+ )
def test_impossible_floors_is_dropped() -> None:
@@ -127,6 +127,6 @@ def test_footprint_never_exceeds_parcel_even_without_pct_and_floors() -> None:
f"пятно {t.built_area_sqm} на участке {_ПЛОЩАДЬ} — нарушена геометрия, "
"а не только правдоподобие регламента"
)
- assert t.total_floor_area_sqm == pytest.approx(
- _ПЛОЩАДЬ * 2.0
- ), "GFA не должна меняться от ограничения пятна"
+ assert t.total_floor_area_sqm == pytest.approx(_ПЛОЩАДЬ * 2.0), (
+ "GFA не должна меняться от ограничения пятна"
+ )
diff --git a/backend/tests/services/site_finder/test_2464_zouit_label_all_kinds.py b/backend/tests/services/site_finder/test_2464_zouit_label_all_kinds.py
index 31c08f46..18f17a18 100644
--- a/backend/tests/services/site_finder/test_2464_zouit_label_all_kinds.py
+++ b/backend/tests/services/site_finder/test_2464_zouit_label_all_kinds.py
@@ -95,9 +95,9 @@ def test_label_is_independent_of_overlap_order() -> None:
"ZOUIT_NETWORK_OBREMENENIE",
)
assert d1 is not None and d2 is not None
- assert (
- d1["detail"] == d2["detail"]
- ), f"подпись зависит от порядка:\n{d1['detail']}\n{d2['detail']}"
+ assert d1["detail"] == d2["detail"], (
+ f"подпись зависит от порядка:\n{d1['detail']}\n{d2['detail']}"
+ )
def test_duplicate_kind_named_once() -> None:
diff --git a/backend/tests/services/site_finder/test_best_layouts.py b/backend/tests/services/site_finder/test_best_layouts.py
index 8825c9c5..6ddc3906 100644
--- a/backend/tests/services/site_finder/test_best_layouts.py
+++ b/backend/tests/services/site_finder/test_best_layouts.py
@@ -543,14 +543,14 @@ def test_supply_joins_flats_per_object_latest_snapshot() -> None:
sql_text = str(_SUPPLY_BATCH_SQL.text)
# per-object последний снимок flats
assert "flats_latest" in sql_text, "нет flats_latest CTE → supply снова на глобал-max снимке"
- assert (
- "DISTINCT ON (f.obj_id)" in sql_text
- ), "flats не дедупятся per-object → нет per-obj снимка"
+ assert "DISTINCT ON (f.obj_id)" in sql_text, (
+ "flats не дедупятся per-object → нет per-obj снимка"
+ )
# НЕ должно быть джойна по единой внешней дате-параметру (регрессия #1944)
assert ":latest_snap" not in sql_text, "глобальный :latest_snap вернулся → supply=0 регрессия"
- assert (
- "MAX(snapshot_date)" not in sql_text
- ), "глобальный MAX(snapshot_date) → supply=0 регрессия"
+ assert "MAX(snapshot_date)" not in sql_text, (
+ "глобальный MAX(snapshot_date) → supply=0 регрессия"
+ )
# ── Тесты _cap_and_redistribute (Fix SF-09 review) ───────────────────────────
@@ -592,16 +592,16 @@ def test_cap_and_redistribute_invariants(
"""
result, cap_skipped = _cap_and_redistribute(pct_map)
- assert (
- cap_skipped == expect_pathological
- ), f"cap_skipped={cap_skipped} но ожидали {expect_pathological} для {pct_map}"
- assert (
- sum(result.values()) == 100
- ), f"sum={sum(result.values())} != 100 для {pct_map} → {result}"
+ assert cap_skipped == expect_pathological, (
+ f"cap_skipped={cap_skipped} но ожидали {expect_pathological} для {pct_map}"
+ )
+ assert sum(result.values()) == 100, (
+ f"sum={sum(result.values())} != 100 для {pct_map} → {result}"
+ )
if not expect_pathological:
- assert (
- max(result.values()) <= MAX_BUCKET_SHARE_PCT
- ), f"max={max(result.values())} > cap={MAX_BUCKET_SHARE_PCT} для {pct_map} → {result}"
+ assert max(result.values()) <= MAX_BUCKET_SHARE_PCT, (
+ f"max={max(result.values())} > cap={MAX_BUCKET_SHARE_PCT} для {pct_map} → {result}"
+ )
@pytest.mark.parametrize(
@@ -623,14 +623,14 @@ def test_cap_reproduced_failing_cases(
) -> None:
"""Review round-2 reproduced cases: 2-bucket — pathological, 3-bucket — fit cap."""
result, cap_skipped = _cap_and_redistribute(deals)
- assert (
- cap_skipped == expect_pathological
- ), f"cap_skipped={cap_skipped} ожидали {expect_pathological} для {label}"
+ assert cap_skipped == expect_pathological, (
+ f"cap_skipped={cap_skipped} ожидали {expect_pathological} для {label}"
+ )
assert sum(result.values()) == 100, f"sum != 100 для {label} → {result}"
if not expect_pathological:
- assert (
- max(result.values()) <= MAX_BUCKET_SHARE_PCT
- ), f"max={max(result.values())} > {MAX_BUCKET_SHARE_PCT} для {label} → {result}"
+ assert max(result.values()) <= MAX_BUCKET_SHARE_PCT, (
+ f"max={max(result.values())} > {MAX_BUCKET_SHARE_PCT} для {label} → {result}"
+ )
def test_cap_iteration_count_bounded() -> None:
@@ -773,9 +773,9 @@ def test_group_radius_objects_prod_case_groups() -> None:
assert len(groups) == 6, f"ожидалось 6 групп, получено {len(groups)}: {got}"
# load-bearing инвариант: 7 ключей и безымянные Эфесы разделены >300 м
assert frozenset({15731}) in got, "7 ключей должна быть отдельной группой"
- assert (
- frozenset({55320, 55321, 55322}) in got
- ), "3 безымянных Эфеса — один кластер, отдельный от 7 ключей"
+ assert frozenset({55320, 55321, 55322}) in got, (
+ "3 безымянных Эфеса — один кластер, отдельный от 7 ключей"
+ )
assert got == expected
@@ -1155,9 +1155,9 @@ def test_supply_only_velocity_group_not_duplicated() -> None:
]
for call in supply_calls:
params = call.args[1] if len(call.args) > 1 else call.kwargs
- assert "Траектория" not in (
- params.get("names") or []
- ), "velocity-проект не должен уходить в supply-only запрос"
+ assert "Траектория" not in (params.get("names") or []), (
+ "velocity-проект не должен уходить в supply-only запрос"
+ )
def test_supply_only_empty_snapshot_yields_empty_block() -> None:
diff --git a/backend/tests/services/site_finder/test_competitors_parking.py b/backend/tests/services/site_finder/test_competitors_parking.py
index 58fb795d..fecc42eb 100644
--- a/backend/tests/services/site_finder/test_competitors_parking.py
+++ b/backend/tests/services/site_finder/test_competitors_parking.py
@@ -133,18 +133,14 @@ def _competitors_response(*obj_ids: int) -> CompetitorsResponse:
def test_parking_wire_happy_path(monkeypatch: Any) -> None:
"""top-конкурент сматчился на здание → получает parking_ratio из НСПД."""
- monkeypatch.setattr(
- competitors_mod, "get_competitors", lambda **_: _competitors_response(101)
- )
+ monkeypatch.setattr(competitors_mod, "get_competitors", lambda **_: _competitors_response(101))
monkeypatch.setattr(
competitors_mod,
"resolve_cad_for_domrf",
lambda *_a, **_k: BuildingMatch("66:41:0106036:183", 40995027, 12.3),
)
listing = ObjectsListing(objdoc_id=40995027, flats_count=200, parking_count=100)
- monkeypatch.setattr(
- competitors_mod, "get_building_premises_for_match", lambda _m: listing
- )
+ monkeypatch.setattr(competitors_mod, "get_building_premises_for_match", lambda _m: listing)
out = competitors_mod.get_competitors_parking(
MagicMock(), "66:41:0303001:1", CompetitorsRequest()
@@ -192,9 +188,7 @@ def test_parking_wire_limits_to_top_n(monkeypatch: Any) -> None:
def test_parking_wire_no_geom_match_graceful(monkeypatch: Any) -> None:
"""geom-match промахнулся → конкурент в items с None-полями, matched_count=0."""
- monkeypatch.setattr(
- competitors_mod, "get_competitors", lambda **_: _competitors_response(7)
- )
+ monkeypatch.setattr(competitors_mod, "get_competitors", lambda **_: _competitors_response(7))
monkeypatch.setattr(competitors_mod, "resolve_cad_for_domrf", lambda *_a, **_k: None)
premises = MagicMock()
monkeypatch.setattr(competitors_mod, "get_building_premises_for_match", premises)
@@ -212,9 +206,7 @@ def test_parking_wire_no_geom_match_graceful(monkeypatch: Any) -> None:
def test_parking_wire_premises_none_keeps_cad(monkeypatch: Any) -> None:
"""Здание сматчилось, но НСПД premises=None → cad есть, parking_ratio None."""
- monkeypatch.setattr(
- competitors_mod, "get_competitors", lambda **_: _competitors_response(9)
- )
+ monkeypatch.setattr(competitors_mod, "get_competitors", lambda **_: _competitors_response(9))
monkeypatch.setattr(
competitors_mod,
"resolve_cad_for_domrf",
@@ -234,9 +226,7 @@ def test_parking_wire_premises_none_keeps_cad(monkeypatch: Any) -> None:
def test_parking_wire_premises_exception_graceful(monkeypatch: Any) -> None:
"""Неожиданное исключение в premises-lookup → конкурент без паркинга, не 500."""
- monkeypatch.setattr(
- competitors_mod, "get_competitors", lambda **_: _competitors_response(11)
- )
+ monkeypatch.setattr(competitors_mod, "get_competitors", lambda **_: _competitors_response(11))
monkeypatch.setattr(
competitors_mod,
"resolve_cad_for_domrf",
diff --git a/backend/tests/services/site_finder/test_pat_subzones.py b/backend/tests/services/site_finder/test_pat_subzones.py
index 826f8ef0..61b79e38 100644
--- a/backend/tests/services/site_finder/test_pat_subzones.py
+++ b/backend/tests/services/site_finder/test_pat_subzones.py
@@ -321,9 +321,9 @@ def test_json_path_candidates_end_with_expected_suffix() -> None:
expected_suffix = pathlib.Path("data") / "pat" / "koltsovo_367p_subzones.json"
for candidate in _JSON_PATH_CANDIDATES:
# последние 3 части пути совпадают с ожидаемыми
- assert (
- candidate.parts[-3:] == expected_suffix.parts
- ), f"Кандидат {candidate} не заканчивается на {expected_suffix}"
+ assert candidate.parts[-3:] == expected_suffix.parts, (
+ f"Кандидат {candidate} не заканчивается на {expected_suffix}"
+ )
def test_resolve_json_path_finds_real_file() -> None:
diff --git a/backend/tests/services/site_finder/test_permits_nearby.py b/backend/tests/services/site_finder/test_permits_nearby.py
index 36f82ae5..f39f8832 100644
--- a/backend/tests/services/site_finder/test_permits_nearby.py
+++ b/backend/tests/services/site_finder/test_permits_nearby.py
@@ -201,9 +201,9 @@ def test_sql_excludes_the_amendments_group() -> None:
держаться на том, что таких строк «пока нет».
"""
sql = str(_PERMITS_NEARBY_SQL)
- assert (
- "doc_group IN ('RS', 'RV')" in sql
- ), f"запрос не сужен по группе — строки 'IZ' сломают total_count:\n{sql}"
+ assert "doc_group IN ('RS', 'RV')" in sql, (
+ f"запрос не сужен по группе — строки 'IZ' сломают total_count:\n{sql}"
+ )
def test_total_equals_rs_plus_rv_even_if_iz_leaks_in() -> None:
diff --git a/backend/tests/services/site_finder/test_supply_layers.py b/backend/tests/services/site_finder/test_supply_layers.py
index 0e11e83f..7a10105f 100644
--- a/backend/tests/services/site_finder/test_supply_layers.py
+++ b/backend/tests/services/site_finder/test_supply_layers.py
@@ -395,12 +395,12 @@ class TestLayer2Hidden:
norm = " ".join(sql.split())
# Оба FILTER-предложения (n_with_free_flats COUNT и hidden_units SUM) должны
# гейтить flat_count IS NOT NULL наравне с free_flats IS NOT NULL.
- assert (
- norm.count("flat_count IS NOT NULL") == 2
- ), f"expected flat_count IS NOT NULL guard on both COUNT and SUM filters:\n{sql}"
- assert (
- norm.count("free_flats IS NOT NULL") == 2
- ), f"expected free_flats IS NOT NULL guard on both COUNT and SUM filters:\n{sql}"
+ assert norm.count("flat_count IS NOT NULL") == 2, (
+ f"expected flat_count IS NOT NULL guard on both COUNT and SUM filters:\n{sql}"
+ )
+ assert norm.count("free_flats IS NOT NULL") == 2, (
+ f"expected free_flats IS NOT NULL guard on both COUNT and SUM filters:\n{sql}"
+ )
def test_row_with_null_flat_count_and_positive_free_flats_excluded_end_to_end(
self,
diff --git a/backend/tests/services/test_2464_docstring_matches_code.py b/backend/tests/services/test_2464_docstring_matches_code.py
index 136bc1f5..b14e07cc 100644
--- a/backend/tests/services/test_2464_docstring_matches_code.py
+++ b/backend/tests/services/test_2464_docstring_matches_code.py
@@ -56,9 +56,9 @@ def test_quarter_dump_docstring_does_not_claim_17_requests() -> None:
from app.services.scrapers.nspd_client import QuarterDump
doc = inspect.getdoc(QuarterDump) or ""
- assert (
- "не сжигать rate-limit на 17 запросов" not in doc
- ), "в докстроке осталось число 17, противоречащее grid-walk"
+ assert "не сжигать rate-limit на 17 запросов" not in doc, (
+ "в докстроке осталось число 17, противоречащее grid-walk"
+ )
def test_on_demand_docstring_does_not_promise_a_60s_window() -> None:
@@ -105,6 +105,6 @@ def test_docstrings_state_the_actual_behaviour() -> None:
assert "include_zouit" in qd, "не назван фактический дефолт дампа"
od = inspect.getdoc(find_active_on_demand_job) or ""
- assert (
- "НИКОГДА" in od or "никогда" in od
- ), "не сказано, что failed не возвращается независимо от давности"
+ assert "НИКОГДА" in od or "никогда" in od, (
+ "не сказано, что failed не возвращается независимо от давности"
+ )
diff --git a/backend/tests/services/test_2464_three_small_leaks.py b/backend/tests/services/test_2464_three_small_leaks.py
index 57479248..a3a5279f 100644
--- a/backend/tests/services/test_2464_three_small_leaks.py
+++ b/backend/tests/services/test_2464_three_small_leaks.py
@@ -42,9 +42,9 @@ def test_water_result_keeps_period() -> None:
patch.object(mod.zipfile, "ZipFile", MagicMock()),
):
res = mod.load_water_reserves_from_docx(MagicMock(), "supply", b"", "http://x")
- assert (
- res.get("period") == "III кв. 2025"
- ), f"период выброшен из ответа: {res} — по логам он есть, у вызывающего нет"
+ assert res.get("period") == "III кв. 2025", (
+ f"период выброшен из ответа: {res} — по логам он есть, у вызывающего нет"
+ )
assert res.get("records") == 1 and res.get("inserted") == 1, res
@@ -108,12 +108,12 @@ def test_placement_warning_uses_actual_footprint_not_catalog() -> None:
src = inspect.getsource(mod.place_program)
хвост = src[src.index("участок мал") :]
- assert (
- "fp_w," in хвост and "fp_d," in хвост
- ), f"в предупреждении не фактические габариты:\n{хвост[:320]}"
- assert (
- "house.footprint_w_m," not in хвост and "house.footprint_d_m," not in хвост
- ), f"в предупреждении остался каталожный размер:\n{хвост[:320]}"
+ assert "fp_w," in хвост and "fp_d," in хвост, (
+ f"в предупреждении не фактические габариты:\n{хвост[:320]}"
+ )
+ assert "house.footprint_w_m," not in хвост and "house.footprint_d_m," not in хвост, (
+ f"в предупреждении остался каталожный размер:\n{хвост[:320]}"
+ )
@pytest.mark.parametrize("имя", ["fp_w", "fp_d"])
diff --git a/backend/tests/services/test_2464a_job_settings_savepoint.py b/backend/tests/services/test_2464a_job_settings_savepoint.py
index 5539f82c..c842536f 100644
--- a/backend/tests/services/test_2464a_job_settings_savepoint.py
+++ b/backend/tests/services/test_2464a_job_settings_savepoint.py
@@ -60,7 +60,7 @@ class _PostgresLikeDb:
def execute(self, *_args: Any, **_kwargs: Any) -> Any:
if self.aborted:
raise AbortedTransactionError(
- "current transaction is aborted, commands ignored until end of " "transaction block"
+ "current transaction is aborted, commands ignored until end of transaction block"
)
self.calls += 1
if self.calls == 1 and self._fail_first:
diff --git a/backend/tests/services/test_analytics_queries_domrf_dedup.py b/backend/tests/services/test_analytics_queries_domrf_dedup.py
index a6db1805..77e0edf1 100644
--- a/backend/tests/services/test_analytics_queries_domrf_dedup.py
+++ b/backend/tests/services/test_analytics_queries_domrf_dedup.py
@@ -116,9 +116,9 @@ class TestActiveCompetitorsCountSqlShape:
# site_status legitimately appears in the CTE's SELECT list (it's the
# column DISTINCT ON needs to expose) -- what must NOT appear is a
# filter predicate on it inside the CTE's WHERE.
- assert (
- "site_status = 'Строящиеся'" not in cte_body
- ), f"site_status must not pre-filter the DISTINCT ON CTE (volatile field):\n{cte_body}"
+ assert "site_status = 'Строящиеся'" not in cte_body, (
+ f"site_status must not pre-filter the DISTINCT ON CTE (volatile field):\n{cte_body}"
+ )
assert "site_status = 'Строящиеся'" in outer_body
def test_district_and_class_are_volatile_applied_after_distinct_on(self) -> None:
@@ -136,12 +136,12 @@ class TestActiveCompetitorsCountSqlShape:
)
cte_body, outer_body = _split_latest_cte(_executed_sql(db, 0))
# CTE WHERE must scope on ONLY the stable region_cd — no volatile predicate.
- assert (
- "district_name = :dn" not in cte_body
- ), f"district_name (volatile) must not pre-filter the DISTINCT ON CTE:\n{cte_body}"
- assert (
- "COALESCE(obj_class, obj_class_fallback) = :cls" not in cte_body
- ), f"obj_class (volatile) must not pre-filter the DISTINCT ON CTE:\n{cte_body}"
+ assert "district_name = :dn" not in cte_body, (
+ f"district_name (volatile) must not pre-filter the DISTINCT ON CTE:\n{cte_body}"
+ )
+ assert "COALESCE(obj_class, obj_class_fallback) = :cls" not in cte_body, (
+ f"obj_class (volatile) must not pre-filter the DISTINCT ON CTE:\n{cte_body}"
+ )
# Both live in the outer WHERE, applied to the deduped true-latest row.
assert "district_name = :dn" in outer_body
assert "COALESCE(obj_class, obj_class_fallback) = :cls" in outer_body
@@ -161,9 +161,9 @@ class TestActiveCompetitorsCountSqlShape:
where_start = cte_body.index("WHERE ") + len("WHERE ")
where_end = cte_body.index("ORDER BY", where_start)
where_clause = cte_body[where_start:where_end].strip()
- assert (
- where_clause == "region_cd = :rc"
- ), f"CTE WHERE must scope on ONLY stable region_cd, got: {where_clause!r}"
+ assert where_clause == "region_cd = :rc", (
+ f"CTE WHERE must scope on ONLY stable region_cd, got: {where_clause!r}"
+ )
def test_no_double_colon_cast(self) -> None:
import re
diff --git a/backend/tests/services/test_ekburg_permits.py b/backend/tests/services/test_ekburg_permits.py
index 51c47cd2..1bd8f8b9 100644
--- a/backend/tests/services/test_ekburg_permits.py
+++ b/backend/tests/services/test_ekburg_permits.py
@@ -397,12 +397,12 @@ class TestMsk66ToWgs84:
result = msk66_to_wgs84("1534814.7997", "394813.2001")
assert result is not None
lon, lat = result
- assert (
- abs(lon - 60.619637) < _TOL_LON_DEG
- ), f"lon={lon:.6f} далеко от эталонного 60.619637 (Δ={abs(lon - 60.619637):.6f}°)"
- assert (
- abs(lat - 56.871948) < _TOL_LAT_DEG
- ), f"lat={lat:.6f} далеко от эталонного 56.871948 (Δ={abs(lat - 56.871948):.6f}°)"
+ assert abs(lon - 60.619637) < _TOL_LON_DEG, (
+ f"lon={lon:.6f} далеко от эталонного 60.619637 (Δ={abs(lon - 60.619637):.6f}°)"
+ )
+ assert abs(lat - 56.871948) < _TOL_LAT_DEG, (
+ f"lat={lat:.6f} далеко от эталонного 56.871948 (Δ={abs(lat - 56.871948):.6f}°)"
+ )
def test_koltsovo_cad_crosscheck(self) -> None:
"""X=1544026.7997/Y=381585.4401 — сверка с centroid cad_parcels 66:41:0503018:248.
@@ -412,12 +412,12 @@ class TestMsk66ToWgs84:
result = msk66_to_wgs84("1544026.7997", "381585.4401")
assert result is not None
lon, lat = result
- assert (
- abs(lon - 60.768255) < _TOL_LON_DEG
- ), f"lon={lon:.6f} далеко от эталонного 60.768255 (Δ={abs(lon - 60.768255):.6f}°)"
- assert (
- abs(lat - 56.752330) < _TOL_LAT_DEG
- ), f"lat={lat:.6f} далеко от эталонного 56.752330 (Δ={abs(lat - 56.752330):.6f}°)"
+ assert abs(lon - 60.768255) < _TOL_LON_DEG, (
+ f"lon={lon:.6f} далеко от эталонного 60.768255 (Δ={abs(lon - 60.768255):.6f}°)"
+ )
+ assert abs(lat - 56.752330) < _TOL_LAT_DEG, (
+ f"lat={lat:.6f} далеко от эталонного 56.752330 (Δ={abs(lat - 56.752330):.6f}°)"
+ )
def test_smorodinovaya_cad_crosscheck(self) -> None:
"""X=1526550.1397/Y=387129.3801 — сверка с centroid cad_parcels 66:41:0306057:89.
@@ -427,12 +427,12 @@ class TestMsk66ToWgs84:
result = msk66_to_wgs84("1526550.1397", "387129.3801")
assert result is not None
lon, lat = result
- assert (
- abs(lon - 60.483224) < _TOL_LON_DEG
- ), f"lon={lon:.6f} далеко от эталонного 60.483224 (Δ={abs(lon - 60.483224):.6f}°)"
- assert (
- abs(lat - 56.803547) < _TOL_LAT_DEG
- ), f"lat={lat:.6f} далеко от эталонного 56.803547 (Δ={abs(lat - 56.803547):.6f}°)"
+ assert abs(lon - 60.483224) < _TOL_LON_DEG, (
+ f"lon={lon:.6f} далеко от эталонного 60.483224 (Δ={abs(lon - 60.483224):.6f}°)"
+ )
+ assert abs(lat - 56.803547) < _TOL_LAT_DEG, (
+ f"lat={lat:.6f} далеко от эталонного 56.803547 (Δ={abs(lat - 56.803547):.6f}°)"
+ )
def test_respublikanskaya_cad_crosscheck(self) -> None:
"""X=1531655.2797/Y=398430.2401 — ул. Республиканская 1а, cad 66:41:0106051:10.
@@ -443,12 +443,12 @@ class TestMsk66ToWgs84:
result = msk66_to_wgs84("1531655.2797", "398430.2401")
assert result is not None
lon, lat = result
- assert (
- abs(lon - 60.568320) < _TOL_LON_DEG
- ), f"lon={lon:.6f} далеко от эталонного 60.568320 (Δ={abs(lon - 60.568320):.6f}°)"
- assert (
- abs(lat - 56.904674) < _TOL_LAT_DEG
- ), f"lat={lat:.6f} далеко от эталонного 56.904674 (Δ={abs(lat - 56.904674):.6f}°)"
+ assert abs(lon - 60.568320) < _TOL_LON_DEG, (
+ f"lon={lon:.6f} далеко от эталонного 60.568320 (Δ={abs(lon - 60.568320):.6f}°)"
+ )
+ assert abs(lat - 56.904674) < _TOL_LAT_DEG, (
+ f"lat={lat:.6f} далеко от эталонного 56.904674 (Δ={abs(lat - 56.904674):.6f}°)"
+ )
def test_kosmonavtov_in_ekb(self) -> None:
"""Пр. Космонавтов — попадает в ЕКБ-регион."""
diff --git a/backend/tests/services/test_newbuilding_crossload.py b/backend/tests/services/test_newbuilding_crossload.py
index 232cc7f1..fecd061d 100644
--- a/backend/tests/services/test_newbuilding_crossload.py
+++ b/backend/tests/services/test_newbuilding_crossload.py
@@ -162,10 +162,10 @@ def test_upsert_sql_coalesce_external_ids():
assert "COALESCE" in do_update_section, "DO UPDATE должен содержать COALESCE"
# yandex_jk_id — строка вида "YANDEX_JK_ID = COALESCE("
- assert (
- "YANDEX_JK_ID = COALESCE(" in do_update_section
- ), "yandex_jk_id в DO UPDATE должен использовать COALESCE чтобы не затирать NULL'ом"
+ assert "YANDEX_JK_ID = COALESCE(" in do_update_section, (
+ "yandex_jk_id в DO UPDATE должен использовать COALESCE чтобы не затирать NULL'ом"
+ )
# cian_internal_house_id — строка вида "CIAN_INTERNAL_HOUSE_ID = COALESCE("
- assert (
- "CIAN_INTERNAL_HOUSE_ID = COALESCE(" in do_update_section
- ), "cian_internal_house_id в DO UPDATE должен использовать COALESCE чтобы не затирать NULL'ом"
+ assert "CIAN_INTERNAL_HOUSE_ID = COALESCE(" in do_update_section, (
+ "cian_internal_house_id в DO UPDATE должен использовать COALESCE чтобы не затирать NULL'ом"
+ )
diff --git a/backend/tests/services/test_recommend_mix_velocity.py b/backend/tests/services/test_recommend_mix_velocity.py
index 73ea6e0a..38804a2c 100644
--- a/backend/tests/services/test_recommend_mix_velocity.py
+++ b/backend/tests/services/test_recommend_mix_velocity.py
@@ -422,9 +422,9 @@ class TestRealisticSrokFallback:
)
srok = result["summary"]["months_to_sellout_total"]
assert srok is not None
- assert (
- lo <= srok <= hi
- ), f"n_comp={n_comp}, area={area}: срок {srok:.1f} вне [{lo}, {hi}]"
+ assert lo <= srok <= hi, (
+ f"n_comp={n_comp}, area={area}: срок {srok:.1f} вне [{lo}, {hi}]"
+ )
def test_scope_has_n_competitors(self) -> None:
"""scope.n_competitors присутствует и равен district+class competitors."""
@@ -522,9 +522,9 @@ class TestObjectivePerBucketPath:
# Studio: macro_mult = sat_factor × trend_factor = 1.0 × 1.0 = 1.0
studio = bkt_map.get("Студии 15-30")
assert studio is not None
- assert studio["velocity_per_month"] == pytest.approx(
- 3.5, rel=0.01
- ), f"Studio velocity={studio['velocity_per_month']:.3f}, ожидалось 3.5"
+ assert studio["velocity_per_month"] == pytest.approx(3.5, rel=0.01), (
+ f"Studio velocity={studio['velocity_per_month']:.3f}, ожидалось 3.5"
+ )
assert studio.get("velocity_source") == "objective_per_bucket"
def test_objective_velocities_vary(self) -> None:
diff --git a/backend/tests/services/test_weather_cache.py b/backend/tests/services/test_weather_cache.py
index ed5b5f71..8e3f6a7c 100644
--- a/backend/tests/services/test_weather_cache.py
+++ b/backend/tests/services/test_weather_cache.py
@@ -553,9 +553,9 @@ class TestWindDirectionAllNone:
f"ожидался None, получено {wind['dominant_direction_deg']!r} — "
"вероятно, fabricated 0.0° из-за atan2(0,0)"
)
- assert (
- wind["dominant_direction_label"] is None
- ), f"ожидался None, получено {wind['dominant_direction_label']!r}"
+ assert wind["dominant_direction_label"] is None, (
+ f"ожидался None, получено {wind['dominant_direction_label']!r}"
+ )
def test_missing_wind_key_gives_none_direction(self) -> None:
"""winddirection_10m_dominant отсутствует в ответе → dominant_direction_deg/label = None."""
diff --git a/backend/tests/sql/test_2464_act_date_backfill.py b/backend/tests/sql/test_2464_act_date_backfill.py
index bc6a0311..9442753f 100644
--- a/backend/tests/sql/test_2464_act_date_backfill.py
+++ b/backend/tests/sql/test_2464_act_date_backfill.py
@@ -106,9 +106,7 @@ def _run() -> dict[tuple[str, date], int]:
session.execute(text(_TEMP))
for cad, d, url in _SEED:
session.execute(
- text(
- "INSERT INTO land_reservation (cad_num, act_date, doc_url)" " VALUES (:c,:d,:u)"
- ),
+ text("INSERT INTO land_reservation (cad_num, act_date, doc_url) VALUES (:c,:d,:u)"),
{"c": cad, "d": d, "u": url},
)
for chunk in _body():
diff --git a/backend/tests/sql/test_2464_land_reservation_dedup.py b/backend/tests/sql/test_2464_land_reservation_dedup.py
index f9e923ee..067302af 100644
--- a/backend/tests/sql/test_2464_land_reservation_dedup.py
+++ b/backend/tests/sql/test_2464_land_reservation_dedup.py
@@ -123,9 +123,9 @@ def test_plain_unique_does_not_deduplicate(db) -> None:
Без этой проверки зелёный тест выше неотличим от «оно и так работало».
"""
_add_constraint(db, nulls_not_distinct=False)
- assert (
- _insert_twice(db) == 2
- ), "обычный UNIQUE неожиданно поймал дубль — значит тест выше ничего не доказывает"
+ assert _insert_twice(db) == 2, (
+ "обычный UNIQUE неожиданно поймал дубль — значит тест выше ничего не доказывает"
+ )
def test_records_with_act_number_still_deduplicate(db) -> None:
@@ -159,9 +159,9 @@ def test_migration_dedup_statement_matches_the_key(db) -> None:
assert delete_stmt is not None, "в миграции нет DELETE — дедуп не выполняется"
body = delete_stmt.group(0)
assert "a.cad_num = b.cad_num" in body, "дедуп не по cad_num"
- assert (
- "a.act_number IS NULL" in body and "b.act_number IS NULL" in body
- ), "дедуп затрагивает записи С номером акта — они и так были уникальны"
+ assert "a.act_number IS NULL" in body and "b.act_number IS NULL" in body, (
+ "дедуп затрагивает записи С номером акта — они и так были уникальны"
+ )
assert "a.id > b.id" in body, "не задан выживающий (минимальный id)"
diff --git a/backend/tests/sql/test_2464_leads_stats_suffix_contract.py b/backend/tests/sql/test_2464_leads_stats_suffix_contract.py
index fc13f048..f83a868f 100644
--- a/backend/tests/sql/test_2464_leads_stats_suffix_contract.py
+++ b/backend/tests/sql/test_2464_leads_stats_suffix_contract.py
@@ -169,9 +169,9 @@ def test_revenue_and_deals_are_named_by_their_scope(seeded) -> None:
просто перестала показывать поле в UI.
"""
stats = _stats(seeded)
- assert (
- stats.get("revenue_window") == _IN_WINDOW_REVENUE
- ), f"revenue_window = {stats.get('revenue_window')}, ожидалось {_IN_WINDOW_REVENUE}"
+ assert stats.get("revenue_window") == _IN_WINDOW_REVENUE, (
+ f"revenue_window = {stats.get('revenue_window')}, ожидалось {_IN_WINDOW_REVENUE}"
+ )
assert stats.get("deals_window") == _IN_WINDOW_DEALS
diff --git a/backend/tests/sql/test_2956_freshness_ignores_failed_dumps.py b/backend/tests/sql/test_2956_freshness_ignores_failed_dumps.py
index 7193158c..c30077ec 100644
--- a/backend/tests/sql/test_2956_freshness_ignores_failed_dumps.py
+++ b/backend/tests/sql/test_2956_freshness_ignores_failed_dumps.py
@@ -110,9 +110,9 @@ def _nspd(db) -> dict:
payload = compute_freshness(db)
rows = [s for s in payload["sources"] if s["source"] == "nspd"]
- assert (
- len(rows) == 1
- ), f"источник nspd не найден в реестре: {[s['source'] for s in payload['sources']]}"
+ assert len(rows) == 1, (
+ f"источник nspd не найден в реестре: {[s['source'] for s in payload['sources']]}"
+ )
return rows[0]
@@ -181,7 +181,7 @@ def test_attempt_is_still_recorded(db) -> None:
assert src["last_attempt_at"] is not None
assert src["last_success_at"] is not None
assert src["last_attempt_at"] > src["last_success_at"], (
- "последняя попытка должна быть новее последнего успеха — иначе провалы " "не видны вообще"
+ "последняя попытка должна быть новее последнего успеха — иначе провалы не видны вообще"
)
diff --git a/backend/tests/sql/test_2986_permits_source_key.py b/backend/tests/sql/test_2986_permits_source_key.py
index b4b2d675..1214708c 100644
--- a/backend/tests/sql/test_2986_permits_source_key.py
+++ b/backend/tests/sql/test_2986_permits_source_key.py
@@ -141,9 +141,9 @@ def test_loader_knows_the_amendments_group() -> None:
"""
from app.services.scrapers.gisogd66 import GROUP_CODE
- assert (
- GROUP_CODE.get("DocIZ") == "IZ"
- ), f"группа изменений не грузится; GROUP_CODE = {GROUP_CODE}"
+ assert GROUP_CODE.get("DocIZ") == "IZ", (
+ f"группа изменений не грузится; GROUP_CODE = {GROUP_CODE}"
+ )
def test_loader_upserts_by_source_key() -> None:
@@ -158,9 +158,9 @@ def test_loader_upserts_by_source_key() -> None:
src = inspect.getsource(_upsert_permit)
assert "ON CONFLICT (source_key)" in src, "UPSERT конфликтует не по source_key"
- assert (
- "ON CONFLICT (doc_group, doc_num)" not in src
- ), "старый ключ всё ещё в запросе — разрешение и изменения схлопнутся"
+ assert "ON CONFLICT (doc_group, doc_num)" not in src, (
+ "старый ключ всё ещё в запросе — разрешение и изменения схлопнутся"
+ )
# ── Механизм и миграция: нужен живой Postgres ────────────────────────────────
@@ -184,9 +184,9 @@ def test_old_key_collapses_permit_and_its_amendment() -> None:
s.execute(text(_OLD_UPSERT), _ИЗМЕНЕНИЕ)
rows = s.execute(text("SELECT doc_name, source_key FROM gisogd_permits")).all()
assert len(rows) == 1, f"ожидали схлопывание, получили {len(rows)} строк"
- assert (
- "Изменения" in rows[0][0]
- ), f"вытеснено не то: осталось {rows[0][0]!r} — на проде остаётся именно изменение"
+ assert "Изменения" in rows[0][0], (
+ f"вытеснено не то: осталось {rows[0][0]!r} — на проде остаётся именно изменение"
+ )
finally:
s.rollback()
s.close()
diff --git a/backend/tests/sql/test_2998_rosreestr_partition_horizon.py b/backend/tests/sql/test_2998_rosreestr_partition_horizon.py
index 1275c420..1236bade 100644
--- a/backend/tests/sql/test_2998_rosreestr_partition_horizon.py
+++ b/backend/tests/sql/test_2998_rosreestr_partition_horizon.py
@@ -141,9 +141,9 @@ def test_schema_01_alone_is_red_for_the_publishable_quarter(sandbox) -> None:
conn, schema = sandbox
have = _partition_starts(conn, schema)
assert have, "песочница пуста — 01_schema не применилась"
- assert max(have) == date(
- 2026, 1, 1
- ), f"горизонт 01-схемы ожидался 2026q1, есть {sorted(have)[-2:]}"
+ assert max(have) == date(2026, 1, 1), (
+ f"горизонт 01-схемы ожидался 2026q1, есть {sorted(have)[-2:]}"
+ )
need = date(2026, 4, 1) # публикуемый квартал на дату инцидента
assert need not in have, "красная сторона не состоялась: 01-схема уже знает Q2 2026"
diff --git a/backend/tests/sql/test_auth_sql_migrations.py b/backend/tests/sql/test_auth_sql_migrations.py
index 29a14105..cd446547 100644
--- a/backend/tests/sql/test_auth_sql_migrations.py
+++ b/backend/tests/sql/test_auth_sql_migrations.py
@@ -115,9 +115,9 @@ def test_migrations_are_transactional() -> None:
]
if not statements or statements[0] != "BEGIN;" or statements[-1] != "COMMIT;":
broken.append(path.name)
- assert (
- not broken
- ), f"Миграции без обёртки BEGIN;/COMMIT;: {broken} (.claude/rules/sql.md → Structure)."
+ assert not broken, (
+ f"Миграции без обёртки BEGIN;/COMMIT;: {broken} (.claude/rules/sql.md → Structure)."
+ )
def test_no_concurrent_index_in_migrations() -> None:
@@ -177,6 +177,6 @@ def test_deploy_workflow_applies_auth_migrations() -> None:
f"В {_DEPLOY_WORKFLOW.name} нет цикла по data/sql/auth/*.sql — миграции БД auth "
"не применяются на деплое."
)
- assert (
- "ops/db-bootstrap/create_auth_db.sql" in workflow
- ), f"В {_DEPLOY_WORKFLOW.name} нет bootstrap-шага создания БД auth."
+ assert "ops/db-bootstrap/create_auth_db.sql" in workflow, (
+ f"В {_DEPLOY_WORKFLOW.name} нет bootstrap-шага создания БД auth."
+ )
diff --git a/backend/tests/sql/test_ddu_price_indicator.py b/backend/tests/sql/test_ddu_price_indicator.py
index df9bd034..9033a404 100644
--- a/backend/tests/sql/test_ddu_price_indicator.py
+++ b/backend/tests/sql/test_ddu_price_indicator.py
@@ -40,8 +40,7 @@ _DB_OK, _DB_ERR = _db_reachable()
pytestmark = pytest.mark.skipif(
not _DB_OK,
reason=(
- "Нет доступной postgres БД (TEST_DATABASE_URL/DATABASE_URL) — "
- f"тест #99 пропущен: {_DB_ERR}"
+ f"Нет доступной postgres БД (TEST_DATABASE_URL/DATABASE_URL) — тест #99 пропущен: {_DB_ERR}"
),
)
@@ -107,10 +106,7 @@ def conn():
def _insert_quarter(cur, q_start, bucket_area, price_m2, n) -> None:
"""Insert n single-flat ДДУ rows at given per-unit area + price/m²."""
- rows = [
- ("002001003000", "ДДУ", 66, q_start, bucket_area, 1, price_m2)
- for _ in range(n)
- ]
+ rows = [("002001003000", "ДДУ", 66, q_start, bucket_area, 1, price_m2) for _ in range(n)]
cur.executemany(
"INSERT INTO rd (realestate_type_code, doc_type, region_code, "
"period_start_date, area, deal_count, price_per_sqm) "
@@ -136,7 +132,7 @@ def _setup(cur: psycopg.Cursor) -> None:
# Bucket 4 (60-80 m²): 2025-Q3 present, 2025-Q4 SPARSE (<10 → filtered),
# 2026-Q1 present. index_previous for 2026-Q1 must compare to 2025-Q3.
_insert_quarter(cur, "2025-07-01", 70, 150000, 11)
- _insert_quarter(cur, "2025-10-01", 70, 999999, 3) # below min_deals → dropped
+ _insert_quarter(cur, "2025-10-01", 70, 999999, 3) # below min_deals → dropped
_insert_quarter(cur, "2026-01-01", 70, 165000, 11)
# Packaged-deal trap: one row area=350 deal_count=7 → per-unit 50 m² (bucket 3),
# NOT bucket 6. Price chosen mid-range so it doesn't move the median much.
diff --git a/backend/tests/sql/test_velocity_alerts.py b/backend/tests/sql/test_velocity_alerts.py
index 44b34bfe..7baf7631 100644
--- a/backend/tests/sql/test_velocity_alerts.py
+++ b/backend/tests/sql/test_velocity_alerts.py
@@ -39,8 +39,7 @@ _DB_OK, _DB_ERR = _db_reachable()
pytestmark = pytest.mark.skipif(
not _DB_OK,
reason=(
- "Нет доступной postgres БД (TEST_DATABASE_URL/DATABASE_URL) — "
- f"тест #17 пропущен: {_DB_ERR}"
+ f"Нет доступной postgres БД (TEST_DATABASE_URL/DATABASE_URL) — тест #17 пропущен: {_DB_ERR}"
),
)
@@ -107,8 +106,15 @@ def _setup(cur: psycopg.Cursor) -> None:
)
snap = "2026-04-28" # stale scrape date; data months end 2025-12 (4-mo gap)
months = [
- "2025-04-01", "2025-05-01", "2025-06-01", "2025-07-01", "2025-08-01",
- "2025-09-01", "2025-10-01", "2025-11-01", "2025-12-01",
+ "2025-04-01",
+ "2025-05-01",
+ "2025-06-01",
+ "2025-07-01",
+ "2025-08-01",
+ "2025-09-01",
+ "2025-10-01",
+ "2025-11-01",
+ "2025-12-01",
]
# obj 1 — sharp drop: prior ~15/mo, recent ~4/mo -> alert
dropper = [16, 14, 15, 17, 13, 14, 5, 4, 3]
diff --git a/backend/tests/test_2464c_photos_session_release.py b/backend/tests/test_2464c_photos_session_release.py
index 10b2a9db..dae3c691 100644
--- a/backend/tests/test_2464c_photos_session_release.py
+++ b/backend/tests/test_2464c_photos_session_release.py
@@ -153,9 +153,9 @@ def test_session_usable_after_close_for_thumb_update(
resp = photos.get_photo(db=_session_with_photo_row, obj_id=1, file_id="f1", size="thumb")
- assert (
- seen.get("in_transaction") is False
- ), "миниатюра генерируется при открытой транзакции — соединение пула занято"
+ assert seen.get("in_transaction") is False, (
+ "миниатюра генерируется при открытой транзакции — соединение пула занято"
+ )
assert getattr(resp, "path", None) == str(generated)
# Главное: запись ПОСЛЕ close() действительно доехала до БД — читаем ОТДЕЛЬНЫМ
diff --git a/backend/tests/test_2867_avg_area_nullable.py b/backend/tests/test_2867_avg_area_nullable.py
index b9a96cfb..2f95a392 100644
--- a/backend/tests/test_2867_avg_area_nullable.py
+++ b/backend/tests/test_2867_avg_area_nullable.py
@@ -74,9 +74,9 @@ def test_row_assembly_keeps_none_not_zero() -> None:
src = inspect.getsource(m)
assert 'if r["avg_area_m2"] is not None else None' in src, "в сборке ряда None → 0.0"
- assert (
- 'round(row["avg_area_m2"], 1) if row["avg_area_m2"] is not None else None' in src
- ), "round(row['avg_area_m2']) без проверки на None"
+ assert 'round(row["avg_area_m2"], 1) if row["avg_area_m2"] is not None else None' in src, (
+ "round(row['avg_area_m2']) без проверки на None"
+ )
def test_mix_weighted_area_excludes_rows_without_area() -> None:
@@ -89,9 +89,9 @@ def test_mix_weighted_area_excludes_rows_without_area() -> None:
src = inspect.getsource(m)
assert "rb_area_total_deals" in src, "нет отдельного знаменателя для площади"
- assert re.search(
- r"rb_area_weighted\[rb\]\s*/\s*rb_area_total_deals\[rb\]", src
- ), "площадь по-прежнему делится на все сделки (rb_deals), а не на ряды с площадью"
+ assert re.search(r"rb_area_weighted\[rb\]\s*/\s*rb_area_total_deals\[rb\]", src), (
+ "площадь по-прежнему делится на все сделки (rb_deals), а не на ряды с площадью"
+ )
def test_pdf_renders_dash_for_missing_area() -> None:
diff --git a/backend/tests/test_audit_middleware.py b/backend/tests/test_audit_middleware.py
index c02e684f..e046d1a0 100644
--- a/backend/tests/test_audit_middleware.py
+++ b/backend/tests/test_audit_middleware.py
@@ -87,9 +87,10 @@ def test_classify_path_forecast() -> None:
def test_classify_path_forecast_export_not_confused_with_forecast() -> None:
"""forecast/export должен дать action='export', НЕ 'forecast'."""
- assert audit_mod.classify_path(
- "/api/v1/parcels/66:41:0204016:10/forecast/export"
- ) == ("export", "66:41:0204016:10")
+ assert audit_mod.classify_path("/api/v1/parcels/66:41:0204016:10/forecast/export") == (
+ "export",
+ "66:41:0204016:10",
+ )
def test_classify_path_unmatched_returns_none() -> None:
@@ -134,9 +135,10 @@ def test_classify_path_insight_nested_path_not_matched() -> None:
def test_classify_path_parcels_method_ignored() -> None:
"""Parcels-паттерны не зависят от method — analyze матчится при любом методе."""
- assert audit_mod.classify_path(
- "/api/v1/parcels/66:41:0204016:10/analyze", "GET"
- ) == ("analyze", "66:41:0204016:10")
+ assert audit_mod.classify_path("/api/v1/parcels/66:41:0204016:10/analyze", "GET") == (
+ "analyze",
+ "66:41:0204016:10",
+ )
# ---------------------------------------------------------------------------
diff --git a/backend/tests/test_gas_grs_loader.py b/backend/tests/test_gas_grs_loader.py
index 54687e72..4725dc3a 100644
--- a/backend/tests/test_gas_grs_loader.py
+++ b/backend/tests/test_gas_grs_loader.py
@@ -167,8 +167,7 @@ def test_parse_no_table_returns_empty() -> None:
def test_parse_no_header_row_returns_empty() -> None:
"""Нет строки-заголовка (маркеры «наименование»+«проектн») → пусто (defensive)."""
html = (
- "| Колонка А | Колонка Б |
"
- "| 1 | 2 |
"
+ ""
)
assert gg.parse_grs_table(html) == []
diff --git a/backend/tests/test_gate_verdict.py b/backend/tests/test_gate_verdict.py
index bc5bd67c..f0dfed29 100644
--- a/backend/tests/test_gate_verdict.py
+++ b/backend/tests/test_gate_verdict.py
@@ -129,9 +129,7 @@ def test_residential_main_vri_zh5_with_mkd_true():
def test_residential_main_vri_overrides_subcategory():
"""main_vri ИЖС перебивает даже raw_props subcategory=2 (authoritative wins)."""
- assert (
- is_residential_zone("Ж-2", None, {"subcategory": 2}, main_vri=_VRI_IZHS_ONLY) is False
- )
+ assert is_residential_zone("Ж-2", None, {"subcategory": 2}, main_vri=_VRI_IZHS_ONLY) is False
def test_residential_empty_main_vri_falls_back_to_regex():
diff --git a/backend/tests/test_poi_score.py b/backend/tests/test_poi_score.py
index ae5aeae3..97cfcffb 100644
--- a/backend/tests/test_poi_score.py
+++ b/backend/tests/test_poi_score.py
@@ -28,9 +28,9 @@ def test_category_weight_metro():
metro_w = _category_weight("metro_stop")
for cat in CATEGORY_WEIGHTS:
if cat != "metro_stop" and cat != "default":
- assert metro_w >= _category_weight(
- cat
- ), f"metro_stop weight {metro_w} должен быть >= {cat} weight {_category_weight(cat)}"
+ assert metro_w >= _category_weight(cat), (
+ f"metro_stop weight {metro_w} должен быть >= {cat} weight {_category_weight(cat)}"
+ )
def test_category_weight_unknown_returns_default():
@@ -130,9 +130,9 @@ def test_metro_beats_school_at_equal_distance():
]
db = _MockDb(rows)
result = compute_poi_weighted_top7(db, "66:41:0204016:10", 56.838, 60.605)
- assert (
- result.top_poi[0].category == "metro_stop"
- ), "При равном расстоянии метро (category_weight=6.0) должно быть выше школы (5.0)"
+ assert result.top_poi[0].category == "metro_stop", (
+ "При равном расстоянии метро (category_weight=6.0) должно быть выше школы (5.0)"
+ )
def test_metro_first_when_close():
@@ -145,7 +145,7 @@ def test_metro_first_when_close():
result = compute_poi_weighted_top7(db, "66:41:0204016:10", 56.838, 60.605)
assert result.top_poi[0].category == "metro_stop", (
"Метро (weight=6.0) в 50м должно быть впереди школы (weight=5.0) в 300м — "
- f"metro_weight={(1/(50+100))*6:.5f} vs school_weight={(1/(300+100))*5:.5f}"
+ f"metro_weight={(1 / (50 + 100)) * 6:.5f} vs school_weight={(1 / (300 + 100)) * 5:.5f}"
)
@@ -194,9 +194,9 @@ def test_score_contribution_in_range():
db = _MockDb(rows)
result = compute_poi_weighted_top7(db, "cad", 56.838, 60.605)
for item in result.top_poi:
- assert (
- 0.0 <= item.score_contribution <= 100.0
- ), f"{item.category} score_contribution={item.score_contribution} вне 0..100"
+ assert 0.0 <= item.score_contribution <= 100.0, (
+ f"{item.category} score_contribution={item.score_contribution} вне 0..100"
+ )
def test_metro_at_zero_distance_scores_high():
@@ -205,9 +205,9 @@ def test_metro_at_zero_distance_scores_high():
rows = [_make_row("Метро у дома", "metro_stop", 0.0)]
db = _MockDb(rows)
result = compute_poi_weighted_top7(db, "cad", 56.838, 60.605)
- assert (
- result.poi_weighted_score >= 19.0
- ), f"Метро у дома (d=0) должно давать ≥19/100, получили {result.poi_weighted_score}"
+ assert result.poi_weighted_score >= 19.0, (
+ f"Метро у дома (d=0) должно давать ≥19/100, получили {result.poi_weighted_score}"
+ )
def test_score_contribution_sum_equals_total():
diff --git a/backend/tests/test_quarter_dump_lookup.py b/backend/tests/test_quarter_dump_lookup.py
index 15d1fdc1..bf58373b 100644
--- a/backend/tests/test_quarter_dump_lookup.py
+++ b/backend/tests/test_quarter_dump_lookup.py
@@ -399,7 +399,7 @@ def _make_zouit_row(
def test_cad_zouit_overlaps_includes_geom_geojson() -> None:
"""#255: каждый overlap содержит geom_geojson (string из ST_AsGeoJSON)."""
geojson_str = (
- '{"type":"Polygon","coordinates":' "[[[60.6,56.8],[60.7,56.8],[60.7,56.9],[60.6,56.8]]]}"
+ '{"type":"Polygon","coordinates":[[[60.6,56.8],[60.7,56.8],[60.7,56.9],[60.6,56.8]]]}'
)
rows = [
_make_zouit_row(
@@ -568,9 +568,9 @@ def test_empty_result_reports_unknown_risk_coverage() -> None:
"""
result = make_empty_result()
- assert (
- "risks_count" in result["nspd_dump"]
- ), "признак покрытия не отдаётся — фронт не сможет отличить «чисто» от «не спрашивали»"
+ assert "risks_count" in result["nspd_dump"], (
+ "признак покрытия не отдаётся — фронт не сможет отличить «чисто» от «не спрашивали»"
+ )
assert result["nspd_dump"]["risks_count"] is None
diff --git a/backend/tests/test_sentry_init.py b/backend/tests/test_sentry_init.py
index 19ff11d1..6abc2b9c 100644
--- a/backend/tests/test_sentry_init.py
+++ b/backend/tests/test_sentry_init.py
@@ -476,9 +476,9 @@ def test_local_variables_never_reach_transport(module: str) -> None:
assert probe["counts"]["exception"] == 1
assert "sentry-wiring-probe boom" in payload, "событие с исключением не доехало"
- assert (
- probe["markers"]["local_var"] not in payload
- ), f"{module}: значение локальной переменной ушло в мониторинг"
+ assert probe["markers"]["local_var"] not in payload, (
+ f"{module}: значение локальной переменной ушло в мониторинг"
+ )
@pytest.mark.parametrize("module", ["app.main", "app.workers.celery_app"])
@@ -493,7 +493,7 @@ def test_scrub_failure_does_not_spawn_second_event(module: str) -> None:
"""
probe = json.loads(_probe(module))
- assert (
- probe["scrub_handler_entries"] == 1
- ), "сбой скраба вернулся вторым событием: строка журнала уходит в мониторинг"
+ assert probe["scrub_handler_entries"] == 1, (
+ "сбой скраба вернулся вторым событием: строка журнала уходит в мониторинг"
+ )
assert probe["counts"]["scrub_failure"] == 1
diff --git a/backend/tests/workers/tasks/test_gas_outlet_sync.py b/backend/tests/workers/tasks/test_gas_outlet_sync.py
index 4cd5c056..2da47ad4 100644
--- a/backend/tests/workers/tasks/test_gas_outlet_sync.py
+++ b/backend/tests/workers/tasks/test_gas_outlet_sync.py
@@ -27,9 +27,9 @@ def test_beat_schedule_includes_gas_outlet_sync_weekly_tuesday() -> None:
with patch.object(beat_schedule, "_build_beat_schedule_from_db", return_value={}):
schedule = beat_schedule.build_beat_schedule()
- assert (
- "gas-outlet-points-sync-weekly" in schedule
- ), f"gas-outlet-points-sync-weekly отсутствует в beat: {sorted(schedule.keys())}"
+ assert "gas-outlet-points-sync-weekly" in schedule, (
+ f"gas-outlet-points-sync-weekly отсутствует в beat: {sorted(schedule.keys())}"
+ )
entry = schedule["gas-outlet-points-sync-weekly"]
assert entry["task"] == "tasks.connection_capacity_sync.sync_gas_outlet_points"
# crontab day_of_week = вторник (2).
diff --git a/backend/tests/workers/tasks/test_okn_objects_sync.py b/backend/tests/workers/tasks/test_okn_objects_sync.py
index 466ed6dd..5984ca3d 100644
--- a/backend/tests/workers/tasks/test_okn_objects_sync.py
+++ b/backend/tests/workers/tasks/test_okn_objects_sync.py
@@ -86,9 +86,9 @@ def test_extract_coords_inverted_would_fail_bbox() -> None:
inverted_lon = feature["geometry"]["coordinates"][0] # 56.83 → не в bbox [58–63]
inverted_lat = feature["geometry"]["coordinates"][1] # 60.61 → не в bbox [55–58.5]
inverted_inside = (55.0 <= inverted_lat <= 58.5) and (58.0 <= inverted_lon <= 63.0)
- assert (
- not inverted_inside
- ), "Перепутанные координаты прошли bbox-фильтр — _extract_coords не обнаружит баг coord swap"
+ assert not inverted_inside, (
+ "Перепутанные координаты прошли bbox-фильтр — _extract_coords не обнаружит баг coord swap"
+ )
# Проверяем что реальный _extract_coords возвращает КОРРЕКТНЫЕ (lon, lat)
result = _extract_coords(feature)
@@ -141,9 +141,9 @@ def test_address_passes_include_ekb_districts() -> None:
"Верх-Исетский",
"Железнодорожный",
}
- assert expected_districts <= set(
- _ADDRESS_PASSES
- ), f"Не все районы ЕКБ в _ADDRESS_PASSES: missing={expected_districts - set(_ADDRESS_PASSES)}"
+ assert expected_districts <= set(_ADDRESS_PASSES), (
+ f"Не все районы ЕКБ в _ADDRESS_PASSES: missing={expected_districts - set(_ADDRESS_PASSES)}"
+ )
def test_address_passes_order_city_before_district() -> None:
diff --git a/backend/tests/workers/test_2464_objective_zombie_sweep.py b/backend/tests/workers/test_2464_objective_zombie_sweep.py
index 25ce10d3..1291ea35 100644
--- a/backend/tests/workers/test_2464_objective_zombie_sweep.py
+++ b/backend/tests/workers/test_2464_objective_zombie_sweep.py
@@ -119,17 +119,17 @@ def test_finished_at_is_last_sign_of_life_not_now() -> None:
"""
sql = _objective_update(_run())
assert sql is not None
- assert (
- "COALESCE(heartbeat_at, started_at)" in sql
- ), f"finished_at ставится не по последнему признаку жизни:\n{sql}"
- assert (
- "finished_at = NOW()" not in sql
- ), f"finished_at = NOW() — время завершения соврано:\n{sql}"
+ assert "COALESCE(heartbeat_at, started_at)" in sql, (
+ f"finished_at ставится не по последнему признаку жизни:\n{sql}"
+ )
+ assert "finished_at = NOW()" not in sql, (
+ f"finished_at = NOW() — время завершения соврано:\n{sql}"
+ )
def test_kn_sweep_still_runs() -> None:
"""Контроль от регресса: добавление Объектива не сломало подметание kn."""
db = _run()
- assert any(
- "kn_scrape_runs" in s for s in db.sql
- ), f"подметание kn_scrape_runs пропало; выполнено: {db.sql}"
+ assert any("kn_scrape_runs" in s for s in db.sql), (
+ f"подметание kn_scrape_runs пропало; выполнено: {db.sql}"
+ )
diff --git a/backend/tests/workers/test_nspd_geo.py b/backend/tests/workers/test_nspd_geo.py
index 8ee58bfb..77e3381a 100644
--- a/backend/tests/workers/test_nspd_geo.py
+++ b/backend/tests/workers/test_nspd_geo.py
@@ -569,9 +569,9 @@ def test_soft_time_limit_exceeded_flushes_heartbeat(monkeypatch: Any) -> None:
for sql, params in captured
if "heartbeat_at = NOW()" in sql and "targets_done" in sql
]
- assert (
- heartbeat_updates
- ), "SoftTimeLimitExceeded handler должен flush'нуть heartbeat с counters перед raise"
+ assert heartbeat_updates, (
+ "SoftTimeLimitExceeded handler должен flush'нуть heartbeat с counters перед raise"
+ )
def test_soft_time_limit_exceeded_does_not_overwrite_paused_with_failed(
diff --git a/backend/tests/workers/test_scrape_freshness_check.py b/backend/tests/workers/test_scrape_freshness_check.py
index 4e05af2e..b014daf9 100644
--- a/backend/tests/workers/test_scrape_freshness_check.py
+++ b/backend/tests/workers/test_scrape_freshness_check.py
@@ -24,8 +24,11 @@ def _source(
def _payload(sources: list[dict[str, Any]], overall: str) -> dict[str, Any]:
- return {"generated_at": "2026-06-25T09:00:00+00:00", "overall_status": overall,
- "sources": sources}
+ return {
+ "generated_at": "2026-06-25T09:00:00+00:00",
+ "overall_status": overall,
+ "sources": sources,
+ }
def _run_with(payload_or_exc: Any) -> tuple[dict[str, Any], MagicMock]:
diff --git a/tradein-mvp/backend/app/api/v1/search.py b/tradein-mvp/backend/app/api/v1/search.py
index 9580c2af..26d34ba3 100644
--- a/tradein-mvp/backend/app/api/v1/search.py
+++ b/tradein-mvp/backend/app/api/v1/search.py
@@ -47,7 +47,9 @@ async def search(
if cached is not None:
logger.info(
"search cache HIT key=%s page=%d size=%d",
- cache_key[:24], params.page, params.page_size,
+ cache_key[:24],
+ params.page,
+ params.page_size,
)
resp = SearchResponse.model_validate(cached)
resp.cache_hit = True
@@ -73,6 +75,10 @@ async def search(
await cache.set(cache_key, response.model_dump(mode="json"), ttl=cache.TTL_SEARCH)
logger.info(
"search MISS page=%d size=%d total=%d items=%d elapsed=%.1fms",
- params.page, params.page_size, total, len(items), elapsed_ms,
+ params.page,
+ params.page_size,
+ total,
+ len(items),
+ elapsed_ms,
)
return response
diff --git a/tradein-mvp/backend/app/services/cache.py b/tradein-mvp/backend/app/services/cache.py
index 7de08ac0..80b2465e 100644
--- a/tradein-mvp/backend/app/services/cache.py
+++ b/tradein-mvp/backend/app/services/cache.py
@@ -48,9 +48,7 @@ class SearchCache:
async def set(self, key: str, value: dict[str, Any], ttl: int) -> None:
try:
- await self._client.set(
- key, json.dumps(value, default=str, ensure_ascii=False), ex=ttl
- )
+ await self._client.set(key, json.dumps(value, default=str, ensure_ascii=False), ex=ttl)
except Exception as e:
logger.warning("redis SET failed key=%s: %s", key[:24], e)
diff --git a/tradein-mvp/backend/app/services/estimator.py b/tradein-mvp/backend/app/services/estimator.py
index 3248e13a..73880ec1 100644
--- a/tradein-mvp/backend/app/services/estimator.py
+++ b/tradein-mvp/backend/app/services/estimator.py
@@ -1091,7 +1091,7 @@ def _save_yandex_history_items(
if skipped_area > 0:
logger.info(
- "yandex_valuation: skipped %d/%d history items with area_m2 <= 0 or None" " (addr=%r)",
+ "yandex_valuation: skipped %d/%d history items with area_m2 <= 0 or None (addr=%r)",
skipped_area,
len(result.history_items),
result.address,
@@ -3328,8 +3328,7 @@ def _price_from_inputs(
)
if same_quarter_ratio > settings.estimate_quarter_match_skip_ratio:
logger.info(
- "quarter_index: Guard-2 skip (same-quarter ratio=%.2f > %.2f)"
- " for %s",
+ "quarter_index: Guard-2 skip (same-quarter ratio=%.2f > %.2f) for %s",
same_quarter_ratio,
settings.estimate_quarter_match_skip_ratio,
target_quarter,
@@ -6980,7 +6979,7 @@ def _enforce_zero_analog_low(
"""
if n_analogs == 0 and confidence != "low":
logger.warning(
- "ghost_anchor_guard #1871: forcing confidence 'low' (was %s, " "median=%s, sources=%s)",
+ "ghost_anchor_guard #1871: forcing confidence 'low' (was %s, median=%s, sources=%s)",
confidence,
median_price,
sources_used,
diff --git a/tradein-mvp/backend/app/services/exporters/trade_in_pdf.py b/tradein-mvp/backend/app/services/exporters/trade_in_pdf.py
index ef896560..ca2aff59 100644
--- a/tradein-mvp/backend/app/services/exporters/trade_in_pdf.py
+++ b/tradein-mvp/backend/app/services/exporters/trade_in_pdf.py
@@ -1665,9 +1665,12 @@ def _build_deals_page(estimate: AggregatedEstimate, input_snapshot: dict, brand)
| Количество сделок по аналогичном объектам |
{_mono(f"{n_deals} шт.")} |
- {f'''| Сделки |
+ {
+ f'''
| Сделки |
{_mono(deals_as_of)} |
'''
- if deals_as_of else ""}
+ if deals_as_of
+ else ""
+ }
Источники данных
diff --git a/tradein-mvp/backend/app/services/house_metadata.py b/tradein-mvp/backend/app/services/house_metadata.py
index ff2d1f87..c69844c8 100644
--- a/tradein-mvp/backend/app/services/house_metadata.py
+++ b/tradein-mvp/backend/app/services/house_metadata.py
@@ -27,9 +27,9 @@ from app.core.config import settings
logger = logging.getLogger(__name__)
OVERPASS_URL = "https://overpass-api.de/api/interpreter"
-_CACHE_RADIUS_M = 40 # дом в пределах 40 м от точки — считаем тем же
-_OVERPASS_RADIUS_M = 25 # ищем здание в 25 м от геокодированной точки
-_CAD_RADIUS_M = 60 # радиус поиска здания в cad_buildings (#393)
+_CACHE_RADIUS_M = 40 # дом в пределах 40 м от точки — считаем тем же
+_OVERPASS_RADIUS_M = 25 # ищем здание в 25 м от геокодированной точки
+_CAD_RADIUS_M = 60 # радиус поиска здания в cad_buildings (#393)
@dataclass(frozen=True, slots=True)
@@ -38,15 +38,18 @@ class HouseMetadata:
lon: float
year_built: int | None
total_floors: int | None
- house_type: str | None # panel / brick / monolith / monolith_brick / other
+ house_type: str | None # panel / brick / monolith / monolith_brick / other
total_units: int | None
- source: str # 'osm' / 'cache'
+ source: str # 'osm' / 'cache'
# ── Парсинг тегов OSM ────────────────────────────────────────────────────────
_YEAR_KEYS = (
- "start_date", "construction_date", "building:year",
- "year_of_construction", "building:start_date",
+ "start_date",
+ "construction_date",
+ "building:year",
+ "year_of_construction",
+ "building:start_date",
)
@@ -110,9 +113,12 @@ def _cache_get(db: Session, lat: float, lon: float) -> HouseMetadata | None:
if row is None:
return None
return HouseMetadata(
- lat=row.lat, lon=row.lon,
- year_built=row.year_built, total_floors=row.total_floors,
- house_type=row.house_type, total_units=row.total_units,
+ lat=row.lat,
+ lon=row.lon,
+ year_built=row.year_built,
+ total_floors=row.total_floors,
+ house_type=row.house_type,
+ total_units=row.total_units,
source="cache",
)
@@ -129,9 +135,12 @@ def _cache_put(db: Session, meta: HouseMetadata, raw: dict) -> None:
"""
),
{
- "lat": meta.lat, "lon": meta.lon,
- "year": meta.year_built, "floors": meta.total_floors,
- "htype": meta.house_type, "units": meta.total_units,
+ "lat": meta.lat,
+ "lon": meta.lon,
+ "year": meta.year_built,
+ "floors": meta.total_floors,
+ "htype": meta.house_type,
+ "units": meta.total_units,
"raw": json.dumps(raw, ensure_ascii=False),
},
)
@@ -184,10 +193,11 @@ def _cad_buildings_get(db: Session, lat: float, lon: float) -> HouseMetadata | N
if row is None:
return None
return HouseMetadata(
- lat=lat, lon=lon,
+ lat=lat,
+ lon=lon,
year_built=row.year_built,
total_floors=row.floors,
- house_type=None, # purpose не маппится в panel/brick
+ house_type=None, # purpose не маппится в panel/brick
total_units=None,
source="cadastre",
)
@@ -219,7 +229,8 @@ async def _overpass_lookup(lat: float, lon: float) -> tuple[HouseMetadata, dict]
best = min(elements, key=_dist2)
tags = best.get("tags", {})
meta = HouseMetadata(
- lat=lat, lon=lon,
+ lat=lat,
+ lon=lon,
year_built=_parse_year(tags),
total_floors=_parse_int(tags, "building:levels"),
house_type=_parse_house_type(tags),
@@ -242,7 +253,10 @@ async def get_house_metadata(lat: float, lon: float, db: Session) -> HouseMetada
if cad is not None:
logger.info(
"house_metadata: cad_buildings (%.5f, %.5f) → year=%s floors=%s",
- lat, lon, cad.year_built, cad.total_floors,
+ lat,
+ lon,
+ cad.year_built,
+ cad.total_floors,
)
return cad
@@ -264,13 +278,15 @@ async def get_house_metadata(lat: float, lon: float, db: Session) -> HouseMetada
_cache_put(db, meta, raw)
logger.info(
"house_metadata OSM: (%.5f, %.5f) → year=%s floors=%s type=%s",
- lat, lon, meta.year_built, meta.total_floors, meta.house_type,
+ lat,
+ lon,
+ meta.year_built,
+ meta.total_floors,
+ meta.house_type,
)
return meta
except Exception:
- logger.warning(
- "house_metadata enrichment failed at (%.5f, %.5f)", lat, lon, exc_info=True
- )
+ logger.warning("house_metadata enrichment failed at (%.5f, %.5f)", lat, lon, exc_info=True)
try:
db.rollback()
except Exception:
diff --git a/tradein-mvp/backend/app/services/matching/houses.py b/tradein-mvp/backend/app/services/matching/houses.py
index 1effa723..531cb1fc 100644
--- a/tradein-mvp/backend/app/services/matching/houses.py
+++ b/tradein-mvp/backend/app/services/matching/houses.py
@@ -191,8 +191,7 @@ def match_or_create_house(
row = (
db.execute(
text(
- "SELECT house_id FROM house_sources "
- "WHERE ext_source = :s AND ext_id = :e LIMIT 1"
+ "SELECT house_id FROM house_sources WHERE ext_source = :s AND ext_id = :e LIMIT 1"
),
{"s": ext_source, "e": str(ext_id)},
)
diff --git a/tradein-mvp/backend/app/tasks/domclick_detail_backfill.py b/tradein-mvp/backend/app/tasks/domclick_detail_backfill.py
index 00d91cf9..affa088b 100644
--- a/tradein-mvp/backend/app/tasks/domclick_detail_backfill.py
+++ b/tradein-mvp/backend/app/tasks/domclick_detail_backfill.py
@@ -125,8 +125,7 @@ def _alert_domclick_cookies(db: Session, run_id: int) -> None:
detail = "кук DomClick нет в БД"
elif expires_at <= now:
detail = (
- f"куки DomClick протухли {expires_at:%Y-%m-%d} "
- f"({(now - expires_at).days} дн. назад)"
+ f"куки DomClick протухли {expires_at:%Y-%m-%d} ({(now - expires_at).days} дн. назад)"
)
else:
detail = "куки DomClick помечены невалидными (last_invalid_at)"
@@ -292,7 +291,7 @@ async def run_domclick_detail_backfill(
# #1182 Phase 2: кооперативный SIGTERM-drain (деплой recreate scraper).
if shutdown_requested():
logger.info(
- "domclick_detail_backfill: run_id=%d SIGTERM-drain — stopping at " "#%d/%d",
+ "domclick_detail_backfill: run_id=%d SIGTERM-drain — stopping at #%d/%d",
run_id,
idx,
len(snapshot),
@@ -331,8 +330,7 @@ async def run_domclick_detail_backfill(
consecutive_blocks += 1
counters.blocked += 1
logger.warning(
- "domclick_detail_backfill: run_id=%d BLOCKED #%d/%d "
- "(consecutive=%d): %s",
+ "domclick_detail_backfill: run_id=%d BLOCKED #%d/%d (consecutive=%d): %s",
run_id,
idx + 1,
len(snapshot),
diff --git a/tradein-mvp/backend/scripts/backfill_houses_dadata.py b/tradein-mvp/backend/scripts/backfill_houses_dadata.py
index ec7dd2be..cabbd302 100644
--- a/tradein-mvp/backend/scripts/backfill_houses_dadata.py
+++ b/tradein-mvp/backend/scripts/backfill_houses_dadata.py
@@ -158,14 +158,11 @@ class Stats:
# для `--priority listings`. Correlated EXISTS бьёт по partial-индексу
# `listings_house_id_fk_idx (house_id_fk) WHERE house_id_fk IS NOT NULL`.
_HAS_ACTIVE_LISTINGS_EXPR = (
- "EXISTS (SELECT 1 FROM listings l "
- "WHERE l.house_id_fk = houses.id AND l.is_active)"
+ "EXISTS (SELECT 1 FROM listings l WHERE l.house_id_fk = houses.id AND l.is_active)"
)
-def _select_candidates(
- db: Session, *, priority: str, limit: int
-) -> list[tuple[HouseRow, str]]:
+def _select_candidates(db: Session, *, priority: str, limit: int) -> list[tuple[HouseRow, str]]:
"""Возвращает список (HouseRow, priority_bucket) для enrichment.
priority:
@@ -477,9 +474,7 @@ async def _run_backfill(
db.rollback()
stats.failed += 1
stats.bump(priority, "failed")
- logger.warning(
- "db_write failed for house_id=%s: %s", row.id, exc
- )
+ logger.warning("db_write failed for house_id=%s: %s", row.id, exc)
if i % _LOG_EVERY == 0:
logger.info(
@@ -568,9 +563,7 @@ async def main(argv: list[str] | None = None) -> int:
db = SessionLocal()
try:
candidates = _select_candidates(db, priority=args.priority, limit=args.limit)
- logger.info(
- "loaded candidates: %d (priority=%s)", len(candidates), args.priority
- )
+ logger.info("loaded candidates: %d (priority=%s)", len(candidates), args.priority)
if not candidates:
logger.info(
"nothing to do — нет rows с dadata_enriched_at IS NULL для priority=%s",
@@ -578,9 +571,7 @@ async def main(argv: list[str] | None = None) -> int:
)
return 0
- stats = await _run_backfill(
- db, candidates, batch=args.batch, dry_run=args.dry_run
- )
+ stats = await _run_backfill(db, candidates, batch=args.batch, dry_run=args.dry_run)
logger.info(
"done: batch=%s processed=%d enriched=%d no_change=%d "
diff --git a/tradein-mvp/backend/scripts/geocode_deals_from_houses.py b/tradein-mvp/backend/scripts/geocode_deals_from_houses.py
index fc1acc15..d924e67d 100644
--- a/tradein-mvp/backend/scripts/geocode_deals_from_houses.py
+++ b/tradein-mvp/backend/scripts/geocode_deals_from_houses.py
@@ -293,17 +293,21 @@ def _build_centroid_map(db: Session) -> dict[str, Centroid]:
would require duplicating the regex logic in plpgsql and risk drift.
8,600 rows is trivial to hold in memory.
"""
- rows = db.execute(
- text(
- "SELECT address, lat, lon "
- "FROM houses "
- "WHERE geom IS NOT NULL "
- " AND lat IS NOT NULL "
- " AND lon IS NOT NULL "
- " AND address IS NOT NULL "
- " AND length(trim(address)) > 0"
+ rows = (
+ db.execute(
+ text(
+ "SELECT address, lat, lon "
+ "FROM houses "
+ "WHERE geom IS NOT NULL "
+ " AND lat IS NOT NULL "
+ " AND lon IS NOT NULL "
+ " AND address IS NOT NULL "
+ " AND length(trim(address)) > 0"
+ )
)
- ).mappings().all()
+ .mappings()
+ .all()
+ )
# street_key → running [lat_sum, lon_sum, n]
acc: dict[str, list[float]] = {}
@@ -328,18 +332,22 @@ def _select_deals_without_coords(db: Session, limit: int) -> list[DealRow]:
Matches `deals_geocode_pending_idx` (WHERE lat IS NULL). A successful
UPDATE sets lat NOT NULL, dropping the row out on the next run.
"""
- rows = db.execute(
- text(
- "SELECT id, address "
- "FROM deals "
- "WHERE lat IS NULL "
- " AND address IS NOT NULL "
- " AND length(trim(address)) > 0 "
- "ORDER BY id "
- "LIMIT CAST(:lim AS int)"
- ),
- {"lim": limit},
- ).mappings().all()
+ rows = (
+ db.execute(
+ text(
+ "SELECT id, address "
+ "FROM deals "
+ "WHERE lat IS NULL "
+ " AND address IS NOT NULL "
+ " AND length(trim(address)) > 0 "
+ "ORDER BY id "
+ "LIMIT CAST(:lim AS int)"
+ ),
+ {"lim": limit},
+ )
+ .mappings()
+ .all()
+ )
return [DealRow(id=r["id"], address=r["address"]) for r in rows]
@@ -415,9 +423,7 @@ def _run_backfill(
else:
try:
with db.begin_nested():
- _update_deal_coords(
- db, deal_id=deal.id, lat=centroid.lat, lon=centroid.lon
- )
+ _update_deal_coords(db, deal_id=deal.id, lat=centroid.lat, lon=centroid.lon)
# Per-row commit so resume picks up exactly where we crashed.
db.commit()
stats.geocoded += 1
@@ -470,9 +476,7 @@ def _report_dry_run(
logger.info("─" * 60)
logger.info("DRY-RUN SUMMARY (no DB writes)")
logger.info("distinct streets with a house centroid: %d", distinct_streets)
- logger.info(
- "deals scanned this run (lat IS NULL, capped by --limit): %d", scanned
- )
+ logger.info("deals scanned this run (lat IS NULL, capped by --limit): %d", scanned)
logger.info("deals matched to a centroid: %d", matched)
logger.info("deals with no street match: %d", stats.no_street_match)
logger.info("match rate on scanned slice: %.1f%%", match_rate * 100.0)
@@ -550,9 +554,7 @@ def main(argv: list[str] | None = None) -> int:
centroids = _build_centroid_map(db)
logger.info("built centroid map: %d distinct streets", len(centroids))
if not centroids:
- logger.warning(
- "no house centroids — houses table has no geocoded rows; nothing to do"
- )
+ logger.warning("no house centroids — houses table has no geocoded rows; nothing to do")
return 0
deals = _select_deals_without_coords(db, args.limit)
@@ -561,9 +563,7 @@ def main(argv: list[str] | None = None) -> int:
logger.info("nothing to do — no deals with lat IS NULL and an address")
return 0
- stats = _run_backfill(
- db, deals, centroids, batch=args.batch, dry_run=args.dry_run
- )
+ stats = _run_backfill(db, deals, centroids, batch=args.batch, dry_run=args.dry_run)
if args.dry_run:
total_null = _count_deals_null(db)
diff --git a/tradein-mvp/backend/scripts/geocode_deals_nominatim.py b/tradein-mvp/backend/scripts/geocode_deals_nominatim.py
index c6876d83..8bdeda9a 100644
--- a/tradein-mvp/backend/scripts/geocode_deals_nominatim.py
+++ b/tradein-mvp/backend/scripts/geocode_deals_nominatim.py
@@ -461,8 +461,7 @@ def _maybe_log_progress(i: int, groups: list[AddressGroup], batch: str, stats: S
"""Emit a progress line every `_LOG_EVERY` distinct addresses."""
if i % _LOG_EVERY == 0:
logger.info(
- "batch=%s progress %d/%d geocoded=%d failed=%d deals_updated=%d "
- "cache=(hit=%d miss=%d)",
+ "batch=%s progress %d/%d geocoded=%d failed=%d deals_updated=%d cache=(hit=%d miss=%d)",
batch,
i,
len(groups),
diff --git a/tradein-mvp/backend/tests/scripts/test_backfill_houses_dadata.py b/tradein-mvp/backend/tests/scripts/test_backfill_houses_dadata.py
index ea4711bf..a1633511 100644
--- a/tradein-mvp/backend/tests/scripts/test_backfill_houses_dadata.py
+++ b/tradein-mvp/backend/tests/scripts/test_backfill_houses_dadata.py
@@ -251,8 +251,12 @@ def test_update_house_enriched_passes_full_payload():
"""UPDATE houses содержит lat/lon/cadnum/fias/qc-codes и dadata_enriched_at = NOW()."""
db = MagicMock()
res = _make_dadata_result(
- qc_geo=0, qc_house=2, lat=56.838, lon=60.586,
- cadnum="66:41:0704045:350", fias="fias-uuid-here",
+ qc_geo=0,
+ qc_house=2,
+ lat=56.838,
+ lon=60.586,
+ cadnum="66:41:0704045:350",
+ fias="fias-uuid-here",
)
_update_house_enriched(db, house_id=42, result=res)
@@ -304,9 +308,7 @@ async def test_run_backfill_qc_geo_0_writes_enriched_update():
"scripts.backfill_houses_dadata.clean_address",
new=AsyncMock(return_value=fake),
):
- stats = await _run_backfill(
- db, [(row, "coords")], batch="b1", dry_run=False
- )
+ stats = await _run_backfill(db, [(row, "coords")], batch="b1", dry_run=False)
assert stats.enriched == 1
assert stats.no_change == 0
@@ -338,9 +340,7 @@ async def test_run_backfill_qc_geo_3_records_attempt_only():
"scripts.backfill_houses_dadata.clean_address",
new=AsyncMock(return_value=fake),
):
- stats = await _run_backfill(
- db, [(row, "coords")], batch="b2", dry_run=False
- )
+ stats = await _run_backfill(db, [(row, "coords")], batch="b2", dry_run=False)
assert stats.enriched == 0
assert stats.no_change == 1
@@ -366,9 +366,7 @@ async def test_run_backfill_dadata_returns_none_records_attempt():
"scripts.backfill_houses_dadata.clean_address",
new=AsyncMock(return_value=None),
):
- stats = await _run_backfill(
- db, [(row, "coords")], batch="b3", dry_run=False
- )
+ stats = await _run_backfill(db, [(row, "coords")], batch="b3", dry_run=False)
assert stats.no_change == 1
assert stats.enriched == 0
@@ -425,9 +423,7 @@ async def test_run_backfill_dry_run_skips_db_writes():
"scripts.backfill_houses_dadata.clean_address",
new=AsyncMock(return_value=fake),
):
- stats = await _run_backfill(
- db, [(row, "coords")], batch="dry", dry_run=True
- )
+ stats = await _run_backfill(db, [(row, "coords")], batch="dry", dry_run=True)
assert stats.processed == 1
assert stats.enriched == 1
diff --git a/tradein-mvp/backend/tests/scripts/test_geocode_deals_from_houses.py b/tradein-mvp/backend/tests/scripts/test_geocode_deals_from_houses.py
index b94e1202..a7c05e8e 100644
--- a/tradein-mvp/backend/tests/scripts/test_geocode_deals_from_houses.py
+++ b/tradein-mvp/backend/tests/scripts/test_geocode_deals_from_houses.py
@@ -105,10 +105,7 @@ def test_street_key_three_spec_variants_collapse_to_same_key():
def test_street_key_strips_region_and_house_number():
- assert (
- _street_key("Свердловская обл., Екатеринбург, ул. Большакова, 17")
- == "большакова"
- )
+ assert _street_key("Свердловская обл., Екатеринбург, ул. Большакова, 17") == "большакова"
def test_street_key_strips_district_marker():
@@ -139,10 +136,7 @@ def test_street_key_does_not_eat_name_starting_like_type_token():
def test_street_key_strips_korpus_and_kv_suffix():
assert _street_key("Екатеринбург, ул. Крауля, 48, корп. 2") == "крауля"
- assert (
- _street_key("РФ, Свердловская обл., Екатеринбург, ул. Ленина, 5, кв. 12")
- == "ленина"
- )
+ assert _street_key("РФ, Свердловская обл., Екатеринбург, ул. Ленина, 5, кв. 12") == "ленина"
def test_street_key_does_not_eat_names_starting_like_apt_suffix():
diff --git a/tradein-mvp/backend/tests/services/test_location_index.py b/tradein-mvp/backend/tests/services/test_location_index.py
index cee70c8c..9fa62103 100644
--- a/tradein-mvp/backend/tests/services/test_location_index.py
+++ b/tradein-mvp/backend/tests/services/test_location_index.py
@@ -173,9 +173,9 @@ def test_freshness_window_is_the_estimator_constant_not_a_copy() -> None:
from app.services import estimator
- assert "LISTINGS_FRESH_DAYS =" not in inspect.getsource(
- lc
- ), "константа скопирована в location_index — она должна ИМПОРТИРОВАТЬСЯ из estimator"
+ assert "LISTINGS_FRESH_DAYS =" not in inspect.getsource(lc), (
+ "константа скопирована в location_index — она должна ИМПОРТИРОВАТЬСЯ из estimator"
+ )
assert lc.LISTINGS_FRESH_DAYS == estimator.LISTINGS_FRESH_DAYS
diff --git a/tradein-mvp/backend/tests/services/test_proxy_rotation.py b/tradein-mvp/backend/tests/services/test_proxy_rotation.py
index a311b7c9..b4fb5b0b 100644
--- a/tradein-mvp/backend/tests/services/test_proxy_rotation.py
+++ b/tradein-mvp/backend/tests/services/test_proxy_rotation.py
@@ -538,9 +538,9 @@ async def test_token_never_appears_in_log_messages_or_sentry_text(
await proxy_rotation.rotate_proxy(db, 1) # type: ignore[arg-type]
for record in caplog.records:
- assert (
- SECRET_TOKEN not in record.getMessage()
- ), f"scenario={name}: token leaked into log message args"
+ assert SECRET_TOKEN not in record.getMessage(), (
+ f"scenario={name}: token leaked into log message args"
+ )
assert sentry_texts, "expected at least one Sentry capture (401 scenario)"
assert all(SECRET_TOKEN not in text for text in sentry_texts)
diff --git a/tradein-mvp/backend/tests/support/identity_modes.py b/tradein-mvp/backend/tests/support/identity_modes.py
index 73c5672a..66d885ec 100644
--- a/tradein-mvp/backend/tests/support/identity_modes.py
+++ b/tradein-mvp/backend/tests/support/identity_modes.py
@@ -111,8 +111,7 @@ def assert_reads_access_state(sql: str, names: SqlNames) -> None:
expected = f"{names.access_state_column} AS access_state"
if expected not in sql:
raise AssertionError(
- f"запрос к реестру не читает колонку состояния текущего режима "
- f"({expected!r}): {sql!r}"
+ f"запрос к реестру не читает колонку состояния текущего режима ({expected!r}): {sql!r}"
)
diff --git a/tradein-mvp/backend/tests/tasks/test_geocode_missing.py b/tradein-mvp/backend/tests/tasks/test_geocode_missing.py
index 0e786ffb..fe362774 100644
--- a/tradein-mvp/backend/tests/tasks/test_geocode_missing.py
+++ b/tradein-mvp/backend/tests/tasks/test_geocode_missing.py
@@ -680,9 +680,9 @@ def test_estimator_fetch_analogs_includes_avito() -> None:
from app.services import estimator
source = inspect.getsource(estimator._fetch_analogs)
- assert (
- "source <> 'avito'" not in source
- ), "Avito exclusion должен быть удалён из estimator._fetch_analogs"
+ assert "source <> 'avito'" not in source, (
+ "Avito exclusion должен быть удалён из estimator._fetch_analogs"
+ )
# ── Admin endpoint smoke (schema-only, no real DB) ────────────────────────────
diff --git a/tradein-mvp/backend/tests/tasks/test_yandex_address_backfill.py b/tradein-mvp/backend/tests/tasks/test_yandex_address_backfill.py
index 2e59eb54..ff51444b 100644
--- a/tradein-mvp/backend/tests/tasks/test_yandex_address_backfill.py
+++ b/tradein-mvp/backend/tests/tasks/test_yandex_address_backfill.py
@@ -38,9 +38,9 @@ def test_extract_address_standard_title() -> None:
)
addr = _extract_address_from_title(html)
assert addr is not None, "Should extract address from standard title"
- assert _RE_HAS_HOUSE_NUMBER.search(
- addr
- ), f"Extracted address should contain house number: {addr!r}"
+ assert _RE_HAS_HOUSE_NUMBER.search(addr), (
+ f"Extracted address should contain house number: {addr!r}"
+ )
assert "Екатеринбург" in addr
assert "Горького" in addr
assert "36" in addr
@@ -54,9 +54,9 @@ def test_extract_address_with_zhk_prefix() -> None:
)
addr = _extract_address_from_title(html)
assert addr is not None, "Should extract address when ЖК prefix is present"
- assert _RE_HAS_HOUSE_NUMBER.search(
- addr
- ), f"Extracted address should contain house number: {addr!r}"
+ assert _RE_HAS_HOUSE_NUMBER.search(addr), (
+ f"Extracted address should contain house number: {addr!r}"
+ )
assert "Екатеринбург" in addr
@@ -97,7 +97,7 @@ def test_extract_address_beryozovsky_with_zhk() -> None:
def test_extract_address_ekb_no_zhk_still_matches() -> None:
"""EKB variant from docstring (no ЖК prefix) still matches after city-agnostic change."""
- html = "Продажа квартиры — Екатеринбург, улица Горького, 36" " — id 7654321"
+ html = "Продажа квартиры — Екатеринбург, улица Горького, 36 — id 7654321"
addr = _extract_address_from_title(html)
assert addr is not None, "EKB no-ЖК title should still match"
assert _RE_HAS_HOUSE_NUMBER.search(addr), f"Should contain house number: {addr!r}"
@@ -116,9 +116,9 @@ def test_extract_address_no_house_number_guard() -> None:
addr = _extract_address_from_title(html)
# If the regex matches at all, the extracted value must not contain a house number.
if addr is not None:
- assert not _RE_HAS_HOUSE_NUMBER.search(
- addr
- ), f"Street-only addr must fail house-number guard: {addr!r}"
+ assert not _RE_HAS_HOUSE_NUMBER.search(addr), (
+ f"Street-only addr must fail house-number guard: {addr!r}"
+ )
def test_extract_address_nbsp_replaced() -> None:
@@ -129,9 +129,9 @@ def test_extract_address_nbsp_replaced() -> None:
)
addr = _extract_address_from_title(html)
assert addr is not None, "\\xa0 should be replaced before regex"
- assert _RE_HAS_HOUSE_NUMBER.search(
- addr
- ), f"Extracted address should contain house number: {addr!r}"
+ assert _RE_HAS_HOUSE_NUMBER.search(addr), (
+ f"Extracted address should contain house number: {addr!r}"
+ )
def test_extract_address_no_title_returns_none() -> None:
diff --git a/tradein-mvp/backend/tests/test_1781_secondary_only_param.py b/tradein-mvp/backend/tests/test_1781_secondary_only_param.py
index 9ca89111..c4d474ab 100644
--- a/tradein-mvp/backend/tests/test_1781_secondary_only_param.py
+++ b/tradein-mvp/backend/tests/test_1781_secondary_only_param.py
@@ -38,9 +38,9 @@ def test_full_load_no_longer_hardcodes_secondary_only() -> None:
src = inspect.getsource(pipeline.run_cian_full_load)
assert "secondary_only=secondary_only" in src, "значение не пробрасывается из параметра"
- assert (
- "secondary_only=True," not in src
- ), "в теле остался хардкод — расписание на него повлиять не сможет"
+ assert "secondary_only=True," not in src, (
+ "в теле остался хардкод — расписание на него повлиять не сможет"
+ )
def test_default_is_unchanged() -> None:
@@ -101,12 +101,12 @@ def test_scraper_resets_the_counter_per_run() -> None:
src = inspect.getsource(CianScraper.fetch_all_secondary)
assert "self.last_dropped_nb = 0" in src, "нет сброса в начале прогона"
- assert "self.last_dropped_nb += " in inspect.getsource(
- CianScraper._paginate_leaf_bucket
- ), "накопление не там, где считается dropped_nb"
- assert (
- getattr(CianScraper, "last_dropped_nb", None) == 0
- ), "нет класс-дефолта: атрибут не прочитается, если прогон упал до первого бакета"
+ assert "self.last_dropped_nb += " in inspect.getsource(CianScraper._paginate_leaf_bucket), (
+ "накопление не там, где считается dropped_nb"
+ )
+ assert getattr(CianScraper, "last_dropped_nb", None) == 0, (
+ "нет класс-дефолта: атрибут не прочитается, если прогон упал до первого бакета"
+ )
def test_filter_still_drops_when_flag_is_on() -> None:
diff --git a/tradein-mvp/backend/tests/test_2674_writers_honor_schema.py b/tradein-mvp/backend/tests/test_2674_writers_honor_schema.py
index 188b185d..0a9c69aa 100644
--- a/tradein-mvp/backend/tests/test_2674_writers_honor_schema.py
+++ b/tradein-mvp/backend/tests/test_2674_writers_honor_schema.py
@@ -142,9 +142,9 @@ def test_house_suggestions_insert_covers_every_declared_column() -> None:
(_SQL_DIR / "064_house_imv_phase_c.sql").read_text("utf-8"), "house_suggestions"
)
written = _insert_columns(inspect.getsource(hib.save_imv_result), "house_suggestions")
- assert (
- declared - {"id"} <= written
- ), f"колонки без писателя: {sorted(declared - {'id'} - written)}"
+ assert declared - {"id"} <= written, (
+ f"колонки без писателя: {sorted(declared - {'id'} - written)}"
+ )
def test_save_imv_result_binds_image_link_and_metrics() -> None:
diff --git a/tradein-mvp/backend/tests/test_2830_pool_bypass_tails.py b/tradein-mvp/backend/tests/test_2830_pool_bypass_tails.py
index a6fb3820..8384a28a 100644
--- a/tradein-mvp/backend/tests/test_2830_pool_bypass_tails.py
+++ b/tradein-mvp/backend/tests/test_2830_pool_bypass_tails.py
@@ -147,9 +147,9 @@ async def test_price_history_takes_pool_node_despite_flag_off() -> None:
"""
from app.core.config import settings
- assert (
- settings.use_proxy_pool_curl is False
- ), "тест обязан идти тем же путём, что прод-контейнер backend: без USE_PROXY_POOL_CURL"
+ assert settings.use_proxy_pool_curl is False, (
+ "тест обязан идти тем же путём, что прод-контейнер backend: без USE_PROXY_POOL_CURL"
+ )
pool = _SpyPool()
await _run_price_history(pool, status_code=200)
assert pool.acquire_calls == ["cian"]
diff --git a/tradein-mvp/backend/tests/test_2924_yandex_resolve_tried_at.py b/tradein-mvp/backend/tests/test_2924_yandex_resolve_tried_at.py
index b196303e..fbb26d18 100644
--- a/tradein-mvp/backend/tests/test_2924_yandex_resolve_tried_at.py
+++ b/tradein-mvp/backend/tests/test_2924_yandex_resolve_tried_at.py
@@ -139,9 +139,9 @@ async def test_failed_resolve_marks_the_house() -> None:
db = await _run(
[{"house_id": 6706, "yandex_jk_slug": None, "yandex_jk_id": None, "ext_id": "286394"}]
)
- assert _tried_updates(db) == [
- 6706
- ], f"неудача резолва не помечена; выполненный SQL: {[s[:50] for s, _ in db.sql]}"
+ assert _tried_updates(db) == [6706], (
+ f"неудача резолва не помечена; выполненный SQL: {[s[:50] for s, _ in db.sql]}"
+ )
@pytest.mark.asyncio
@@ -174,7 +174,7 @@ async def test_force_bypasses_the_marker() -> None:
db = await _run([], force=True)
sql = _select_sql(db)
# Тот же OR-блок, что и у гейта «уже обогащён»: CAST(:force AS boolean) = TRUE OR …
- assert (
- sql.count("CAST(:force AS boolean) = TRUE") >= 2
- ), "маркер не обходится через force — второго OR-блока с :force нет"
+ assert sql.count("CAST(:force AS boolean) = TRUE") >= 2, (
+ "маркер не обходится через force — второго OR-блока с :force нет"
+ )
assert _select_params(db).get("force") is True
diff --git a/tradein-mvp/backend/tests/test_2936_unknown_attr_penalty.py b/tradein-mvp/backend/tests/test_2936_unknown_attr_penalty.py
index 1dea4cab..db4cd3f4 100644
--- a/tradein-mvp/backend/tests/test_2936_unknown_attr_penalty.py
+++ b/tradein-mvp/backend/tests/test_2936_unknown_attr_penalty.py
@@ -48,12 +48,12 @@ def test_null_year_gets_pool_median_penalty() -> None:
by = {c["id"]: c["relevance_score"] for c in pool}
# Известных годов ШЕСТЬ (ид. 1,2,3,4,5 и 7): штрафы 0,1,2,3,4 и 0 → медиана 1.5.
# (Первая редакция теста считала пятерых и ждала 2.0 — арифметика, не код.)
- assert (
- by[6] == 0.5 + 1.5
- ), f"NULL-год должен получить медианный штраф 1.5, получил {by[6] - 0.5}"
- assert (
- by[1] == 0.5 and by[3] == 0.5
- ), "известный год не должен трогаться — его уже оштрафовал SQL"
+ assert by[6] == 0.5 + 1.5, (
+ f"NULL-год должен получить медианный штраф 1.5, получил {by[6] - 0.5}"
+ )
+ assert by[1] == 0.5 and by[3] == 0.5, (
+ "известный год не должен трогаться — его уже оштрафовал SQL"
+ )
def test_null_house_type_gets_pool_median_penalty() -> None:
diff --git a/tradein-mvp/backend/tests/test_2953_nominatim_throttle.py b/tradein-mvp/backend/tests/test_2953_nominatim_throttle.py
index cbe363a7..49c6bd60 100644
--- a/tradein-mvp/backend/tests/test_2953_nominatim_throttle.py
+++ b/tradein-mvp/backend/tests/test_2953_nominatim_throttle.py
@@ -84,8 +84,7 @@ async def test_consecutive_queries_are_spaced(throttle_reset: None) -> None:
assert len(stamps) == 3, f"ожидали 3 запроса, ушло {len(stamps)}"
gaps = _gaps(stamps)
assert all(g >= _TEST_INTERVAL * 0.9 for g in gaps), (
- f"запросы идут вплотную: зазоры {[round(g, 4) for g in gaps]}, "
- f"ожидалось ≥ {_TEST_INTERVAL}"
+ f"запросы идут вплотную: зазоры {[round(g, 4) for g in gaps]}, ожидалось ≥ {_TEST_INTERVAL}"
)
@@ -108,8 +107,7 @@ async def test_failed_lookup_does_not_reset_the_pace(throttle_reset: None) -> No
await geocoder._nominatim_lookup("ненаходимый адрес два")
assert boundary_index > 0 and len(stamps) > boundary_index, (
- f"оба поиска должны были сходить в сеть: {len(stamps)} запросов, "
- f"граница {boundary_index}"
+ f"оба поиска должны были сходить в сеть: {len(stamps)} запросов, граница {boundary_index}"
)
boundary_gap = stamps[boundary_index] - stamps[boundary_index - 1]
assert boundary_gap >= _TEST_INTERVAL * 0.9, (
diff --git a/tradein-mvp/backend/tests/test_2992_upsert_unchanged_gate.py b/tradein-mvp/backend/tests/test_2992_upsert_unchanged_gate.py
index 0b3a0703..dbe3c0ca 100644
--- a/tradein-mvp/backend/tests/test_2992_upsert_unchanged_gate.py
+++ b/tradein-mvp/backend/tests/test_2992_upsert_unchanged_gate.py
@@ -117,9 +117,9 @@ def test_unchanged_rescrape_same_day_does_not_update_the_row() -> None:
assert first is not None, "первая вставка не прошла"
ins, upd = save_listings(db, [_lot(sid)], matcher=_matcher(), region_code=66)
second = _row(db, sid)
- assert (
- second.ctid == first.ctid
- ), f"повторный скрейп неизменного лота переписал строку: ctid {first.ctid}→{second.ctid}"
+ assert second.ctid == first.ctid, (
+ f"повторный скрейп неизменного лота переписал строку: ctid {first.ctid}→{second.ctid}"
+ )
assert (ins, upd) == (0, 0), f"счётчики: inserted={ins} updated={upd}, ждали 0/0"
assert second.last_seen_at == first.last_seen_at, "last_seen_at сдвинулся без нужды"
finally:
@@ -204,13 +204,13 @@ def test_skipped_row_still_yields_listing_id_for_downstream() -> None:
try:
save_listings(db, [lot], matcher=m, region_code=66)
calls_before = m.match_or_create_house.call_count
- assert (
- calls_before == 1
- ), "контроль сконструирован неверно: матчинг не зовётся и в первый раз"
+ assert calls_before == 1, (
+ "контроль сконструирован неверно: матчинг не зовётся и в первый раз"
+ )
save_listings(db, [lot], matcher=m, region_code=66)
- assert (
- m.match_or_create_house.call_count == calls_before + 1
- ), "при пропущенном апдейте матчинг не вызван — listing_id потерян"
+ assert m.match_or_create_house.call_count == calls_before + 1, (
+ "при пропущенном апдейте матчинг не вызван — listing_id потерян"
+ )
finally:
_cleanup(db)
@@ -245,14 +245,14 @@ def test_listing_sources_unchanged_rescrape_same_day_does_not_update() -> None:
upsert_listing_source(db, listing_id=lid, price_rub=5_000_000, **kw)
db.commit()
second = ls_row()
- assert (
- second.ctid == first.ctid
- ), f"listing_sources переписана без изменений: ctid {first.ctid}→{second.ctid}"
+ assert second.ctid == first.ctid, (
+ f"listing_sources переписана без изменений: ctid {first.ctid}→{second.ctid}"
+ )
upsert_listing_source(db, listing_id=lid, price_rub=5_100_000, **kw)
db.commit()
third = ls_row()
- assert (
- third.ctid != second.ctid and third.price_rub == 5_100_000
- ), "изменение цены не записалось"
+ assert third.ctid != second.ctid and third.price_rub == 5_100_000, (
+ "изменение цены не записалось"
+ )
finally:
_cleanup(db)
diff --git a/tradein-mvp/backend/tests/test_2996_mislocated_houses_watchdog.py b/tradein-mvp/backend/tests/test_2996_mislocated_houses_watchdog.py
index 94d57a46..a216f3c8 100644
--- a/tradein-mvp/backend/tests/test_2996_mislocated_houses_watchdog.py
+++ b/tradein-mvp/backend/tests/test_2996_mislocated_houses_watchdog.py
@@ -51,7 +51,7 @@ def test_threshold_sits_in_the_measured_gap() -> None:
порог = getattr(admin, "_MISLOCATED_KM", None)
assert порог is not None, (
- "порога нет вовсе — сторож не задан, дома в Варшаве и Таллине никем " "не считаются"
+ "порога нет вовсе — сторож не задан, дома в Варшаве и Таллине никем не считаются"
)
assert 46.7 < порог < 119.2, (
f"порог {порог} вне измеренного промежутка (46.7, 119.2): ниже — ловит "
@@ -91,9 +91,9 @@ def test_query_compares_house_to_its_own_listings() -> None:
src = str(запрос)
assert "l.house_id_fk = h.id" in src, "дом не сравнивается со СВОИМИ объявлениями"
assert "percentile_disc(0.5)" in src, "берётся не медиана координат объявлений"
- assert not re.search(
- r"ST_Y\(.*BETWEEN", src
- ), "появилась географическая рамка — она не переживёт расширение региона"
+ assert not re.search(r"ST_Y\(.*BETWEEN", src), (
+ "появилась географическая рамка — она не переживёт расширение региона"
+ )
def test_check_stays_out_of_the_polled_endpoint() -> None:
@@ -106,7 +106,7 @@ def test_check_stays_out_of_the_polled_endpoint() -> None:
from app.api.v1 import admin
src = inspect.getsource(admin.get_data_quality)
- assert (
- "percentile_disc" not in src
- ), "тяжёлый агрегат вернулся в опрашиваемую каждые 2 минуты ручку"
+ assert "percentile_disc" not in src, (
+ "тяжёлый агрегат вернулся в опрашиваемую каждые 2 минуты ручку"
+ )
assert "mislocated" not in src.lower(), "проверка снова подмешана в data-quality"
diff --git a/tradein-mvp/backend/tests/test_781_quality_gate.py b/tradein-mvp/backend/tests/test_781_quality_gate.py
index ac689067..26452d64 100644
--- a/tradein-mvp/backend/tests/test_781_quality_gate.py
+++ b/tradein-mvp/backend/tests/test_781_quality_gate.py
@@ -166,9 +166,9 @@ def test_755_anchor_n2_does_not_fire_headline_stays_radius() -> None:
"anchor with n=2 comps must NOT fire (min_comps=4 post-#755)"
)
# Confidence from 5 radius analogs must not be "high" (unique_addr < 7 threshold).
- assert (
- est.confidence != "high"
- ), f"Confidence should not be 'high' with 5 radius analogs, got {est.confidence!r}"
+ assert est.confidence != "high", (
+ f"Confidence should not be 'high' with 5 radius analogs, got {est.confidence!r}"
+ )
def test_755_anchor_n2_pure_unit_confidence_never_high() -> None:
@@ -250,9 +250,9 @@ def test_753_dedup_hash_stable_across_reprice_with_source_id() -> None:
lot_original = _make_scraped_lot(source_url=url, source_id=sid, price_rub=5_000_000)
lot_repriced = _make_scraped_lot(source_url=url, source_id=sid, price_rub=4_900_000)
- assert (
- lot_original.compute_dedup_hash() == lot_repriced.compute_dedup_hash()
- ), "dedup_hash must be stable across reprice when source_id is present (#753)"
+ assert lot_original.compute_dedup_hash() == lot_repriced.compute_dedup_hash(), (
+ "dedup_hash must be stable across reprice when source_id is present (#753)"
+ )
def test_753_dedup_hash_stable_across_context_query_no_source_id() -> None:
@@ -275,9 +275,9 @@ def test_753_dedup_hash_stable_across_context_query_no_source_id() -> None:
source_id=None,
price_rub=5_000_000,
)
- assert (
- lot_v1.compute_dedup_hash() == lot_v2.compute_dedup_hash()
- ), "dedup_hash must strip ?context= query and match for same listing URL (#753)"
+ assert lot_v1.compute_dedup_hash() == lot_v2.compute_dedup_hash(), (
+ "dedup_hash must strip ?context= query and match for same listing URL (#753)"
+ )
def test_753_dedup_hash_differs_for_distinct_listings() -> None:
@@ -312,9 +312,9 @@ def test_753_dedup_hash_source_id_takes_priority_over_url() -> None:
source_url="https://www.avito.ru/ru/offer/9999?some=other",
source_id="9999",
)
- assert (
- lot_canonical.compute_dedup_hash() == lot_redirect.compute_dedup_hash()
- ), "source_id takes priority: same source_id -> same hash regardless of URL (#753)"
+ assert lot_canonical.compute_dedup_hash() == lot_redirect.compute_dedup_hash(), (
+ "source_id takes priority: same source_id -> same hash regardless of URL (#753)"
+ )
# ---------------------------------------------------------------------------
@@ -353,12 +353,12 @@ def test_773_expected_sold_positive_on_anchor_only_path(
# Anchor gave a valid headline.
assert est.median_price_rub > 0, "anchor must produce non-zero headline"
# expected_sold must also be computed (not skipped by empty listings_clean).
- assert (
- est.expected_sold_price_rub is not None
- ), "expected_sold_price_rub must not be None on anchor-only path when ratio present (#773)"
- assert (
- est.expected_sold_price_rub > 0
- ), f"expected_sold_price_rub must be > 0, got {est.expected_sold_price_rub} (#773)"
+ assert est.expected_sold_price_rub is not None, (
+ "expected_sold_price_rub must not be None on anchor-only path when ratio present (#773)"
+ )
+ assert est.expected_sold_price_rub > 0, (
+ f"expected_sold_price_rub must be > 0, got {est.expected_sold_price_rub} (#773)"
+ )
# Consistency: sold = asking * ratio.
assert est.expected_sold_price_rub == round(est.median_price_rub * 0.92)
@@ -376,9 +376,9 @@ def test_773_expected_sold_null_when_no_ratio_anchor_only() -> None:
ratio_tuple=(None, None),
)
assert est.median_price_rub > 0 # anchor headline is valid
- assert (
- est.expected_sold_price_rub is None
- ), "expected_sold must be None when ratio is None, even on anchor-only path (#773)"
+ assert est.expected_sold_price_rub is None, (
+ "expected_sold must be None when ratio is None, even on anchor-only path (#773)"
+ )
# ---------------------------------------------------------------------------
@@ -437,9 +437,9 @@ async def test_754_block_page_http200_page_gt1_graceful_return() -> None:
result = await scraper.fetch_around(56.838, 60.605, radius_m=1000, pages=2)
# The important invariant: no exception raised, result is a list.
- assert isinstance(
- result, list
- ), "fetch_around must return a list, not raise, when page>1 returns 0 cards (#754)"
+ assert isinstance(result, list), (
+ "fetch_around must return a list, not raise, when page>1 returns 0 cards (#754)"
+ )
# ---------------------------------------------------------------------------
@@ -476,9 +476,9 @@ def test_740_median_zero_gives_insufficient_data_true() -> None:
actual_deals=[],
expires_at=datetime(2026, 6, 1, tzinfo=UTC),
)
- assert (
- est.insufficient_data is True
- ), "insufficient_data must be True when median_price_rub=0 (#740)"
+ assert est.insufficient_data is True, (
+ "insufficient_data must be True when median_price_rub=0 (#740)"
+ )
assert est.median_price_rub == 0
@@ -505,9 +505,9 @@ def test_740_positive_median_gives_insufficient_data_false() -> None:
actual_deals=[],
expires_at=datetime(2026, 6, 1, tzinfo=UTC),
)
- assert (
- est.insufficient_data is False
- ), "insufficient_data must be False when median_price_rub>0 (#740)"
+ assert est.insufficient_data is False, (
+ "insufficient_data must be False when median_price_rub>0 (#740)"
+ )
def test_740_insufficient_data_serialized_in_model_dump() -> None:
diff --git a/tradein-mvp/backend/tests/test_alerts_become_events.py b/tradein-mvp/backend/tests/test_alerts_become_events.py
index 22324746..82b0b0f7 100644
--- a/tradein-mvp/backend/tests/test_alerts_become_events.py
+++ b/tradein-mvp/backend/tests/test_alerts_become_events.py
@@ -155,9 +155,9 @@ def test_sber_pull_stall_becomes_event(monkeypatch: pytest.MonkeyPatch) -> None:
now=datetime(2026, 8, 6, tzinfo=UTC),
)
assert out["alert"] == 1
- assert any(
- "sber freshness" in t for t in event_texts(events)
- ), "отставание загрузки не стало событием — WARNING до GlitchTip не долетает"
+ assert any("sber freshness" in t for t in event_texts(events)), (
+ "отставание загрузки не стало событием — WARNING до GlitchTip не долетает"
+ )
def test_sber_old_period_with_healthy_pull_stays_silent(monkeypatch: pytest.MonkeyPatch) -> None:
diff --git a/tradein-mvp/backend/tests/test_asking_to_sold_ratio.py b/tradein-mvp/backend/tests/test_asking_to_sold_ratio.py
index 671d8f58..b12d02a5 100644
--- a/tradein-mvp/backend/tests/test_asking_to_sold_ratio.py
+++ b/tradein-mvp/backend/tests/test_asking_to_sold_ratio.py
@@ -494,13 +494,13 @@ def test_migration_098_band_matches_settings_default() -> None:
from app.tasks.asking_to_sold_ratio import _PPM2_MIN
# Lower bound — matches the 30000 literal in migration 098.
- assert (
- _PPM2_MIN == 30_000
- ), f"_PPM2_MIN changed ({_PPM2_MIN}); update migration 098 seed literals to match"
+ assert _PPM2_MIN == 30_000, (
+ f"_PPM2_MIN changed ({_PPM2_MIN}); update migration 098 seed literals to match"
+ )
# Upper bound — matches the 1200000 literal in migration 098.
- assert (
- Settings().asking_ratio_ppm2_max == 1_200_000
- ), "asking_ratio_ppm2_max default changed; update migration 098 seed literals to match"
+ assert Settings().asking_ratio_ppm2_max == 1_200_000, (
+ "asking_ratio_ppm2_max default changed; update migration 098 seed literals to match"
+ )
# ── area_bucket() Python twin matches _AREA_ROOMS_BUCKET_SQL (#2620) ─────────
diff --git a/tradein-mvp/backend/tests/test_auth_api.py b/tradein-mvp/backend/tests/test_auth_api.py
index 9cd7b5cd..9436d1f7 100644
--- a/tradein-mvp/backend/tests/test_auth_api.py
+++ b/tradein-mvp/backend/tests/test_auth_api.py
@@ -826,18 +826,18 @@ async def test_login_flood_capped_by_rate_while_api_stays_responsive(
# этого вердикта, а не вместо него.
assert probe_latencies, "проба не сделала ни одного запроса"
probe_latencies.sort()
- assert (
- probe_latencies[-1] < 0.5
- ), f"худший сторонний запрос {probe_latencies[-1] * 1000:.0f}мс — API встаёт под флудом входа"
+ assert probe_latencies[-1] < 0.5, (
+ f"худший сторонний запрос {probe_latencies[-1] * 1000:.0f}мс — API встаёт под флудом входа"
+ )
median_probe = probe_latencies[len(probe_latencies) // 2]
assert median_probe < verify_s, (
f"медиана стороннего запроса {median_probe * 1000:.0f}мс ≥ времени одной "
f"сверки — цикл занят проверкой пароля, API стоит"
)
# Мало проб за секунду — тоже занятый цикл: проба просыпается раз в 10мс.
- assert (
- len(probe_latencies) >= 10
- ), f"проба успела всего {len(probe_latencies)} раз за {elapsed:.2f}с — цикл был занят"
+ assert len(probe_latencies) >= 10, (
+ f"проба успела всего {len(probe_latencies)} раз за {elapsed:.2f}с — цикл был занят"
+ )
# 2. ТЕМП ограничен. Флуд предлагал больше попыток в секунду, чем разрешает
# потолок — до bcrypt их доехало не больше него (запас ×1.5 на планировщик).
diff --git a/tradein-mvp/backend/tests/test_backfill_wave2.py b/tradein-mvp/backend/tests/test_backfill_wave2.py
index 8a386aa4..178986ab 100644
--- a/tradein-mvp/backend/tests/test_backfill_wave2.py
+++ b/tradein-mvp/backend/tests/test_backfill_wave2.py
@@ -59,17 +59,13 @@ class TestYandexTitleExtract:
assert not result.endswith(",")
def test_nbsp_replaced(self):
- html = (
- "— Екатеринбург,\xa0улица Горького, 36 — id 12 " "на Яндекс.Недвижимости"
- )
+ html = "— Екатеринбург,\xa0улица Горького, 36 — id 12 на Яндекс.Недвижимости"
result = self._extract(html)
assert result is not None
assert "\xa0" not in result
def test_case_insensitive_city(self):
- html = (
- "— ЕКАТЕРИНБУРГ, улица Горького, 36 — id 777 " "на Яндекс.Недвижимости"
- )
+ html = "— ЕКАТЕРИНБУРГ, улица Горького, 36 — id 777 на Яндекс.Недвижимости"
# regex is case-insensitive; city name retains original case
result = self._extract(html)
assert result is not None
@@ -101,8 +97,7 @@ async def test_backfill_yandex_addresses_saves_enriched():
from app.services.yandex_address_backfill import backfill_yandex_addresses
html_with_addr = (
- "Продажа — Екатеринбург, улица Горького, 36 — id 11 "
- "на Яндекс.Недвижимости"
+ "Продажа — Екатеринбург, улица Горького, 36 — id 11 на Яндекс.Недвижимости"
)
mock_resp = MagicMock()
diff --git a/tradein-mvp/backend/tests/test_backtest_regression_gate.py b/tradein-mvp/backend/tests/test_backtest_regression_gate.py
index 51a26de8..0275db49 100644
--- a/tradein-mvp/backend/tests/test_backtest_regression_gate.py
+++ b/tradein-mvp/backend/tests/test_backtest_regression_gate.py
@@ -46,9 +46,9 @@ _ABS_TOL = 1e-6
def _assert_match(path: str, expected: object, actual: object) -> None:
if isinstance(expected, dict):
assert isinstance(actual, dict), f"{path}: expected dict, got {type(actual).__name__}"
- assert (
- expected.keys() == actual.keys()
- ), f"{path}: key set differs\n expected={sorted(expected)}\n actual= {sorted(actual)}"
+ assert expected.keys() == actual.keys(), (
+ f"{path}: key set differs\n expected={sorted(expected)}\n actual= {sorted(actual)}"
+ )
for k in expected:
_assert_match(f"{path}.{k}", expected[k], actual[k])
elif isinstance(expected, list):
diff --git a/tradein-mvp/backend/tests/test_cian_state_parser.py b/tradein-mvp/backend/tests/test_cian_state_parser.py
index e8d7e4c4..a8665672 100644
--- a/tradein-mvp/backend/tests/test_cian_state_parser.py
+++ b/tradein-mvp/backend/tests/test_cian_state_parser.py
@@ -28,9 +28,7 @@ def _concat_push(mfe: str, entries: list[tuple[str, str]]) -> str:
(не эскейпленных дважды — `.concat([...])` несёт валидный JSON-массив как есть).
"""
items = ",".join(f'{{"key":"{k}","value":{v},"priority":0}}' for k, v in entries)
- return (
- f"window._cianConfig['{mfe}'] = (window._cianConfig['{mfe}'] || [])" f".concat([{items}]);"
- )
+ return f"window._cianConfig['{mfe}'] = (window._cianConfig['{mfe}'] || []).concat([{items}]);"
def _push(mfe: str, key: str, value: str) -> str:
diff --git a/tradein-mvp/backend/tests/test_deactivate_stale_avito.py b/tradein-mvp/backend/tests/test_deactivate_stale_avito.py
index 9dffac4c..fcac27cf 100644
--- a/tradein-mvp/backend/tests/test_deactivate_stale_avito.py
+++ b/tradein-mvp/backend/tests/test_deactivate_stale_avito.py
@@ -253,17 +253,17 @@ def test_migration_100_updates_correct_source() -> None:
def test_migration_100_sets_enabled_true() -> None:
sql = _MIGRATION_100.read_text("utf-8")
# UPDATE … SET enabled = true
- assert re.search(
- r"enabled\s*=\s*true", sql, re.IGNORECASE
- ), "migration 100 must SET enabled = true"
+ assert re.search(r"enabled\s*=\s*true", sql, re.IGNORECASE), (
+ "migration 100 must SET enabled = true"
+ )
def test_migration_100_is_idempotent() -> None:
"""WHERE enabled = false ensures re-running after fix → 0 rows matched."""
sql = _MIGRATION_100.read_text("utf-8")
- assert re.search(
- r"enabled\s*=\s*false", sql, re.IGNORECASE
- ), "migration 100 must guard with AND enabled = false for idempotency"
+ assert re.search(r"enabled\s*=\s*false", sql, re.IGNORECASE), (
+ "migration 100 must guard with AND enabled = false for idempotency"
+ )
def test_migration_100_is_transactional() -> None:
diff --git a/tradein-mvp/backend/tests/test_deactivate_stale_revisit_floor.py b/tradein-mvp/backend/tests/test_deactivate_stale_revisit_floor.py
index fbfc49f5..5ea5fce5 100644
--- a/tradein-mvp/backend/tests/test_deactivate_stale_revisit_floor.py
+++ b/tradein-mvp/backend/tests/test_deactivate_stale_revisit_floor.py
@@ -157,9 +157,9 @@ def test_effective_ttl_covers_every_proven_false_kill(monkeypatch: pytest.Monkey
)
_, update_params = db.update_query
assert update_params is not None
- assert (
- update_params["ttl_days"] == effective
- ), f"{slice_name}: UPDATE получил не поднятый TTL — пол посчитан и выброшен"
+ assert update_params["ttl_days"] == effective, (
+ f"{slice_name}: UPDATE получил не поднятый TTL — пол посчитан и выброшен"
+ )
def _read_cap_mult_from_migration(filename: str, *, source: str) -> int:
diff --git a/tradein-mvp/backend/tests/test_dead_code_sweep_2674.py b/tradein-mvp/backend/tests/test_dead_code_sweep_2674.py
index 5e6e0129..9e437ef9 100644
--- a/tradein-mvp/backend/tests/test_dead_code_sweep_2674.py
+++ b/tradein-mvp/backend/tests/test_dead_code_sweep_2674.py
@@ -82,8 +82,7 @@ def test_domrf_window_does_not_collide_with_matview_refresh() -> None:
start, end = hours[0], hours[1]
matview_start, matview_end = 3, 4 # прод-значение scrape_schedules на 2026-08-06
assert end <= matview_start or start >= matview_end, (
- f"окно {start}-{end} пересекается с refresh_search_matview "
- f"{matview_start}-{matview_end}"
+ f"окно {start}-{end} пересекается с refresh_search_matview {matview_start}-{matview_end}"
)
@@ -248,9 +247,9 @@ def test_price_divergence_is_documented_as_structurally_empty() -> None:
"""Оставленный задел обязан говорить, чем он НЕ является сегодня."""
sql = MIGRATION.read_text(encoding="utf-8")
comment = sql.split("COMMENT ON VIEW v_price_divergence IS", 1)[1].split(";", 1)[0]
- assert (
- "match_or_create_listing" in comment
- ), "комментарий не называет причину пустоты — без неё это просто «пока пусто»"
+ assert "match_or_create_listing" in comment, (
+ "комментарий не называет причину пустоты — без неё это просто «пока пусто»"
+ )
# ─────────────────────────────────────────────────────────────────────────────
diff --git a/tradein-mvp/backend/tests/test_estimator_cohort.py b/tradein-mvp/backend/tests/test_estimator_cohort.py
index b6c572fb..0a4682c0 100644
--- a/tradein-mvp/backend/tests/test_estimator_cohort.py
+++ b/tradein-mvp/backend/tests/test_estimator_cohort.py
@@ -3,6 +3,7 @@
PR 10 (2026-05-24) — covers _target_cohort_range edge cases:
None, out-of-range, exact boundaries, first-match semantics.
"""
+
import os
# Settings requires DATABASE_URL at init time. Set dummy DSN before any app import.
@@ -16,26 +17,26 @@ from app.services.estimator import _target_cohort_range
@pytest.mark.parametrize(
("year", "expected"),
[
- (None, None), # no year → no cohort
- (1900, None), # out-of-range below → no cohort
- (1954, None), # below khrushchev floor
- (1955, (1955, 1969)), # khrushchev floor
- (1960, (1955, 1969)), # khrushchev mid
- (1969, (1955, 1969)), # khrushchev ceiling
- (1970, (1970, 1989)), # brezhnev floor
- (1978, (1970, 1989)), # brezhnev (target audit case)
- (1988, (1970, 1989)), # brezhnev pre-overlap fix held
- (1989, (1970, 1989)), # brezhnev ceiling
- (1990, (1990, 1999)), # late_soviet floor (post-PR10 fix)
- (1995, (1990, 1999)), # late_soviet mid
- (1999, (1990, 1999)), # late_soviet ceiling
- (2000, (2000, 2010)), # 2000s floor
- (2010, (2000, 2010)), # 2000s ceiling
- (2011, (2011, 2100)), # modern floor
- (2024, (2011, 2100)), # modern current
- (2100, (2011, 2100)), # modern ceiling
- (2101, None), # above modern ceiling → no cohort
- (2200, None), # far future → no cohort
+ (None, None), # no year → no cohort
+ (1900, None), # out-of-range below → no cohort
+ (1954, None), # below khrushchev floor
+ (1955, (1955, 1969)), # khrushchev floor
+ (1960, (1955, 1969)), # khrushchev mid
+ (1969, (1955, 1969)), # khrushchev ceiling
+ (1970, (1970, 1989)), # brezhnev floor
+ (1978, (1970, 1989)), # brezhnev (target audit case)
+ (1988, (1970, 1989)), # brezhnev pre-overlap fix held
+ (1989, (1970, 1989)), # brezhnev ceiling
+ (1990, (1990, 1999)), # late_soviet floor (post-PR10 fix)
+ (1995, (1990, 1999)), # late_soviet mid
+ (1999, (1990, 1999)), # late_soviet ceiling
+ (2000, (2000, 2010)), # 2000s floor
+ (2010, (2000, 2010)), # 2000s ceiling
+ (2011, (2011, 2100)), # modern floor
+ (2024, (2011, 2100)), # modern current
+ (2100, (2011, 2100)), # modern ceiling
+ (2101, None), # above modern ceiling → no cohort
+ (2200, None), # far future → no cohort
],
)
def test_target_cohort_range(year, expected):
diff --git a/tradein-mvp/backend/tests/test_estimator_confidence_reliability_consistency.py b/tradein-mvp/backend/tests/test_estimator_confidence_reliability_consistency.py
index 16815488..ccaec2ae 100644
--- a/tradein-mvp/backend/tests/test_estimator_confidence_reliability_consistency.py
+++ b/tradein-mvp/backend/tests/test_estimator_confidence_reliability_consistency.py
@@ -57,9 +57,9 @@ def test_very_low_reliability_forces_confidence_low() -> None:
"""reliability == 'very_low' → confidence forced to 'low', regardless of
what _compute_confidence originally scored."""
for original in ("high", "medium", "low"):
- assert (
- _cap_confidence_by_reliability(original, "very_low") == "low"
- ), f"original={original!r} must be forced to 'low' under very_low reliability"
+ assert _cap_confidence_by_reliability(original, "very_low") == "low", (
+ f"original={original!r} must be forced to 'low' under very_low reliability"
+ )
def test_low_reliability_caps_confidence_at_medium() -> None:
diff --git a/tradein-mvp/backend/tests/test_estimator_expected_sold.py b/tradein-mvp/backend/tests/test_estimator_expected_sold.py
index 51e8a675..c0dece68 100644
--- a/tradein-mvp/backend/tests/test_estimator_expected_sold.py
+++ b/tradein-mvp/backend/tests/test_estimator_expected_sold.py
@@ -68,9 +68,9 @@ def test_bucket_clamping() -> None:
_get_asking_sold_ratio(db, rooms)
# First positional bind param is {"b": bucket}.
bind = db.execute.call_args_list[0].args[1]
- assert (
- bind["b"] == expected_bucket
- ), f"rooms={rooms} → bucket {bind['b']} != {expected_bucket}"
+ assert bind["b"] == expected_bucket, (
+ f"rooms={rooms} → bucket {bind['b']} != {expected_bucket}"
+ )
def test_bucket_keyed_by_area_when_area_known() -> None:
diff --git a/tradein-mvp/backend/tests/test_estimator_expected_sold_clamp.py b/tradein-mvp/backend/tests/test_estimator_expected_sold_clamp.py
index ca8842ac..8cc771d6 100644
--- a/tradein-mvp/backend/tests/test_estimator_expected_sold_clamp.py
+++ b/tradein-mvp/backend/tests/test_estimator_expected_sold_clamp.py
@@ -134,13 +134,13 @@ def test_expected_sold_clamped_to_headline_when_ratio_above_1(
est = _run_estimate((ratio, "per_rooms"), clamp_enabled=True)
assert est.median_price_rub > 0, "headline должен быть задан аналогами"
assert est.expected_sold_price_rub is not None
- assert (
- est.expected_sold_price_rub <= est.median_price_rub
- ), f"expected_sold {est.expected_sold_price_rub} > asking {est.median_price_rub}"
+ assert est.expected_sold_price_rub <= est.median_price_rub, (
+ f"expected_sold {est.expected_sold_price_rub} > asking {est.median_price_rub}"
+ )
assert est.expected_sold_per_m2 is not None
- assert (
- est.expected_sold_per_m2 <= est.median_price_per_m2
- ), "expected_sold_per_m2 превышает median_price_per_m2"
+ assert est.expected_sold_per_m2 <= est.median_price_per_m2, (
+ "expected_sold_per_m2 превышает median_price_per_m2"
+ )
# #1966: expected_sold range is now a calibrated ~80% PI around the point
# (point × [p10, p90] of sold/expected_sold), so it is NO LONGER bounded by the
# asking-IQR band — the high arm (point × 1.392) legitimately exceeds range_high.
diff --git a/tradein-mvp/backend/tests/test_estimator_floor_optional.py b/tradein-mvp/backend/tests/test_estimator_floor_optional.py
index 718cfe79..addcb2d9 100644
--- a/tradein-mvp/backend/tests/test_estimator_floor_optional.py
+++ b/tradein-mvp/backend/tests/test_estimator_floor_optional.py
@@ -4,6 +4,7 @@ Uses MagicMock for DB (consistent with other test_estimator_* tests — no live
The test validates schema construction + that floor=None is accepted without ValueError.
The actual NOT NULL guard is covered by migration 065.
"""
+
from __future__ import annotations
import os
diff --git a/tradein-mvp/backend/tests/test_estimator_headline_sufficiency.py b/tradein-mvp/backend/tests/test_estimator_headline_sufficiency.py
index a4033a0f..d3df789a 100644
--- a/tradein-mvp/backend/tests/test_estimator_headline_sufficiency.py
+++ b/tradein-mvp/backend/tests/test_estimator_headline_sufficiency.py
@@ -298,9 +298,9 @@ def test_repair_coefficient_now_applies_to_thin_sample() -> None:
geo=_geo(),
dadata_qc_geo=None,
)
- assert (
- pr.median_price != pr_no_repair.median_price
- ), "repair coefficient must be applied even for a thin (#oblast-E-flagged) sample"
+ assert pr.median_price != pr_no_repair.median_price, (
+ "repair coefficient must be applied even for a thin (#oblast-E-flagged) sample"
+ )
# ─────────────────────────────────────────────────────────────────────────────
diff --git a/tradein-mvp/backend/tests/test_estimator_null_floor_timeout.py b/tradein-mvp/backend/tests/test_estimator_null_floor_timeout.py
index fecc2c63..95ded602 100644
--- a/tradein-mvp/backend/tests/test_estimator_null_floor_timeout.py
+++ b/tradein-mvp/backend/tests/test_estimator_null_floor_timeout.py
@@ -77,18 +77,14 @@ def test_estimate_null_floor_skips_imv_and_cian() -> None:
async def _run() -> None:
with (
- patch("app.services.estimator.geocode",
- new=AsyncMock(return_value=_make_fake_geo())),
- patch("app.services.estimator.get_house_metadata",
- new=AsyncMock(return_value=None)),
+ patch("app.services.estimator.geocode", new=AsyncMock(return_value=_make_fake_geo())),
+ patch("app.services.estimator.get_house_metadata", new=AsyncMock(return_value=None)),
patch("app.services.estimator._fetch_analogs", return_value=([], False, "W")),
patch("app.services.estimator._fetch_deals", return_value=[]),
patch("app.services.estimator._get_or_fetch_imv_cached", new=imv_mock),
- patch("app.services.estimator._get_or_fetch_yandex_valuation_cached",
- new=yandex_mock),
+ patch("app.services.estimator._get_or_fetch_yandex_valuation_cached", new=yandex_mock),
patch("app.services.estimator.estimate_via_cian_valuation", new=cian_mock),
- patch("app.services.estimator._get_asking_sold_ratio",
- return_value=(None, None)),
+ patch("app.services.estimator._get_asking_sold_ratio", return_value=(None, None)),
):
result = await estimate_quality(payload, db)
@@ -120,18 +116,14 @@ def test_estimate_full_floor_awaits_imv_and_cian() -> None:
async def _run() -> None:
with (
- patch("app.services.estimator.geocode",
- new=AsyncMock(return_value=_make_fake_geo())),
- patch("app.services.estimator.get_house_metadata",
- new=AsyncMock(return_value=None)),
+ patch("app.services.estimator.geocode", new=AsyncMock(return_value=_make_fake_geo())),
+ patch("app.services.estimator.get_house_metadata", new=AsyncMock(return_value=None)),
patch("app.services.estimator._fetch_analogs", return_value=([], False, "W")),
patch("app.services.estimator._fetch_deals", return_value=[]),
patch("app.services.estimator._get_or_fetch_imv_cached", new=imv_mock),
- patch("app.services.estimator._get_or_fetch_yandex_valuation_cached",
- new=yandex_mock),
+ patch("app.services.estimator._get_or_fetch_yandex_valuation_cached", new=yandex_mock),
patch("app.services.estimator.estimate_via_cian_valuation", new=cian_mock),
- patch("app.services.estimator._get_asking_sold_ratio",
- return_value=(None, None)),
+ patch("app.services.estimator._get_asking_sold_ratio", return_value=(None, None)),
):
result = await estimate_quality(payload, db)
@@ -161,20 +153,19 @@ def test_estimate_yandex_timeout_degrades_no_5xx() -> None:
async def _run() -> None:
with (
- patch("app.services.estimator.geocode",
- new=AsyncMock(return_value=_make_fake_geo())),
- patch("app.services.estimator.get_house_metadata",
- new=AsyncMock(return_value=None)),
+ patch("app.services.estimator.geocode", new=AsyncMock(return_value=_make_fake_geo())),
+ patch("app.services.estimator.get_house_metadata", new=AsyncMock(return_value=None)),
patch("app.services.estimator._fetch_analogs", return_value=([], False, "W")),
patch("app.services.estimator._fetch_deals", return_value=[]),
- patch("app.services.estimator._get_or_fetch_imv_cached",
- new=AsyncMock(return_value=None)),
- patch("app.services.estimator._get_or_fetch_yandex_valuation_cached",
- new=yandex_mock),
- patch("app.services.estimator.estimate_via_cian_valuation",
- new=AsyncMock(return_value=None)),
- patch("app.services.estimator._get_asking_sold_ratio",
- return_value=(None, None)),
+ patch(
+ "app.services.estimator._get_or_fetch_imv_cached", new=AsyncMock(return_value=None)
+ ),
+ patch("app.services.estimator._get_or_fetch_yandex_valuation_cached", new=yandex_mock),
+ patch(
+ "app.services.estimator.estimate_via_cian_valuation",
+ new=AsyncMock(return_value=None),
+ ),
+ patch("app.services.estimator._get_asking_sold_ratio", return_value=(None, None)),
):
result = await estimate_quality(payload, db)
@@ -203,26 +194,23 @@ def test_estimate_yandex_httpx_timeout_at_scraper_degrades_no_5xx() -> None:
fake_scraper = MagicMock()
fake_scraper.__aenter__ = AsyncMock(return_value=fake_scraper)
fake_scraper.__aexit__ = AsyncMock(return_value=False)
- fake_scraper.fetch_house_history = AsyncMock(
- side_effect=httpx.TimeoutException("read timeout")
- )
+ fake_scraper.fetch_house_history = AsyncMock(side_effect=httpx.TimeoutException("read timeout"))
async def _run() -> None:
with (
- patch("app.services.estimator.geocode",
- new=AsyncMock(return_value=_make_fake_geo())),
- patch("app.services.estimator.get_house_metadata",
- new=AsyncMock(return_value=None)),
+ patch("app.services.estimator.geocode", new=AsyncMock(return_value=_make_fake_geo())),
+ patch("app.services.estimator.get_house_metadata", new=AsyncMock(return_value=None)),
patch("app.services.estimator._fetch_analogs", return_value=([], False, "W")),
patch("app.services.estimator._fetch_deals", return_value=[]),
- patch("app.services.estimator._get_or_fetch_imv_cached",
- new=AsyncMock(return_value=None)),
- patch("app.services.estimator.YandexValuationScraper",
- return_value=fake_scraper),
- patch("app.services.estimator.estimate_via_cian_valuation",
- new=AsyncMock(return_value=None)),
- patch("app.services.estimator._get_asking_sold_ratio",
- return_value=(None, None)),
+ patch(
+ "app.services.estimator._get_or_fetch_imv_cached", new=AsyncMock(return_value=None)
+ ),
+ patch("app.services.estimator.YandexValuationScraper", return_value=fake_scraper),
+ patch(
+ "app.services.estimator.estimate_via_cian_valuation",
+ new=AsyncMock(return_value=None),
+ ),
+ patch("app.services.estimator._get_asking_sold_ratio", return_value=(None, None)),
):
result = await estimate_quality(payload, db)
diff --git a/tradein-mvp/backend/tests/test_estimator_radius_floor.py b/tradein-mvp/backend/tests/test_estimator_radius_floor.py
index 1551978e..aba07b00 100644
--- a/tradein-mvp/backend/tests/test_estimator_radius_floor.py
+++ b/tradein-mvp/backend/tests/test_estimator_radius_floor.py
@@ -135,9 +135,9 @@ def test_radius_median_below_dkp_floor_is_lifted() -> None:
est = _run_estimate(analogs, dkp_raw, radius_floor_factor=0.8)
floor_ppm2 = 150_000 * 0.8 # 120_000
- assert (
- est.median_price_per_m2 >= floor_ppm2
- ), f"median_ppm2={est.median_price_per_m2} должна быть >= floor={floor_ppm2}"
+ assert est.median_price_per_m2 >= floor_ppm2, (
+ f"median_ppm2={est.median_price_per_m2} должна быть >= floor={floor_ppm2}"
+ )
# ── тест 2: radius median выше floor → no-op ─────────────────────────────────
@@ -164,9 +164,9 @@ def test_radius_median_above_dkp_floor_unchanged() -> None:
floor_ppm2 = 150_000 * 0.8 # 120_000
assert est.median_price_per_m2 > floor_ppm2, "median должна быть выше floor (no-op)"
# Медиана соответствует аналогам (~200k), а не floor
- assert (
- 180_000 <= est.median_price_per_m2 <= 220_000
- ), f"median_ppm2={est.median_price_per_m2} должна остаться в диапазоне аналогов (no-op)"
+ assert 180_000 <= est.median_price_per_m2 <= 220_000, (
+ f"median_ppm2={est.median_price_per_m2} должна остаться в диапазоне аналогов (no-op)"
+ )
# ── тест 3: dkp_raw is None → no-op ─────────────────────────────────────────
@@ -182,6 +182,6 @@ def test_no_dkp_raw_no_floor() -> None:
est = _run_estimate(analogs, dkp_raw=None, radius_floor_factor=0.8)
# median ~80k, без dkp_raw floor не поднимает
- assert (
- est.median_price_per_m2 < 100_000
- ), f"median_ppm2={est.median_price_per_m2} без dkp_raw не должна расти"
+ assert est.median_price_per_m2 < 100_000, (
+ f"median_ppm2={est.median_price_per_m2} без dkp_raw не должна расти"
+ )
diff --git a/tradein-mvp/backend/tests/test_estimator_ratio_tier_fix.py b/tradein-mvp/backend/tests/test_estimator_ratio_tier_fix.py
index 0e9dab86..2a717175 100644
--- a/tradein-mvp/backend/tests/test_estimator_ratio_tier_fix.py
+++ b/tradein-mvp/backend/tests/test_estimator_ratio_tier_fix.py
@@ -150,9 +150,9 @@ def _run_estimate_with_ratio_spy(
def test_ratio_called_exactly_once() -> None:
"""_get_asking_sold_ratio должен вызываться ровно один раз — после headline."""
_est, calls = _run_estimate_with_ratio_spy(_ANALOGS_LOW, (0.80, "per_rooms"))
- assert (
- len(calls) == 1
- ), f"_get_asking_sold_ratio должен вызываться 1 раз, вызван {len(calls)} раз"
+ assert len(calls) == 1, (
+ f"_get_asking_sold_ratio должен вызываться 1 раз, вызван {len(calls)} раз"
+ )
# ── тест 2: anchor поднял headline → ratio вызван с финальным (high) ppm² ────
@@ -298,9 +298,9 @@ def test_ratio_tier_uses_final_headline_after_anchor() -> None:
ppm2_used = captured_anchor_ppm2[0]
assert ppm2_used is not None
# Финальный headline должен быть в зоне anchor (~300k), а НЕ в зоне radius (~105k).
- assert (
- ppm2_used > 200_000
- ), f"ratio должен вызываться с anchor ppm2 (~300k), получено {ppm2_used}"
+ assert ppm2_used > 200_000, (
+ f"ratio должен вызываться с anchor ppm2 (~300k), получено {ppm2_used}"
+ )
# ── тест 3: graceful — нет ratio → expected_sold_* = None, headline не изменён ──
diff --git a/tradein-mvp/backend/tests/test_estimator_repair_coef.py b/tradein-mvp/backend/tests/test_estimator_repair_coef.py
index 83393181..886c08a9 100644
--- a/tradein-mvp/backend/tests/test_estimator_repair_coef.py
+++ b/tradein-mvp/backend/tests/test_estimator_repair_coef.py
@@ -142,9 +142,9 @@ def test_excellent_to_needs_repair_ratio_matches_coef() -> None:
actual_ratio = excellent.median_price_rub / needs_repair.median_price_rub
# int() truncation on both medians ⇒ allow a small tolerance.
- assert (
- abs(actual_ratio - expected_ratio) < 0.005
- ), f"excellent/needs_repair ratio {actual_ratio:.5f} != coef ratio {expected_ratio:.5f}"
+ assert abs(actual_ratio - expected_ratio) < 0.005, (
+ f"excellent/needs_repair ratio {actual_ratio:.5f} != coef ratio {expected_ratio:.5f}"
+ )
def test_standard_is_baseline_noop() -> None:
diff --git a/tradein-mvp/backend/tests/test_estimator_source_quota.py b/tradein-mvp/backend/tests/test_estimator_source_quota.py
index 9b02b85c..a1745b7f 100644
--- a/tradein-mvp/backend/tests/test_estimator_source_quota.py
+++ b/tradein-mvp/backend/tests/test_estimator_source_quota.py
@@ -172,9 +172,9 @@ def test_source_quota_includes_all_when_supply_below_min() -> None:
result, _, _ = _fetch_analogs(db, lat=56.838, lon=60.595, rooms=1, area=38.0, radius_m=1000)
cian_count = sum(1 for r in result if r["source"] == "cian")
- assert (
- cian_count == 3
- ), f"All 3 Cian listings (below MIN quota) must be included, got {cian_count}"
+ assert cian_count == 3, (
+ f"All 3 Cian listings (below MIN quota) must be included, got {cian_count}"
+ )
assert len(result) == 8 # 5 avito + 3 cian
diff --git a/tradein-mvp/backend/tests/test_extract_short_addr.py b/tradein-mvp/backend/tests/test_extract_short_addr.py
index eb8b2b58..198a1810 100644
--- a/tradein-mvp/backend/tests/test_extract_short_addr.py
+++ b/tradein-mvp/backend/tests/test_extract_short_addr.py
@@ -8,15 +8,14 @@ from app.services.estimator import _extract_short_addr
def test_full_admin_chain() -> None:
- assert _extract_short_addr(
- "Свердловская область, г. Екатеринбург, Склад, ул. Заводская, д. 44-а"
- ) == "ул. Заводская, д. 44-а"
+ assert (
+ _extract_short_addr("Свердловская область, г. Екатеринбург, Склад, ул. Заводская, д. 44-а")
+ == "ул. Заводская, д. 44-а"
+ )
def test_russia_prefix() -> None:
- assert _extract_short_addr(
- "Россия, Екатеринбург, ул. Малышева, 1"
- ) == "ул. Малышева, 1"
+ assert _extract_short_addr("Россия, Екатеринбург, ул. Малышева, 1") == "ул. Малышева, 1"
def test_apt_stripped() -> None:
diff --git a/tradein-mvp/backend/tests/test_geo_precision.py b/tradein-mvp/backend/tests/test_geo_precision.py
index f0f2da67..bece66b0 100644
--- a/tradein-mvp/backend/tests/test_geo_precision.py
+++ b/tradein-mvp/backend/tests/test_geo_precision.py
@@ -76,9 +76,9 @@ async def test_geocode_missing_sets_city_precision_for_bare_city_address() -> No
# Inspect the UPDATE call parameters: geo_precision must be 'city'.
update_call = db.execute.call_args_list[1]
update_params = update_call[0][1] # positional arg[1] = params dict
- assert (
- update_params.get("precision") == "city"
- ), f"Expected precision='city' for bare city address, got {update_params.get('precision')!r}"
+ assert update_params.get("precision") == "city", (
+ f"Expected precision='city' for bare city address, got {update_params.get('precision')!r}"
+ )
@pytest.mark.asyncio
@@ -107,9 +107,9 @@ async def test_geocode_missing_sets_none_precision_for_precise_address() -> None
update_call = db.execute.call_args_list[1]
update_params = update_call[0][1]
- assert (
- update_params.get("precision") is None
- ), f"Expected precision=None for precise address, got {update_params.get('precision')!r}"
+ assert update_params.get("precision") is None, (
+ f"Expected precision=None for precise address, got {update_params.get('precision')!r}"
+ )
@pytest.mark.asyncio
@@ -138,9 +138,9 @@ async def test_geocode_missing_sets_city_precision_for_locality_confidence() ->
update_call = db.execute.call_args_list[1]
update_params = update_call[0][1]
- assert (
- update_params.get("precision") == "city"
- ), f"Expected precision='city' for locality confidence, got {update_params.get('precision')!r}"
+ assert update_params.get("precision") == "city", (
+ f"Expected precision='city' for locality confidence, got {update_params.get('precision')!r}"
+ )
# ── (b) estimator radius-analog SQL contains geo_precision exclusion ──────────
diff --git a/tradein-mvp/backend/tests/test_house_imv_retry_stuck.py b/tradein-mvp/backend/tests/test_house_imv_retry_stuck.py
index b414aefc..f6bd4605 100644
--- a/tradein-mvp/backend/tests/test_house_imv_retry_stuck.py
+++ b/tradein-mvp/backend/tests/test_house_imv_retry_stuck.py
@@ -203,9 +203,9 @@ async def test_explicit_only_status_still_takes_exhausted_houses() -> None:
{"ids": list(_IDS)},
)
}
- assert (
- statuses[_H_EXHAUSTED] != "transient_error"
- ), "явно запрошенный статус обрабатывается целиком, включая исчерпавшие лимит"
+ assert statuses[_H_EXHAUSTED] != "transient_error", (
+ "явно запрошенный статус обрабатывается целиком, включая исчерпавшие лимит"
+ )
# Автоповтора поверх явного запроса нет: pending не тронут.
assert statuses[_H_PENDING] == "pending"
assert result.retried == 0
diff --git a/tradein-mvp/backend/tests/test_matching.py b/tradein-mvp/backend/tests/test_matching.py
index e0066101..a1d7e7ef 100644
--- a/tradein-mvp/backend/tests/test_matching.py
+++ b/tradein-mvp/backend/tests/test_matching.py
@@ -439,13 +439,13 @@ def test_match_house_advisory_lock_called_first():
sql_obj = first_call[0][0] # TextClause
bind = first_call[0][1] # dict
- assert "pg_advisory_xact_lock" in str(
- sql_obj
- ), f"first execute must be advisory lock, got: {sql_obj}"
+ assert "pg_advisory_xact_lock" in str(sql_obj), (
+ f"first execute must be advisory lock, got: {sql_obj}"
+ )
assert "fp" in bind, f"lock bind must include fp, got: {bind}"
- assert (
- isinstance(bind["fp"], str) and len(bind["fp"]) == 32
- ), f"fp must be 32-char sha256 hex, got: {bind.get('fp')!r}"
+ assert isinstance(bind["fp"], str) and len(bind["fp"]) == 32, (
+ f"fp must be 32-char sha256 hex, got: {bind.get('fp')!r}"
+ )
# ---------------------------------------------------------------------------
@@ -484,9 +484,9 @@ def test_geo_match_does_not_register_alias():
db, "n1", "ext-geo", address="улица Новая 3", lat=56.83, lon=60.59
)
assert (house_id, conf, method) == (22, 0.7, "geo_proximity")
- assert not any(
- "INSERT INTO house_address_aliases" in s for s in _executed_sqls(db)
- ), "geo match must not write an alias (P3)"
+ assert not any("INSERT INTO house_address_aliases" in s for s in _executed_sqls(db)), (
+ "geo match must not write an alias (P3)"
+ )
def test_geo_match_rejected_when_house_number_differs():
@@ -539,18 +539,18 @@ def test_bare_street_numberless_no_house_created():
)
assert (house_id, conf, method) == (None, 0.0, "no_house_number")
sqls = _executed_sqls(db)
- assert not any(
- "normalized_address = :na" in s for s in sqls
- ), "bare-street address must not run Tier 2b normalized_address lookup (P1)"
- assert not any(
- "INSERT INTO houses" in s for s in sqls
- ), "numberless address must not create a house (P1 extended)"
- assert not any(
- "INSERT INTO house_address_aliases" in s for s in sqls
- ), "numberless address must not register an alias (P1)"
- assert not any(
- "INSERT INTO house_sources" in s for s in sqls
- ), "numberless refusal must not upsert house_sources"
+ assert not any("normalized_address = :na" in s for s in sqls), (
+ "bare-street address must not run Tier 2b normalized_address lookup (P1)"
+ )
+ assert not any("INSERT INTO houses" in s for s in sqls), (
+ "numberless address must not create a house (P1 extended)"
+ )
+ assert not any("INSERT INTO house_address_aliases" in s for s in sqls), (
+ "numberless address must not register an alias (P1)"
+ )
+ assert not any("INSERT INTO house_sources" in s for s in sqls), (
+ "numberless refusal must not upsert house_sources"
+ )
def test_numberless_none_address_with_coords_no_house_created():
@@ -575,9 +575,9 @@ def test_numberless_none_address_with_coords_no_house_created():
db, "yandex", "ext-none-addr", address=None, lat=56.83, lon=60.59
)
assert (house_id, conf, method) == (None, 0.0, "no_house_number")
- assert not any(
- "INSERT INTO houses" in s for s in _executed_sqls(db)
- ), "address-NULL coords-only listing must not create a house (P1 extended)"
+ assert not any("INSERT INTO houses" in s for s in _executed_sqls(db)), (
+ "address-NULL coords-only listing must not create a house (P1 extended)"
+ )
def test_numberless_address_with_cadastral_creates_house():
@@ -611,9 +611,9 @@ def test_numberless_address_with_cadastral_creates_house():
building_cadastral_number="66:41:0000000:12345",
)
assert (house_id, conf, method) == (321, 1.0, "new")
- assert any(
- "INSERT INTO houses" in s for s in _executed_sqls(db)
- ), "numberless + cadastral must still create a house (cadastral = identity)"
+ assert any("INSERT INTO houses" in s for s in _executed_sqls(db)), (
+ "numberless + cadastral must still create a house (cadastral = identity)"
+ )
def test_insert_alias_noop_for_bare_street():
@@ -735,9 +735,9 @@ def test_tier2b_no_coords_no_city_token_skips_and_creates_new():
db, "avito", "ext-2b-bare", address="улица Ленина 100"
)
assert (house_id, conf, method) == (910, 1.0, "new")
- assert not any(
- "normalized_address = :na" in s for s in _executed_sqls(db)
- ), "bare common-street with no coords/city must not run a Tier-2b lookup"
+ assert not any("normalized_address = :na" in s for s in _executed_sqls(db)), (
+ "bare common-street with no coords/city must not run a Tier-2b lookup"
+ )
def test_tier2b_no_coords_with_city_token_matches():
@@ -792,12 +792,12 @@ def test_tier2a_coord_less_non_ekb_city_skips_both_lookups_and_creates_new():
)
assert (house_id, conf, method) == (701, 1.0, "new")
sqls = _executed_sqls(db)
- assert not any(
- "fingerprint = :fp" in s for s in sqls
- ), "coord-less non-ЕКБ card must NOT run the Tier-2a fingerprint lookup"
- assert not any(
- "normalized_address = :na" in s for s in sqls
- ), "coord-less non-ЕКБ card must NOT run the Tier-2b normalized_address lookup"
+ assert not any("fingerprint = :fp" in s for s in sqls), (
+ "coord-less non-ЕКБ card must NOT run the Tier-2a fingerprint lookup"
+ )
+ assert not any("normalized_address = :na" in s for s in sqls), (
+ "coord-less non-ЕКБ card must NOT run the Tier-2b normalized_address lookup"
+ )
def test_tier2a_coord_less_ekb_city_still_matches():
@@ -818,9 +818,9 @@ def test_tier2a_coord_less_ekb_city_still_matches():
db, "avito", "ext-2a-ekb", address="Екатеринбург, ул. Ленина, 5"
)
assert (house_id, conf, method) == (88, 0.9, "fingerprint")
- assert any(
- "fingerprint = :fp" in s for s in _executed_sqls(db)
- ), "ЕКБ coord-less card must still run the Tier-2a fingerprint lookup"
+ assert any("fingerprint = :fp" in s for s in _executed_sqls(db)), (
+ "ЕКБ coord-less card must still run the Tier-2a fingerprint lookup"
+ )
def test_tier2a_coord_less_bare_street_still_runs_tier2a():
@@ -872,9 +872,9 @@ def test_tier2a_bare_card_from_oblast_sweep_skips_alias_lookups():
)
assert (house_id, conf, method) == (2777, 1.0, "new")
sqls = _executed_sqls(db)
- assert not any(
- "fingerprint = :fp" in s for s in sqls
- ), "карточка чужого города прошла Tier-2a по бескоординатному ключу «улица + номер»"
+ assert not any("fingerprint = :fp" in s for s in sqls), (
+ "карточка чужого города прошла Tier-2a по бескоординатному ключу «улица + номер»"
+ )
assert not any("normalized_address = :na" in s for s in sqls)
@@ -1085,13 +1085,13 @@ def test_field_priority_sources_are_lists_or_known_string_rules():
rules alongside list entries. Both are valid per LISTING_FIELD_PRIORITY type hint.
"""
for col, sources in HOUSE_FIELD_PRIORITY.items():
- assert (
- isinstance(sources, list) or sources in VALID_STRING_RULES
- ), f"HOUSE_FIELD_PRIORITY[{col!r}] must be list or known rule, got {sources!r}"
+ assert isinstance(sources, list) or sources in VALID_STRING_RULES, (
+ f"HOUSE_FIELD_PRIORITY[{col!r}] must be list or known rule, got {sources!r}"
+ )
for col, sources in LISTING_FIELD_PRIORITY.items():
- assert (
- isinstance(sources, list) or sources in VALID_STRING_RULES
- ), f"LISTING_FIELD_PRIORITY[{col!r}] must be list or known rule, got {sources!r}"
+ assert isinstance(sources, list) or sources in VALID_STRING_RULES, (
+ f"LISTING_FIELD_PRIORITY[{col!r}] must be list or known rule, got {sources!r}"
+ )
def test_update_canonical_fields_is_callable():
diff --git a/tradein-mvp/backend/tests/test_matching_tier_reachability_2674.py b/tradein-mvp/backend/tests/test_matching_tier_reachability_2674.py
index b16be85b..51107d0c 100644
--- a/tradein-mvp/backend/tests/test_matching_tier_reachability_2674.py
+++ b/tradein-mvp/backend/tests/test_matching_tier_reachability_2674.py
@@ -65,9 +65,9 @@ def test_fias_tier_is_gone_from_create_path_but_alive_in_readonly() -> None:
# Ищем именно литерал method-значения ('"fias_exact"'), а не слово в комментарии:
# комментарий-надгробие про удалённый тир остаться должен, ветка — нет.
- assert '"fias_exact"' not in inspect.getsource(
- match_or_create_house
- ), "мёртвая fias-ветка вернулась в путь создания домов"
+ assert '"fias_exact"' not in inspect.getsource(match_or_create_house), (
+ "мёртвая fias-ветка вернулась в путь создания домов"
+ )
assert '"fias_exact"' in inspect.getsource(match_house_readonly)
@@ -104,9 +104,9 @@ def test_house_key_never_accepts_flat_cadastre() -> None:
assert "building_cadastral_number" in _params(match_or_create_house)
src = inspect.getsource(match_or_create_house)
- assert (
- "cad = building_cadastral_number\n" in src
- ), "в ключ дома вернулся фолбэк на кадастр квартиры"
+ assert "cad = building_cadastral_number\n" in src, (
+ "в ключ дома вернулся фолбэк на кадастр квартиры"
+ )
def test_sweep_city_actually_reaches_the_matcher_from_save_listings() -> None:
@@ -128,6 +128,6 @@ def test_sweep_city_actually_reaches_the_matcher_from_save_listings() -> None:
)
# save_listings считает lot_city (город batch'а после гео-guard'а) и обязан отдать
# именно его, а не сырой city-аргумент: лот вне city_radius_km города НЕ помечен.
- assert "city=lot_city" in inspect.getsource(
- save_listings
- ), "save_listings отдаёт матчеру не lot_city — гео-guard соседнего города обойдён"
+ assert "city=lot_city" in inspect.getsource(save_listings), (
+ "save_listings отдаёт матчеру не lot_city — гео-guard соседнего города обойдён"
+ )
diff --git a/tradein-mvp/backend/tests/test_migration_201_purge_dead_mobileproxy_proxies.py b/tradein-mvp/backend/tests/test_migration_201_purge_dead_mobileproxy_proxies.py
index 72500fe3..34af6701 100644
--- a/tradein-mvp/backend/tests/test_migration_201_purge_dead_mobileproxy_proxies.py
+++ b/tradein-mvp/backend/tests/test_migration_201_purge_dead_mobileproxy_proxies.py
@@ -51,9 +51,9 @@ def test_migration_201_deletes_by_domain_not_id() -> None:
flat = _flat(_executable_sql())
assert "delete from scrape_proxies" in flat
assert "where url like '%mobileproxy.space%'" in flat
- assert (
- re.search(r"where\s+id\s*(=|in)", flat) is None
- ), "миграция не должна фильтровать по id — id разъезжается между средами"
+ assert re.search(r"where\s+id\s*(=|in)", flat) is None, (
+ "миграция не должна фильтровать по id — id разъезжается между средами"
+ )
def test_migration_201_is_idempotent_by_construction() -> None:
diff --git a/tradein-mvp/backend/tests/test_migration_262_oblast_city_sweeps_wave2.py b/tradein-mvp/backend/tests/test_migration_262_oblast_city_sweeps_wave2.py
index 75da91a6..492fdb1c 100644
--- a/tradein-mvp/backend/tests/test_migration_262_oblast_city_sweeps_wave2.py
+++ b/tradein-mvp/backend/tests/test_migration_262_oblast_city_sweeps_wave2.py
@@ -300,9 +300,9 @@ def test_mikhaylovsk_has_only_cian_row() -> None:
РОВНО одну строку (cian), НЕ три."""
rows = _row_sources(_MIGRATION_262)
mikhaylovsk_providers = {p for p, s in rows if s == "mikhaylovsk"}
- assert mikhaylovsk_providers == {
- "cian"
- }, f"mikhaylovsk должен иметь только cian-строку, получено: {mikhaylovsk_providers}"
+ assert mikhaylovsk_providers == {"cian"}, (
+ f"mikhaylovsk должен иметь только cian-строку, получено: {mikhaylovsk_providers}"
+ )
# ── CITY_ANCHORS parity ─────────────────────────────────────────────────────
@@ -332,9 +332,9 @@ def test_city_anchors_has_no_slug_without_schedule_rows() -> None:
slug for _p, slug in _row_sources(_MIGRATION_262)
}
orphaned = set(CITY_ANCHORS) - seeded_slugs
- assert (
- not orphaned
- ), f"CITY_ANCHORS содержит slug без scrape_schedules-строк: {sorted(orphaned)}"
+ assert not orphaned, (
+ f"CITY_ANCHORS содержит slug без scrape_schedules-строк: {sorted(orphaned)}"
+ )
def test_city_anchors_wave2_count_and_content() -> None:
@@ -398,9 +398,9 @@ def test_migration_262_windows_are_one_hour() -> None:
полночь, см. scraper_kit.orchestration.scheduler)."""
for provider, slug, start, end in _row_windows(_MIGRATION_262):
expected = (start + 1) % 24
- assert (
- end == expected
- ), f"{provider}_{slug}: window [{start},{end}) не 1-часовое (ожидали end={expected})"
+ assert end == expected, (
+ f"{provider}_{slug}: window [{start},{end}) не 1-часовое (ожидали end={expected})"
+ )
def test_migration_262_window_hours_satisfy_db_check_constraint() -> None:
diff --git a/tradein-mvp/backend/tests/test_password.py b/tradein-mvp/backend/tests/test_password.py
index a03e5c9f..8d1dd9e1 100644
--- a/tradein-mvp/backend/tests/test_password.py
+++ b/tradein-mvp/backend/tests/test_password.py
@@ -285,9 +285,9 @@ async def test_one_key_cannot_take_more_than_its_share(monkeypatch: pytest.Monke
async def _wait_inflight(n: int) -> None:
deadline = time.monotonic() + 5
while password_mod._verify_inflight < n:
- assert (
- time.monotonic() < deadline
- ), f"слотов занято {password_mod._verify_inflight} < {n}"
+ assert time.monotonic() < deadline, (
+ f"слотов занято {password_mod._verify_inflight} < {n}"
+ )
await asyncio.sleep(0.005)
flood = [
diff --git a/tradein-mvp/backend/tests/test_pdf_security.py b/tradein-mvp/backend/tests/test_pdf_security.py
index 7d6d21ed..7789b8e0 100644
--- a/tradein-mvp/backend/tests/test_pdf_security.py
+++ b/tradein-mvp/backend/tests/test_pdf_security.py
@@ -522,9 +522,9 @@ def test_brand_not_taken_from_query_param_docstring() -> None:
sig = inspect.signature(estimate_pdf)
param_names = list(sig.parameters.keys())
- assert (
- "brand" not in param_names
- ), "estimate_pdf should NOT have a 'brand' query param after #7 fix"
+ assert "brand" not in param_names, (
+ "estimate_pdf should NOT have a 'brand' query param after #7 fix"
+ )
# ── PR-D1: retain_until (paid retention) — cover row + valid_until unaffected ──
diff --git a/tradein-mvp/backend/tests/test_purge_expired_trade_in_data.py b/tradein-mvp/backend/tests/test_purge_expired_trade_in_data.py
index a49f1a96..f539f67d 100644
--- a/tradein-mvp/backend/tests/test_purge_expired_trade_in_data.py
+++ b/tradein-mvp/backend/tests/test_purge_expired_trade_in_data.py
@@ -435,7 +435,7 @@ def test_real_purge_deletes_only_anonymous_expired_estimates() -> None:
remaining_ids = {
str(r)
for r in db.execute(
- _t("SELECT id FROM trade_in_estimates " "WHERE id = ANY(CAST(:ids AS uuid[]))"),
+ _t("SELECT id FROM trade_in_estimates WHERE id = ANY(CAST(:ids AS uuid[]))"),
{"ids": [str(anon_id), str(pilot_id)]},
)
.scalars()
@@ -497,9 +497,9 @@ def test_real_preflight_ignores_healthy_paid_row_flags_only_anomaly() -> None:
{"order_id": healthy_order, "id": str(healthy_id)},
)
db.commit()
- assert (
- task_mod._preflight_paid_candidates(db) == baseline
- ), "healthy paid row (retain_until set) must NOT raise the pre-flight count"
+ assert task_mod._preflight_paid_candidates(db) == baseline, (
+ "healthy paid row (retain_until set) must NOT raise the pre-flight count"
+ )
# Anomaly: retain_until NULL despite a payments row existing -- exactly
# the case the two DELETE safeguards exist for. Must raise by exactly one.
diff --git a/tradein-mvp/backend/tests/test_ratelimit.py b/tradein-mvp/backend/tests/test_ratelimit.py
index b9b71c1a..fdb75f74 100644
--- a/tradein-mvp/backend/tests/test_ratelimit.py
+++ b/tradein-mvp/backend/tests/test_ratelimit.py
@@ -183,9 +183,9 @@ def test_notify_path_bypasses_general_limiter_400_requests_zero_429(notify_clien
statuses = [
notify_client.post("/api/v1/trade-in/payments/notify").status_code for _ in range(400)
]
- assert all(
- code == 200 for code in statuses
- ), f"notify получил 429 хотя бы раз: {[c for c in statuses if c != 200]}"
+ assert all(code == 200 for code in statuses), (
+ f"notify получил 429 хотя бы раз: {[c for c in statuses if c != 200]}"
+ )
def test_general_limiter_still_active_for_other_paths(notify_client):
diff --git a/tradein-mvp/backend/tests/test_sber_index.py b/tradein-mvp/backend/tests/test_sber_index.py
index 61b7f1c7..ae70ad4f 100644
--- a/tradein-mvp/backend/tests/test_sber_index.py
+++ b/tradein-mvp/backend/tests/test_sber_index.py
@@ -203,8 +203,7 @@ def test_active_dashboards_match_captured_secondary_series() -> None:
d = _DASH[slug]
encoded = build_sber_route(d.slug, "643", d.extra_filter)
assert encoded in captured_b64, (
- f"active dashboard {slug!r} route not in captured-live set — "
- f"filter={d.extra_filter}"
+ f"active dashboard {slug!r} route not in captured-live set — filter={d.extra_filter}"
)
@@ -345,15 +344,15 @@ def test_decode_sber_response_real_fixture() -> None:
# Verify region label decoded correctly
regions = {r["ref_area"] for r in rows}
- assert (
- "Свердловская область" in regions
- ), f"Expected 'Свердловская область' in ref_area values, got: {regions}"
+ assert "Свердловская область" in regions, (
+ f"Expected 'Свердловская область' in ref_area values, got: {regions}"
+ )
# Verify secondary-market segment present
realty_vals = {r["realty"] for r in rows}
- assert any(
- "тори" in str(v) or "Вторичн" in str(v) for v in realty_vals
- ), f"Expected secondary-market label in realty field, got: {realty_vals}"
+ assert any("тори" in str(v) or "Вторичн" in str(v) for v in realty_vals), (
+ f"Expected secondary-market label in realty field, got: {realty_vals}"
+ )
# ---------------------------------------------------------------------------
@@ -434,9 +433,9 @@ async def test_pull_sber_indices_upsert_on_conflict_idempotent() -> None:
# Must NOT contain :: type casts (psycopg v3 rule)
import re
- assert not re.search(
- r":[a-z_]+::[a-z]", upsert_sql
- ), "SQL must not contain ::type casts — use CAST(... AS type) instead"
+ assert not re.search(r":[a-z_]+::[a-z]", upsert_sql), (
+ "SQL must not contain ::type casts — use CAST(... AS type) instead"
+ )
@pytest.mark.asyncio
@@ -532,9 +531,9 @@ async def test_pull_sber_indices_asking_benchmark_logged(caplog: pytest.LogCaptu
dashboards=[_DASH["dinamika-tsen-obyavlenii"]],
)
- assert any(
- "benchmark" in record.message for record in caplog.records
- ), "Expected a benchmark log line after upserting dinamika-tsen-obyavlenii"
- assert any(
- "115000" in record.message for record in caplog.records
- ), "Benchmark log should include the latest asking index value"
+ assert any("benchmark" in record.message for record in caplog.records), (
+ "Expected a benchmark log line after upserting dinamika-tsen-obyavlenii"
+ )
+ assert any("115000" in record.message for record in caplog.records), (
+ "Benchmark log should include the latest asking index value"
+ )
diff --git a/tradein-mvp/backend/tests/test_scraper_admin_apis.py b/tradein-mvp/backend/tests/test_scraper_admin_apis.py
index 813d78a2..ea7f0b5d 100644
--- a/tradein-mvp/backend/tests/test_scraper_admin_apis.py
+++ b/tradein-mvp/backend/tests/test_scraper_admin_apis.py
@@ -477,9 +477,9 @@ def test_data_quality_pct_in_range(client: TestClient) -> None:
for src in body["sources"]:
for field_name, pct in src["fields"].items():
- assert (
- 0.0 <= pct <= 100.0
- ), f"source={src['source']} field={field_name} pct={pct} вне [0,100]"
+ assert 0.0 <= pct <= 100.0, (
+ f"source={src['source']} field={field_name} pct={pct} вне [0,100]"
+ )
# ── Security: текст исключения не утекает в HTTP-ответ (#2234) ────────────────
diff --git a/tradein-mvp/backend/tests/test_scraper_kit_scheduler_parity.py b/tradein-mvp/backend/tests/test_scraper_kit_scheduler_parity.py
index b973e19a..781dd7c9 100644
--- a/tradein-mvp/backend/tests/test_scraper_kit_scheduler_parity.py
+++ b/tradein-mvp/backend/tests/test_scraper_kit_scheduler_parity.py
@@ -164,9 +164,9 @@ def test_real_build_product_handlers_covers_all_scheduled_sources() -> None:
real_registry = build_registry(build_product_handlers(ctx=None)) # type: ignore[arg-type]
for source in _PRODUCT_SOURCES | _KIT_NATIVE_SOURCES:
- assert (
- resolve_handler(source, real_registry) is not None
- ), f"real build_product_handlers()/build_registry() misses source={source}"
+ assert resolve_handler(source, real_registry) is not None, (
+ f"real build_product_handlers()/build_registry() misses source={source}"
+ )
def test_kit_native_handler_set() -> None:
diff --git a/tradein-mvp/backend/tests/test_segment_guard_1186.py b/tradein-mvp/backend/tests/test_segment_guard_1186.py
index 289ff36d..78f7a465 100644
--- a/tradein-mvp/backend/tests/test_segment_guard_1186.py
+++ b/tradein-mvp/backend/tests/test_segment_guard_1186.py
@@ -40,9 +40,9 @@ def _norm(s: str) -> str:
def test_common_where_has_canonical_guard() -> None:
"""_COMMON_WHERE используется Tier S и Tier H — должен содержать канон-предикат."""
- assert _GUARD_RE.search(
- est_mod._COMMON_WHERE
- ), "_COMMON_WHERE lacks novostroyki guard — Tier S/H comp set contaminated"
+ assert _GUARD_RE.search(est_mod._COMMON_WHERE), (
+ "_COMMON_WHERE lacks novostroyki guard — Tier S/H comp set contaminated"
+ )
def test_common_where_no_old_neq_form() -> None:
@@ -95,18 +95,18 @@ _REDERIVE_SQL_TEXT = str(ratio_mod._REDERIVE_SQL.text)
def test_ask_side_cte_has_guard() -> None:
"""ask_side CTE в _REDERIVE_SQL (per-rooms asking медиана) — guard обязателен."""
- assert _GUARD_RE.search(
- _REDERIVE_SQL_TEXT
- ), "ask_side CTE in _REDERIVE_SQL lacks novostroyki guard"
+ assert _GUARD_RE.search(_REDERIVE_SQL_TEXT), (
+ "ask_side CTE in _REDERIVE_SQL lacks novostroyki guard"
+ )
def test_ask_global_cte_has_guard() -> None:
"""ask_global CTE в _REDERIVE_SQL (global fallback asking медиана) — guard обязателен."""
# _REDERIVE_SQL содержит два `ask_global`-блока; ищем оба через count.
matches = len(_GUARD_RE.findall(_REDERIVE_SQL_TEXT))
- assert (
- matches >= 2
- ), f"Expected ≥2 guard occurrences in _REDERIVE_SQL (ask_side + ask_global), got {matches}"
+ assert matches >= 2, (
+ f"Expected ≥2 guard occurrences in _REDERIVE_SQL (ask_side + ask_global), got {matches}"
+ )
# ── Поведенческие тесты: _fetch_analogs (mock DB) ────────────────────────────
@@ -164,9 +164,9 @@ def test_fetch_analogs_sql_guard_present_novostroyki_excluded() -> None:
"""
src = inspect.getsource(est_mod._fetch_analogs)
# Guard должен присутствовать хотя бы один раз в теле функции.
- assert _GUARD_RE.search(
- src
- ), "_fetch_analogs SQL no longer contains novostroyki guard — guard was removed!"
+ assert _GUARD_RE.search(src), (
+ "_fetch_analogs SQL no longer contains novostroyki guard — guard was removed!"
+ )
def test_null_segment_listing_not_excluded_by_guard() -> None:
@@ -185,9 +185,9 @@ def test_null_segment_listing_not_excluded_by_guard() -> None:
)
# NULL-segment listing должен присутствовать в результате (не отброшен Python-стороной).
- assert any(
- r.get("source") == "avito" for r in result
- ), "NULL-segment listing was unexpectedly excluded from comp set"
+ assert any(r.get("source") == "avito" for r in result), (
+ "NULL-segment listing was unexpectedly excluded from comp set"
+ )
def test_vtorichka_segment_listing_included() -> None:
@@ -234,12 +234,12 @@ def test_no_neq_novostroyki_form_in_estimator() -> None:
Канон: IS NULL OR = 'vtorichka'.
"""
src = _estimator_full_src()
- assert (
- "<> 'novostroyki'" not in src
- ), "estimator.py contains deprecated `<> 'novostroyki'` form — use canonical guard"
- assert (
- "!= 'novostroyki'" not in src
- ), "estimator.py contains deprecated `!= 'novostroyki'` form — use canonical guard"
+ assert "<> 'novostroyki'" not in src, (
+ "estimator.py contains deprecated `<> 'novostroyki'` form — use canonical guard"
+ )
+ assert "!= 'novostroyki'" not in src, (
+ "estimator.py contains deprecated `!= 'novostroyki'` form — use canonical guard"
+ )
def test_anchor_comps_no_dead_listing_segment_param() -> None:
@@ -269,6 +269,6 @@ def test_fetch_anchor_comps_tier_c_canonical_guard_not_parametric() -> None:
"must be hardcoded canonical guard"
)
# И канон-guard на месте (Tier C-блок).
- assert _GUARD_RE.search(
- src
- ), "_fetch_anchor_comps lacks canonical guard after removing parametric form"
+ assert _GUARD_RE.search(src), (
+ "_fetch_anchor_comps lacks canonical guard after removing parametric form"
+ )
diff --git a/tradein-mvp/backend/tests/test_snapshot_writer.py b/tradein-mvp/backend/tests/test_snapshot_writer.py
index a779c98d..6cde5c51 100644
--- a/tradein-mvp/backend/tests/test_snapshot_writer.py
+++ b/tradein-mvp/backend/tests/test_snapshot_writer.py
@@ -303,9 +303,9 @@ def test_save_detail_enrichment_oph_on_conflict_constraint():
sqls = _get_all_sqls(db)
oph_sqls = [s for s in sqls if "offer_price_history" in s]
assert oph_sqls, "INSERT offer_price_history не найден"
- assert (
- "offer_price_history_listing_change_uq" in oph_sqls[0]
- ), "ON CONFLICT должен ссылаться на UNIQUE constraint"
+ assert "offer_price_history_listing_change_uq" in oph_sqls[0], (
+ "ON CONFLICT должен ссылаться на UNIQUE constraint"
+ )
def test_save_detail_enrichment_skips_price_change_without_change_time():
diff --git a/tradein-mvp/backend/tests/test_street_deals_endpoint.py b/tradein-mvp/backend/tests/test_street_deals_endpoint.py
index 003883e1..570711db 100644
--- a/tradein-mvp/backend/tests/test_street_deals_endpoint.py
+++ b/tradein-mvp/backend/tests/test_street_deals_endpoint.py
@@ -317,15 +317,15 @@ def test_street_regex_word_boundary_no_false_positive() -> None:
# Must NOT match — 'мира' appears inside 'Макарова' substring check skipped
# but more critically — 'мира' appears inside 'Адмирала Макарова'
false_positive_addr = "улица Адмирала Макарова"
- assert not re.search(
- pattern, false_positive_addr, re.IGNORECASE
- ), f"Pattern {pattern!r} should NOT match {false_positive_addr!r}"
+ assert not re.search(pattern, false_positive_addr, re.IGNORECASE), (
+ f"Pattern {pattern!r} should NOT match {false_positive_addr!r}"
+ )
# Must match — exact word
true_positive_addr = "улица Мира 5"
- assert re.search(
- pattern, true_positive_addr, re.IGNORECASE
- ), f"Pattern {pattern!r} should match {true_positive_addr!r}"
+ assert re.search(pattern, true_positive_addr, re.IGNORECASE), (
+ f"Pattern {pattern!r} should match {true_positive_addr!r}"
+ )
def test_street_regex_param_passed_to_db(trade_in_app: FastAPI) -> None:
@@ -355,6 +355,6 @@ def test_street_regex_param_passed_to_db(trade_in_app: FastAPI) -> None:
assert "street_regex" in params, f"street_regex not in params: {params}"
regex_val = params["street_regex"]
# Must contain word-boundary anchors
- assert (
- r"\m" in regex_val or r"\b" in regex_val or regex_val.startswith(r"\m")
- ), f"Expected word-boundary in regex, got: {regex_val!r}"
+ assert r"\m" in regex_val or r"\b" in regex_val or regex_val.startswith(r"\m"), (
+ f"Expected word-boundary in regex, got: {regex_val!r}"
+ )
diff --git a/tradein-mvp/backend/tests/test_yandex_city_sweep.py b/tradein-mvp/backend/tests/test_yandex_city_sweep.py
index b50729a9..fb3f895a 100644
--- a/tradein-mvp/backend/tests/test_yandex_city_sweep.py
+++ b/tradein-mvp/backend/tests/test_yandex_city_sweep.py
@@ -219,6 +219,6 @@ def test_combos_sweep_timeout_substantially_larger_than_anchor_timeout() -> None
f"combos-mode will still timeout mid-sweep"
)
# И намного > ANCHOR_TIMEOUT_SEC (240s)
- assert (
- sweep_timeout > ANCHOR_TIMEOUT_SEC * 4
- ), f"sweep_timeout={sweep_timeout:.0f}s should be >> ANCHOR_TIMEOUT_SEC={ANCHOR_TIMEOUT_SEC}s"
+ assert sweep_timeout > ANCHOR_TIMEOUT_SEC * 4, (
+ f"sweep_timeout={sweep_timeout:.0f}s should be >> ANCHOR_TIMEOUT_SEC={ANCHOR_TIMEOUT_SEC}s"
+ )
diff --git a/tradein-mvp/backend/tests/test_yandex_valuation.py b/tradein-mvp/backend/tests/test_yandex_valuation.py
index c6196e84..638c5042 100644
--- a/tradein-mvp/backend/tests/test_yandex_valuation.py
+++ b/tradein-mvp/backend/tests/test_yandex_valuation.py
@@ -416,9 +416,9 @@ def test_area_regex_rejects_year_concat():
item = YandexValuationScraper._parse_item_text(text)
# The "202452,2" token has digits jammed before it (no separator), so the
# tightened regex must NOT match it.
- assert (
- item is None or item.area_m2 is None
- ), f"expected no area match for concat token, got {item.area_m2 if item else 'None item'}"
+ assert item is None or item.area_m2 is None, (
+ f"expected no area match for concat token, got {item.area_m2 if item else 'None item'}"
+ )
def test_area_regex_accepts_isolated_token():
@@ -474,9 +474,9 @@ def test_area_regex_two_komn_chunk():
text = "8 000 000 ₽ за м²2-комнатная 52,2 м² 5 этаж 10.05.2024 8 000 000 ₽ В продаже"
item = YandexValuationScraper._parse_item_text(text)
assert item is not None
- assert (
- item.area_m2 == 52.2
- ), f"2-комн area must parse despite preceding tokens, got {item.area_m2}"
+ assert item.area_m2 == 52.2, (
+ f"2-комн area must parse despite preceding tokens, got {item.area_m2}"
+ )
def test_area_regex_still_blocks_year_concat():