diff --git a/tradein-mvp/backend/app/schemas/trade_in.py b/tradein-mvp/backend/app/schemas/trade_in.py index ea558765..2af77c3f 100644 --- a/tradein-mvp/backend/app/schemas/trade_in.py +++ b/tradein-mvp/backend/app/schemas/trade_in.py @@ -27,6 +27,11 @@ class TradeInEstimateInput(BaseModel): # geocode() (который падает на DaData-формах при мёртвом Yandex-ключе). lat: float | None = Field(default=None, ge=-90, le=90) lon: float | None = Field(default=None, ge=-180, le=180) + # #2044: опциональный радиус анализа (контрол РАДИУС на /trade-in/v2), метры. + # None → текущее дефолтное поведение (1000 м поиск аналогов, 2000 м fallback). + # Задан → и первичный, и fallback-поиск аналогов/сделок используют ровно этот + # радиус (без авто-расширения). Диапазон 100–5000 м — sane guard. + radius_m: int | None = Field(default=None, ge=100, le=5000) # CRM-поля (#395) — операционные, на расчёт оценки не влияют ownership_type: str | None = Field(default=None, max_length=100) has_mortgage: bool | None = None diff --git a/tradein-mvp/backend/app/services/estimator.py b/tradein-mvp/backend/app/services/estimator.py index fb039db5..0e4d60e0 100644 --- a/tradein-mvp/backend/app/services/estimator.py +++ b/tradein-mvp/backend/app/services/estimator.py @@ -2592,6 +2592,13 @@ async def estimate_quality( # a) 1km + ±15% area (без cohort — drop fallback) # b) 2km + ±15% area (fallback_used = True) # c) 2km + ±25% area (fallback_used = True, area_widened = True) + # + # #2044: опциональный override радиуса анализа (контрол РАДИУС). None → + # точное текущее поведение (DEFAULT 1000 м первичный, FALLBACK 2000 м + # расширение). Задан → и первичный, и fallback-поиск используют ровно этот + # радиус (он же — максимум, без авто-расширения за пределы выбранного). + base_radius_m = payload.radius_m or DEFAULT_RADIUS_M + fallback_radius_m = payload.radius_m or FALLBACK_RADIUS_M cohort_range = _target_cohort_range(target_year) if cohort_range is not None: @@ -2602,7 +2609,7 @@ async def estimate_quality( lon=geo.lon, rooms=payload.rooms, area=payload.area_m2, - radius_m=DEFAULT_RADIUS_M, + radius_m=base_radius_m, full_address=geo.full_address, target_house_id=target_house_id, year_built=target_year, @@ -2626,7 +2633,7 @@ async def estimate_quality( lon=geo.lon, rooms=payload.rooms, area=payload.area_m2, - radius_m=DEFAULT_RADIUS_M, + radius_m=base_radius_m, full_address=geo.full_address, target_house_id=target_house_id, year_built=target_year, @@ -2642,7 +2649,7 @@ async def estimate_quality( lon=geo.lon, rooms=payload.rooms, area=payload.area_m2, - radius_m=FALLBACK_RADIUS_M, + radius_m=fallback_radius_m, full_address=geo.full_address, target_house_id=target_house_id, year_built=target_year, @@ -2663,7 +2670,7 @@ async def estimate_quality( lon=geo.lon, rooms=payload.rooms, area=payload.area_m2, - radius_m=FALLBACK_RADIUS_M, + radius_m=fallback_radius_m, area_tolerance=0.25, full_address=geo.full_address, target_house_id=target_house_id, @@ -2877,7 +2884,7 @@ async def estimate_quality( lon=geo.lon, rooms=payload.rooms, area=payload.area_m2, - radius_m=DEFAULT_RADIUS_M, + radius_m=base_radius_m, ) # 6. Сохраняем в trade_in_estimates diff --git a/tradein-mvp/frontend/src/components/trade-in/v2/HeroBar.tsx b/tradein-mvp/frontend/src/components/trade-in/v2/HeroBar.tsx index 55090e3a..6864cd46 100644 --- a/tradein-mvp/frontend/src/components/trade-in/v2/HeroBar.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/v2/HeroBar.tsx @@ -1,7 +1,6 @@ "use client"; import { useState, type CSSProperties } from "react"; -import Image from "next/image"; import { API_BASE_URL } from "@/lib/api"; import { safeUrl } from "@/lib/safeUrl"; @@ -13,6 +12,11 @@ import type { HeroBarData } from "./mappers"; // Default presentation data (unwired usage): the existing design fixtures. const HERO_FIXTURE: HeroBarData = { report, object }; +// next/image does NOT prepend the configured basePath ("/trade-in") to a +// literal src, so an 404s behind Caddy. A plain +// with the basePath baked in resolves to /trade-in/trade-in-v2/… → 200. +const BP = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; + // estimate ids are server-issued UUIDs — reject anything else before it lands // in the PDF request path so a tampered id cannot be injected. const PDF_UUID_RE = @@ -244,14 +248,15 @@ export default function HeroBar({ }} > {!imgFailed && ( - setImgFailed(true)} style={{ + position: "absolute", + inset: 0, + width: "100%", + height: "100%", objectFit: "contain", objectPosition: "right center", filter: "saturate(.9) brightness(1.04)", diff --git a/tradein-mvp/frontend/src/components/trade-in/v2/ParamsPanel.tsx b/tradein-mvp/frontend/src/components/trade-in/v2/ParamsPanel.tsx index 40e0a779..db330cb1 100644 --- a/tradein-mvp/frontend/src/components/trade-in/v2/ParamsPanel.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/v2/ParamsPanel.tsx @@ -7,8 +7,9 @@ // styles are UNCHANGED — only the data plumbing differs (display
s became // s styled identically, dropdowns now feed real enum values). RU dropdown // labels <-> API enum values go through HOUSE_TYPE_*/REPAIR_* maps in ./mappers. -// The РАДИУС and CRM dropdowns have no backend yet → kept visually but disabled -// (see TODOs). Hover/active + @keyframes live in a pp-prefixed local