gendesign/tradein-mvp/frontend/src/app/ui-preview/estimate/fixture.ts
lekss361 96491352df feat(tradein): dev/CI preview route for a11y+lighthouse audits (#801)
UI-аудит инструментами (axe/lighthouse) требует отрендеренную контентную
страницу. Live trade-in за auth-gate, локальный фронт без бэкенда = белый экран
(RouteGuard висит на pending /me). Добавлен оффлайн preview «денежного экрана».

- src/app/ui-preview/estimate/{page,fixture}.tsx — рендерит реальные компоненты
  результата (HeroSummary + Cian + Distribution + Deals + StreetDeals + Exposure
  + PriceTrend) на статичной фикстуре AggregatedEstimate, без сети: свой
  QueryClient с засеянным фейковым /me (useMe/useBrand резолвятся оффлайн).
- RouteGuard: bypass /ui-preview/* под флагом (RBAC к preview не применяется).
- Гейт: NEXT_PUBLIC_ENABLE_PREVIEW=1 (иначе notFound) — в проде по умолчанию off.
  Папка ui-preview (НЕ _-prefix: Next трактует _* как private/non-routable).

Проверено локально (NEXT_PUBLIC_ENABLE_PREVIEW=1): страница рендерится оффлайн,
axe (wcag2a/aa) дал baseline — 2 serious: color-contrast (teal #14b8a6 на светлом
2.32<4.5) + svg-img-alt (recharts scatter без accessible text). tsc clean.

Refs #801. Followup: фиксы найденных axe-дефектов отдельными issue.
2026-05-30 20:35:22 +03:00

189 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// #801: статичная фикстура результата оценки для preview-маршрута (a11y/lighthouse).
// Полностью оффлайн — никакой сети. Значения правдоподобны (ЕКБ, 2-к вторичка),
// заполнены ВСЕ surface'ы «денежного экрана»: asking + expected-sold + Avito IMV +
// ДКП-коридор + cian valuation + price_trend + аналоги/сделки.
// photo_url=null у всех лотов — намеренно, чтобы не тянуть внешние картинки
// (чистый оффлайн-рендер для lighthouse/axe).
import type { AggregatedEstimate, AnalogLot, TradeInEstimateInput } from "@/types/trade-in";
const analogs: AnalogLot[] = [
{
address: "ул. Репина, 73",
area_m2: 54,
rooms: 2,
floor: 7,
total_floors: 16,
price_rub: 9_700_000,
price_per_m2: 179_629,
listing_date: "2026-05-12",
days_on_market: 18,
photo_url: null,
source: "avito",
source_url: "https://www.avito.ru/ekaterinburg/kvartiry/preview-1",
distance_m: 120,
tier: null,
lat: 56.8401,
lon: 60.5702,
},
{
address: "ул. Викулова, 33",
area_m2: 57,
rooms: 2,
floor: 4,
total_floors: 10,
price_rub: 9_950_000,
price_per_m2: 174_561,
listing_date: "2026-05-20",
days_on_market: 10,
photo_url: null,
source: "cian",
source_url: "https://www.cian.ru/sale/flat/preview-2",
distance_m: 340,
tier: null,
lat: 56.8389,
lon: 60.5681,
},
{
address: "ул. Кирова, 28",
area_m2: 53,
rooms: 2,
floor: 9,
total_floors: 12,
price_rub: 9_400_000,
price_per_m2: 177_358,
listing_date: "2026-05-04",
days_on_market: 31,
photo_url: null,
source: "avito",
source_url: "https://www.avito.ru/ekaterinburg/kvartiry/preview-3",
distance_m: 510,
tier: null,
lat: 56.8372,
lon: 60.5749,
},
];
const actualDeals: AnalogLot[] = [
{
address: "ул. Репина",
area_m2: 55,
rooms: 2,
floor: null,
total_floors: null,
price_rub: 8_800_000,
price_per_m2: 160_000,
listing_date: "2026-01-01",
days_on_market: null,
photo_url: null,
source: "rosreestr",
source_url: null,
distance_m: null,
tier: "T1_per_street",
lat: null,
lon: null,
},
{
address: "ул. Викулова",
area_m2: 58,
rooms: 2,
floor: null,
total_floors: null,
price_rub: 9_280_000,
price_per_m2: 160_000,
listing_date: "2025-10-01",
days_on_market: null,
photo_url: null,
source: "rosreestr",
source_url: null,
distance_m: null,
tier: "T1_per_street",
lat: null,
lon: null,
},
];
export const FIXTURE_ESTIMATE: AggregatedEstimate = {
estimate_id: "preview-0000-0000-0000-000000000000",
median_price_rub: 9_850_000,
range_low_rub: 9_100_000,
range_high_rub: 10_600_000,
median_price_per_m2: 178_000,
expected_sold_price_rub: 8_900_000,
expected_sold_range_low_rub: 8_300_000,
expected_sold_range_high_rub: 9_500_000,
expected_sold_per_m2: 160_800,
asking_to_sold_ratio: 0.9,
ratio_basis: "per_rooms",
confidence: "high",
confidence_explanation: "12 аналогов в радиусе 800 м, разброс цен ±9%.",
n_analogs: 12,
insufficient_data: false,
period_months: 24,
analogs,
actual_deals: actualDeals,
expires_at: "2026-06-30T00:00:00Z",
target_address: "Екатеринбург, ул. Репина, 75/2",
target_lat: 56.84,
target_lon: 60.57,
sources_used: ["avito", "cian", "rosreestr"],
data_freshness_minutes: 35,
last_scraped_at: "2026-05-30T12:00:00Z",
est_days_on_market: 48,
address_precision: "house",
area_m2: 55.3,
rooms: 2,
floor: 5,
total_floors: 11,
year_built: 2018,
house_type: "monolith",
repair_state: "good",
has_balcony: true,
cian_valuation: {
sale_price_rub: 9_700_000,
rent_price_rub: 42_000,
chart: [
{ date: "2025-11", price: 9_300_000 },
{ date: "2025-12", price: 9_420_000 },
{ date: "2026-01", price: 9_510_000 },
{ date: "2026-02", price: 9_560_000 },
{ date: "2026-03", price: 9_640_000 },
{ date: "2026-04", price: 9_700_000 },
],
chart_change_pct: 3.2,
chart_change_direction: "increase",
},
avito_imv: {
recommended_price: 10_200_000,
lower_price: 9_400_000,
higher_price: 11_000_000,
market_count: 7,
},
dkp_corridor: {
count: 9,
low_ppm2: 150_000,
median_ppm2: 162_000,
high_ppm2: 178_000,
period_months: 24,
},
price_trend: [
{ month: "2025-11", ppm2: 168_000 },
{ month: "2025-12", ppm2: 170_500 },
{ month: "2026-01", ppm2: 172_000 },
{ month: "2026-02", ppm2: 174_000 },
{ month: "2026-03", ppm2: 176_500 },
{ month: "2026-04", ppm2: 178_000 },
],
};
export const FIXTURE_INPUT: TradeInEstimateInput = {
address: "Екатеринбург, ул. Репина, 75/2",
area_m2: 55.3,
rooms: 2,
floor: 5,
total_floors: 11,
year_built: 2018,
house_type: "monolith",
repair_state: "good",
has_balcony: true,
};