Commit graph

11 commits

Author SHA1 Message Date
lekss361
c92b574e59 fix(tradein): security hotfix — config default + empty estimate persist + PDF URL allowlist
C-3: убрать hardcoded DATABASE_URL default из Settings (fail-fast если env не задан).
C-4: _empty_estimate теперь делает INSERT в БД — GET /estimate/{id} не возвращает 404.
C-6: _safe_url allowlist против javascript:/data: в source_url; CDN allowlist для photo_url.
2026-05-23 12:24:11 +03:00
9b2a54788b fix(tradein): восстановленная оценка показывает этаж/площадь, не 0/0
AggregatedEstimate не нёс параметры целевой квартиры (площадь, этаж,
комнаты, год, тип дома). HeroSummary брал их из формы-инпута — у
свежей оценки работало, но при открытии оценки по ссылке (?id=, из
Истории) формы нет → «Сводка» показывала «ЭТАЖ 0/0, Площадь 0 м²».

Параметры теперь проходят сквозь весь стек:
- AggregatedEstimate + TS-тип получили area_m2/rooms/floor/
  total_floors/year_built/house_type/repair_state/has_balcony;
- estimate_quality заполняет их из payload, get_estimate — из строки
  trade_in_estimates (колонки там уже есть);
- HeroSummary читает из estimate с откатом на input.

Заодно в TS-тип добавлен пропущенный est_days_on_market.
2026-05-22 15:50:19 +05:00
8bef0fd872 fix(tradein): оценка исключает Avito из радиусного поиска аналогов
У Avito-объявлений «координаты» = якорь cron-скрейпа ± jitter (DOM
Avito реальных coords не отдаёт). Фейковые точки кучкуются плотными
кластерами на 5 якорях ЕКБ — tier-1 радиус (1 км) набирался почти
целиком из них: оценка для центра давала sources_used=['avito'],
18 аналогов все Avito, при 1400 живых cian/yandex/n1 в БД.

_fetch_analogs теперь исключает source='avito'. Трёхтирный fallback
по радиусу доходит до реальных cian/yandex/n1 с настоящей геопривязкой
— оценка считается по корректным аналогам.
2026-05-22 14:26:29 +05:00
TradeIn Deploy
48ccbdafc8 feat(tradein): CRM-поля в форме оценки (#395)
Доп. поля для трейд-ин менеджера: тип собственности, ипотека/
обременение, имя и телефон клиента. На расчёт оценки не влияют —
сохраняются в trade_in_estimates вместе с записью.

- data/sql/008_crm_fields.sql — 4 колонки в trade_in_estimates.
- TradeInEstimateInput + estimator INSERT — приём и сохранение.
- EstimateForm — секция «CRM — для менеджера» (4 поля).

Closes #395
2026-05-22 11:15:15 +05:00
TradeIn Deploy
bf2886509d feat(tradein): обогащение house_metadata через OSM Overpass (#392)
Когда пользователь не указал год постройки / тип дома, estimator
подтягивает их из OpenStreetMap (Overpass API) — building:levels,
start_date, building:material. Улучшает house-match аналогов (#6).

- app/services/house_metadata.py — Overpass-провайдер + кэш в таблице
  house_metadata (TTL 30 дней, lookup по близости координат, 40 м).
- estimator.estimate_quality — обогащает target_year / target_house_type
  перед _fetch_analogs. Best-effort: ошибка OSM → оценка без обогащения.

OSM по ЕКБ: building:levels населён хорошо, год постройки — частично.

Closes #392
2026-05-22 10:39:56 +05:00
TradeIn Deploy
923513f906 fix(tradein): estimate 500 — AmbiguousParameter на NULL-параметрах
Второй баг из #385 (#388 закрыл только алиас distance_m). В
_fetch_analogs ORDER BY использует :target_year и :target_house_type.
Когда год постройки / тип дома не заданы, они приходят NULL — psycopg
шлёт их без типа, PostgreSQL не может определить тип параметра →
psycopg.errors.AmbiguousParameter ($8) → 500 на каждый estimate.

Фикс: явный CAST(:target_year AS integer) и CAST(:target_house_type
AS text). Проверено на прод-БД через psycopg3: без CAST падает с
AmbiguousParameter, с CAST — успешно (7 строк).
2026-05-22 10:03:17 +05:00
TradeIn Deploy
dbe16c035e fix(tradein): estimate 500 — алиас distance_m в ORDER BY-выражении
Регрессия #385 (#6 house-match): ORDER BY использует SELECT-алиас
distance_m внутри арифметического выражения. PostgreSQL подставляет
алиасы в ORDER BY только голым термом — внутри выражения имя ищется
как колонка listings.distance_m, которой нет → "column does not
exist" → 500 на каждый геокодящийся адрес. Инлайнили ST_Distance()
вместо алиаса.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:39:36 +05:00
TradeIn Deploy
63c96628e3 feat(tradein): прогноз срока продажи + rate-limit (#9,#16) 2026-05-21 20:12:49 +03:00
TradeIn Deploy
a7d010173f feat(tradein): house-match аналогов + поправка на ремонт (#6,#7)
#6: аналоги сортируются по relevance — расстояние + близость года
постройки + совпадение типа дома (соразмерные квартиры из встречи Птицы).
#7: медиана корректируется коэффициентом по состоянию ремонта
(требует ремонта 0.92 ... евроремонт 1.08).
2026-05-21 20:00:47 +03:00
TradeIn Deploy
f7b820984b feat(tradein): радиус 1000м + пагинация Циан/N1
DEFAULT_RADIUS_M 800→1000 (по встрече Птицы: локация до 800-1000м).
Cian/N1 multi-room теперь обходят 3 страницы выдачи → ~2-3x больше лотов.
2026-05-21 11:59:16 +03:00
lekss361
02267d0306 feat: add tradein-mvp subproject (Trade-In Estimator под /trade-in)
- FastAPI backend: PostGIS estimator + 3 scrapers (Avito/Cian/Yandex)
- Next.js 15 frontend: tradein.html mockup design, basePath=/trade-in
- WeasyPrint PDF (Брусника-style 4-page report)
- Address autocomplete с typo-tolerance + 6 EKB presets
- Изолированный docker stack gendesign-tradein (отдельная postgres БД)
- Caddy inline routes: gendsgn.ru/trade-in/* и /trade-in/api/v1/*
- Forgejo Actions: .forgejo/workflows/deploy-tradein.yml (shell-based GHCR login)
- Триггер только по paths: tradein-mvp/** (не пересекается с deploy.yml)
- Образы: ghcr.io/lekss361/gendesign-tradein-{backend,frontend}:latest

Первый запуск на сервере (вручную, один раз):
  - создать /opt/gendesign/tradein-mvp/.env.runtime (postgres pwd, contact email)
  - docker network create gendesign_shared (если нет)
  - docker compose -p gendesign-tradein up -d
  - docker compose -p gendesign exec caddy caddy reload
2026-05-21 00:25:39 +03:00