Commit graph

1787 commits

Author SHA1 Message Date
bot-backend
fe69d6e70e feat(tradein/estimator): ground-floor term in hedonic correction (#2002) 2026-06-27 19:21:11 +03:00
c45445b726 feat(tradein/estimator): hedonic year+area correction on expected_sold (#2002)
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Successful in 54s
Deploy Trade-In / deploy (push) Successful in 47s
Deploy Trade-In / test (push) Successful in 1m25s
2026-06-27 16:06:08 +00:00
9448a945d4 feat(tradein/estimator): calibrated 80% prediction interval for expected_sold range (#1966)
All checks were successful
Deploy Trade-In / test (push) Successful in 1m26s
Deploy Trade-In / deploy (push) Successful in 47s
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Successful in 51s
2026-06-27 14:01:24 +00:00
40b7ca4242 Merge pull request 'feat(tradein/domclick): обогащение Layer A поиска — description, repair-инференс, minhash, seller' (#1999) from feat/tradein-domclick-search-enrich into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-backend (push) Successful in 52s
Deploy Trade-In / deploy (push) Successful in 50s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m24s
2026-06-27 13:37:13 +00:00
54a5d13c48 feat(tradein/domclick): enrich Layer A SERP parser — description, repair-inference, minhash, seller
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Capture extra bff-search fields in _map_item (verified live):
- description → ScrapedLot.description + infer_repair_state_from_text → repair_state
  (DomClick search has no renovation-enum; text inference like cian #622)
- sha1 description_minhash (cross-source dedup)
- agency_name from seller (defensive str|dict)
- raw_payload: is_sber_collateral, has_discount, discount_value, duplicates_offer_count, seller

Additive only; existing Layer A behavior unchanged. Deep card detail (renovation-enum,
wallType, full price-history, owners_count) is card-only = QRATOR-blocked on prod proxy
→ separate effort (needs dedicated residential IP).
2026-06-27 16:35:25 +03:00
3cd1605c5c Merge pull request 'fix(site-finder): сети и точки подключения на карте Раздела 3 (#1961)' (#1998) from fix/report-map-points-1961 into main
All checks were successful
Deploy / changes (push) Successful in 11s
Deploy / build-worker (push) Has been skipped
Deploy / build-backend (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m36s
Deploy / deploy (push) Successful in 1m29s
2026-06-27 13:00:14 +00:00
ef51bb8b62 Merge pull request 'fix(report): человеческий язык в Разделе 6 — дефицит/затоварка, 6.2/6.3/6.5 (#1963)' (#1988) from fix/report-microcopy-1963 into main
Some checks failed
Deploy / deploy (push) Blocked by required conditions
Deploy / changes (push) Successful in 9s
Deploy / build-frontend (push) Has been cancelled
Deploy / build-backend (push) Has been cancelled
Deploy / build-worker (push) Has been cancelled
2026-06-27 12:59:28 +00:00
ec9ed56fad feat(tradein/backtest): hermetic estimator regression gate — frozen fixture + baseline (#1966 PR 3/3)
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m25s
Deploy Trade-In / build-backend (push) Successful in 55s
Deploy Trade-In / deploy (push) Successful in 54s
2026-06-27 12:55:35 +00:00
1bbc032316 fix(site-finder): show networks & connection points on Раздел 3 map (#1961)
All checks were successful
CI / changes (pull_request) Successful in 8s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m3s
CI / openapi-codegen-check (pull_request) Successful in 1m54s
The "Сети" section table summarised engineering networks and distances, but
users could not see those points on the map. Three root causes:

- Connection-points layer was always empty: NSPD engineering_structures
  (cat 36328) arrive as Polygon/MultiPolygon in EPSG:3857 (prod: 587 Polygon
  + 298 MultiPolygon, zero Point), but extractLatLon only handled Point in
  EPSG:4326. Extend it to compute the outer-ring centroid for
  Polygon/MultiPolygon and reproject 3857→4326 (frontend math, no backend
  change), so the 10+ connection points render as markers with popups.
- Map vs table coverage mismatch: the table counts "В 2 км" while the OSM
  utility-infrastructure layer fetched only 500 m (1 object inside 500 m vs
  153 within 2 km). Raise the hook default to a shared
  UTILITY_COVERAGE_RADIUS_M = 2000 so the map matches the table column.
- Coherence microcopy: clarify that the map shows the same networks as the
  table (2 km), plus NSPD connection points and protection zones.

Adds unit tests for extractLatLon polygon-centroid + 3857→4326 reprojection.

Refs #1961
2026-06-27 17:52:51 +05:00
e82761964d fix(report): human RU microcopy in Site Finder Section 6 (#1963)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Successful in 1m4s
CI / openapi-codegen-check (pull_request) Successful in 1m54s
CI / backend-tests (pull_request) Successful in 13m50s
Replace dev-jargon with plain RU across the §22 forecast report (6.2/6.3/6.5
+ deficit/затоварка legend). Source of truth = backend reason strings + the
frontend RU maps; text/render-only, no scoring/forecast math touched.

Backend (source of truth):
- scenarios.py _COLLAPSE_REASON_LOW_BETA: «β rate-sensitivity не прошёл gate …»
  → «чувствительность к ставке не оценена на коротком ряде ЕКБ → один базовый
  сценарий вместо трёх».
- confidence_engine.py _coverage_factor: drop «domrf↔objective» jargon, say it
  affects будущее предложение/конкуренцию. New _history_factor: «глубина истории
  N мес» + на что влияет + связь с 6.2 (короткий ряд → один сценарий).

Frontend (both Section-6 families — live analysis page + ptica cockpit):
- Deficit legend: −1 затоварка / 0 баланс / +1 острый дефицит + actionable
  трактовка; MOI tied to «сколько месяцев район распродаёт предложение».
- 6.2 heading «Почему один сценарий, а не три» over the collapse reason.
- 6.3 render confidence.rationale + weakest-link rule («скорее завысим
  недоверие, чем недооценим риск»); FACTOR_RU gains confounded_window/
  advisory_cap; factor notes shown.
- 6.5 «Вес»→«Оценка»; overall verdict vs 0.5; «Риск избытка предложения»→
  «Запас по предложению»; §-refs moved from reason into tooltip; 6 special-
  index 1-line «что это + куда лучше» descriptions; 0.00-score reasons shown.

Tests: confidence_engine (history/coverage notes), stripSectionRefs vitest.

Refs #1963
2026-06-27 17:42:27 +05:00
16f375782b Merge pull request 'fix(report): вывести ПЗЗ-градрегламент НСПД в Раздел 1 + coerceFloat КСИТ (#1962)' (#1987) from fix/report-nspd-zoning-1962 into main
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 2m59s
Deploy / deploy (push) Successful in 1m10s
2026-06-27 12:22:51 +00:00
63d685a105 fix(report): уникальный React key для ВРИ-списка (дубль-VRI warning) (#1962)
All checks were successful
CI / changes (pull_request) Successful in 14s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m29s
CI / openapi-codegen-check (pull_request) Successful in 2m22s
2026-06-27 17:18:57 +05:00
727f9b1f05 Merge pull request 'fix(tradein/auth): always-available logout button on NoAccessScreen' (#1986) from fix/tradein-logout-on-noaccess into main
Some checks failed
Deploy / changes (push) Successful in 8s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m22s
Deploy Trade-In / deploy (push) Successful in 55s
Deploy / build-frontend (push) Successful in 3m50s
Deploy / deploy (push) Has been cancelled
2026-06-27 12:18:34 +00:00
a9dfe44f5c fix(tradein/auth): always-available logout button on NoAccessScreen
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 51s
CI / openapi-codegen-check (pull_request) Successful in 1m56s
Locked/no-access users (403/denied path/expired session/trial ended) were
trapped: NoAccessScreen had no logout control, and «Выйти» lived only inside
UserMenu, which does not render on the no-access screen. They could not switch
accounts without clearing browser basic-auth manually.

- Extract logout() into shared `lib/logout.ts` (single source of truth).
- UserMenu now imports it instead of a local copy (behaviour unchanged).
- NoAccessScreen renders an always-available «Выйти» accent button for every
  variant.

Mirrored across both frontends (tradein-mvp/frontend + main frontend) to keep
the MIRROR invariant in sync.
2026-06-27 15:16:42 +03:00
58e71dab01 fix(report): surface ПЗЗ-градрегламент НСПД в Раздел 1 (#1962)
Light report (Section1ParcelInfo) only rendered the dead top-level
zoning.data_available=false (closed-PKK6 path) and never showed the
resolved nspd_zoning, so the resolved regulation (synth #1891) was
invisible to the user («мы выгружали НСПД — где они?»).

- Add «Градрегламент (НСПД)»-card to Раздел 1 (right column, под ЕГРН):
  тер.зона, КСИТ/max_far, пред. высота, этажность, коэф. застройки,
  КСИТ-ёмкость/пятно (от площади ЕГРН), источник + список разрешённых
  ВРИ зоны. Empty-state когда регламент не определён (gap-зоны / не-ЕКБ).
  КСИТ-microcopy расшифровывает жаргон на первой странице.
- New pure mapper adaptNspdRegulation (nspd-regulation.ts) reused by
  ptica-adapt so light report и /ptica остаются consistent.
- coerceFloat: max_far/max_building_pct приходят с провода СТРОКАМИ
  ("2.4"/"80") — фикс латентного бага cockpit'а (typeof===number
  глушил реальный строковый КСИТ в massing/insolation).
- Widen NspdZoning.max_far/max_building_pct to string|number, add
  main_vri; expose nspd_zoning on ParcelAnalyzeResponse.
- Unit test для field-mapping + coercion + empty-state.
2026-06-27 17:10:20 +05:00
c1e0ccb2e8 Merge pull request 'fix(site-finder): фронт light-отчёта — encumbrance/obj_class/ЗОУИТ display + forecast axis & flat-state (#1954 #1955 #1957 #1958)' (#1985) from fix/report-frontend-display-1953 into main
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 2m59s
Deploy / deploy (push) Successful in 1m23s
2026-06-27 12:00:13 +00:00
6adba229f0 fix(site-finder): wire encumbrance/obj_class/ЗОУИТ display + forecast axis & flat-state (#1953)
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 51s
CI / openapi-codegen-check (pull_request) Successful in 1m45s
Four frontend display fixes for the Site Finder analysis report (audit #1953);
all consume fields the (already-deployed) backend now returns.

- #1954: ЕГРН «Обременения» row was hardcoded «—». Wire top-level
  `encumbrance` block {has_zouit, zouit_count, zouit_types} via
  formatEncumbrance → «ЗОУИТ: N (types)» / «не выявлено (НСПД)».
- #1955: show obj_class per success bucket («Студии 15-30 · Комфорт») so
  rows with identical area-labels are distinct; fix top-row match + React key
  to (bucket, obj_class).
- #1957: NspdZouitOverlapsBlock renders human type_zone + reg_numb_border,
  adds a СЗЗ caveat banner (СанПиН 2.2.1/2.1.1.1200-03) and «СЗЗ вашего
  участка» highlight; switched raw hex to UI tokens.
- #1958: ForecastChart axis names → nameLocation:middle + nameGap (no legend
  overlap); grey flat-state callout when the deficit line is degenerate
  (scenarios_collapsed or all points clamped at ±1).

Tests: formatEncumbrance + isDeficitDegenerate (15 new). Real segment
differentiation is backend #1959 (separate).
2026-06-27 16:51:40 +05:00
b2d4f980f0 Merge pull request 'chore(design-sync): sync inputs for claude.ai/design trade-in DS' (#1984) from chore/design-sync-tradein into main
Reviewed-on: #1984
2026-06-27 11:42:14 +00:00
89c31e7449 Merge pull request 'fix(report): newbuild-consistent медиана + obj_class/Comfort dedup + recommend_mix regression (#1955 #1960)' (#1978) from fix/report-data-1953 into main
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Successful in 1m50s
Deploy / build-worker (push) Successful in 3m8s
Deploy / build-frontend (push) Successful in 3m44s
Deploy / deploy (push) Successful in 1m26s
2026-06-27 11:41:36 +00:00
e70f5da82b chore(design-sync): sync inputs for claude.ai/design trade-in DS
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Imports tradein-mvp/frontend (38 components) to a claude.ai/design
design-system project. Synth-entry build (Next.js app, no dist):
- overrides/source-kit.mjs: process.env shim for browser IIFE
- preview-provider.tsx: seeded TanStack Query provider (mirrors the
  repo's offline ui-preview client) so fetch-coupled + auth-gated cards render
- previews/*.tsx: authored preview compositions (real fixture data)
- conventions.md: README header for the design agent
- config.json + NOTES.md: reproducible re-sync inputs
2026-06-27 14:40:50 +03:00
d5e51ca809 Merge branch 'main' into fix/report-data-1953
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Successful in 1m36s
CI / openapi-codegen-check (pull_request) Successful in 2m28s
CI / backend-tests (pull_request) Successful in 14m16s
2026-06-27 16:26:39 +05:00
11861e5dc0 Merge pull request 'fix(ci): pin prettier 3.9.0 (codegen-check + pre-commit) + regenerate stale api-types.ts' (#1981) from fix/ci-prettier-codegen-pin into main
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 5m29s
Deploy / deploy (push) Successful in 1m16s
CI / changes (pull_request) Successful in 8s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Reviewed-on: #1981
2026-06-27 11:25:37 +00:00
57bf8ffb81 feat(tradein/backtest): full-spine prediction + range-coverage/calibration/segment metrics (#1966 PR 2/3)
All checks were successful
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-backend (push) Successful in 53s
Deploy Trade-In / deploy (push) Successful in 48s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m24s
Full-spine backtest harness via _price_from_inputs + range-coverage/calibration/per-segment MAPE. code-reviewer APPROVE. Real prod numbers: overall MAPE 18.4%/bias -1.2%; segments эконом +16% / бизнес -23% / элит -38%; range-coverage 56.6%. Read-only; full suite 2488 passed. Refs #1966.
2026-06-27 11:21:57 +00:00
ce6e693ea7 fix(ci): pin prettier 3.9.0 for codegen check + pre-commit so api-types is byte-stable
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (pull_request) Successful in 1m13s
CI / backend-tests (pull_request) Successful in 12m25s
CI / openapi-codegen-check (pull_request) Successful in 2m10s
openapi-codegen-check был RED на каждом PR: CI шаг гонял bare `npx prettier`
(резолвится в плавающий latest), а committed api-types.ts был отформатирован
старым prettier (≤3.6.2, union-типы multi-line). 3.9.0 переносит union-wrapping
на single-line → CI-регенерация всегда давала diff → gate падал.

Чиним детерминизмом одной версии на обоих путях:
- frontend/package.json: добавлен prettier@3.9.0 (exact) в devDependencies
  + обновлён package-lock.json.
- CI openapi-codegen-check: bare `npx prettier` → ./node_modules/.bin/prettier
  (project-local pinned, не плавает).
- .pre-commit-config.yaml: prettier hook additional_dependencies=["prettier@3.9.0"]
  — тот же движок, что в CI (alpha.8-обёртка байт-в-байт == prettier 3.9.0).
- api-types.ts перегенерирован prettier 3.9.0 (union-типы single-line).

Backend OpenAPI-схема не менялась — только формат. CI-регенерация теперь
байт-в-байт совпадает с committed-файлом и с выводом pre-commit hook.
2026-06-27 16:11:26 +05:00
efdf11d10f Merge pull request 'fix(tradein/ux): honest source list — drop dead domklik/restate, remove fake per-source median (#1968)' (#1980) from feat/tradein-honesty-sources into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 1m47s
Deploy Trade-In / deploy (push) Successful in 48s
Reviewed-on: #1980
2026-06-27 11:03:59 +00:00
684385263e Merge pull request 'fix(tradein/domclick): рекан скрейпера на BFF JSON API + честный статус (#1846, #1968)' (#1979) from fix/tradein-domclick-bff-rewrite into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m23s
Deploy Trade-In / build-backend (push) Successful in 50s
Deploy Trade-In / deploy (push) Successful in 49s
2026-06-27 10:59:47 +00:00
f1b8ae47a2 fix(tradein/ux): honest source list — drop dead domklik/restate, remove fake per-source median, sync hero/footer/card (#1968)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
2026-06-27 13:58:16 +03:00
4a63b8f7d7 Merge remote-tracking branch 'forgejo/main' into fix/tradein-domclick-bff-rewrite
All checks were successful
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
2026-06-27 13:57:24 +03:00
52fc920507 fix(tradein/domclick): rewrite dead scraper to BFF JSON API + honest status (#1846, #1968)
Old HTML scraper dead: /search?city_id retired in DomClick redesign + VPS IP
QRATOR-banned (prod domclick_city_sweep reported done/0 every run). New path:

- GET bff-search-web.domclick.ru/api/offers/v1 + count/v1 via
  BrowserFetcher(source="domclick") -> generic provider -> shared mobile proxy
  -> bypasses QRATOR (prod-verified live: 20 items returned, no block).
- Sweeps 6 room buckets (st/1/2/3/4/5+); recursive binary price-split for
  oversized buckets (rooms=2 ~2215 > offset cap 2000); count/v1 sizes buckets.
- Maps SERP JSON -> ScrapedLot with lat/lon at ~100% (closes avito 54% NULL-lat
  gap); per-card EKB geo-guard.
- Honest status (#1968): QRATOR block OR fetch errors with 0 lots -> mark_failed,
  not mark_done. Parse failures are per-page resilient (break bucket, keep partial
  lots) so a transient bad page never aborts the whole sweep.
- Schedule shipped DORMANT (enabled=false, pages=100) via
  138_domclick_bff_rewrite_schedule.sql; operator enables after prod smoke.

42 scraper tests (price-split boundaries, parse-error resilience, honest status);
full backend suite green; ruff clean. Layer B (detail renovation/wallType/
priceHistory) deferred to #1846 follow-up.
2026-06-27 13:57:07 +03:00
a184d38aa6 fix(analytics): recommend_mix success-boost regression from view 172 (#1955)
Some checks failed
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (pull_request) Successful in 1m0s
CI / openapi-codegen-check (pull_request) Failing after 1m51s
CI / backend-tests (pull_request) Successful in 13m47s
Миграция 172 убрала english 'Comfort' из v_bucket_success_score (NULL-class
→ 'не указан'), но _bucket_success_ranking в analytics_queries.py матчил
COALESCE(:cls, 'Comfort') — после 172 ни одна строка не равна 'Comfort' →
recommend_mix success-boost (#25, /analytics/recommend квартирография) тихо
возвращал [] (silent degradation). LIVE на проде с момента ручного применения
172 (graceful empty, без краша).

Fix:
- call site (recommend_mix): передаём target_class_db (уже переведённый через
  _class_to_db_vocab english→русский), а не сырой english target_class.
  Чинит и латентный pre-existing баг: Business/Elite никогда не матчили
  русский источник.
- SQL default 'Comfort' → 'Комфорт' (массовый класс ЕКБ, 723 объекта).
  NULL-class ('не указан') в default-путь намеренно не попадают
  (документировано в docstring).

Prod-verified: OLD 'Comfort' default → 0 строк; NEW 'Комфорт' default → 5.

Tests:
- _bucket_success_ranking: реальный запрос (не замокан) с русским default —
  SQL содержит 'Комфорт', не 'Comfort'; ranking непустой.
- recommend_mix: english 'Business' переводится в 'Бизнес' перед ranking
  (раньше тест патчил _bucket_success_ranking→[] и баг не ловил;
  helper теперь умеет patch_success_ranking=False).
- #1960: позитивный тест выбора quarter_rosreestr basis (deals≥5, все
  higher fallback'и None) → median_price_basis='quarter_rosreestr'.
2026-06-27 15:49:40 +05:00
86ddf99bb6 Merge pull request 'feat(tradein/auth): expire praktika trial access + trial-ended screen (contact Artem)' (#1977) from feat/tradein-praktika-trial-ended into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 9s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m25s
Deploy Trade-In / build-frontend (push) Successful in 1m56s
Deploy Trade-In / build-backend (push) Successful in 50s
Deploy Trade-In / deploy (push) Successful in 49s
Reviewed-on: #1977
2026-06-27 10:35:06 +00:00
703e411a17 feat(tradein/auth): expire praktika trial access + trial-ended screen (contact Artem)
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
praktika pilot→expired role in roles.yaml (paths:[], deny:/**); RouteGuard
short-circuits on role=expired with a dedicated trial-ended screen (not the
generic path-deny path). NoAccessScreen gains variant="trial" with title
«Пробный доступ закончился» and a Telegram link to @ArtemKopylov87. Backend
Role Literal and frontend Role type extended to include "expired". kopylov
(pilot) unaffected.
2026-06-27 13:30:28 +03:00
b60df61f6a Merge pull request 'refactor(tradein/estimator): extract deterministic pricing into pure _price_from_inputs (#1966 foundation)' (#1976) from refactor/tradein-estimator-price-spine into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m22s
Deploy Trade-In / build-backend (push) Successful in 49s
Deploy Trade-In / deploy (push) Successful in 46s
Reviewed-on: #1976
2026-06-27 10:13:33 +00:00
453a1f08da fix(report): newbuild-consistent district median + obj_class dedup (#1953)
FIX A (#1955) «Что хорошо продаётся»: убран фантомный класс 'Comfort'.
- Миграция 172: v_bucket_success_score COALESCE(obj_class,'Comfort')
  → COALESCE(obj_class,'не указан'). Английский литерал заполнял 397 NULL
  и сливался отдельным классом от русского 'Комфорт' → визуальные дубли
  бакетов в UI. Источник уже канонически-русский (проверено на проде),
  synonym-mapping не нужен.
- parcels.py: obj_class протаскивается в success-ranking query + dict.
- TS SuccessRankingBucket.obj_class добавлен.

FIX B (#1960) «Медиана рынка» = 64k (квартальная росреестровская n=1 ДКП):
- district.median_price_per_m2 больше не COALESCE(median_12m, ekb_ref) в SQL.
  Basis-приоритет (newbuild-first): Objective по имени района →
  geo_radius (Objective в 3км) → ekb_districts reference →
  квартальная росреестровская медиана ТОЛЬКО при deals_count≥5.
  Для 66:41:0205010:287: 64k → 132690 (geo_radius, newbuild-consistent).
- median_price_basis добавлен в payload + TS type (nullable median).
- Frontend null-guards для нового nullable median.

Tests: +4 (geo_radius basis, objective-приоритет, deals-guard, obj_class
passthrough); обновлены district-моки в 9 analyze-тестах под новую
SQL-сигнатуру.
2026-06-27 15:10:49 +05:00
845f699176 chore(claude-config): secret-read + psycopg2 hooks, tradein.md rule, tradein-mvp globs (#1975) 2026-06-27 10:03:19 +00:00
2ea49b637f refactor(tradein/estimator): extract deterministic pricing into pure _price_from_inputs (#1966 foundation)
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Behavior-preserving structural refactor: the ~870-line deterministic pricing
block inside estimate_quality() is extracted into a new pure synchronous function
_price_from_inputs() returning a PricingResult dataclass.

Key design decisions:
- All async DB fetches (imv_eval, yandex_val, cian_val) hoisted to estimate_quality
  BEFORE the call; passed as pre-fetched values / bool flags.
- DB-dependent helpers whose arguments are computed inside the block (_get_asking_sold_ratio,
  _lookup_quarter_index, _lookup_quarter_indexes) injected as Callable parameters
  (ratio_resolver, quarter_index_lookup, quarter_indexes_lookup).
- _fetch_house_imv_anchor called once in estimate_quality (was two separate conditional
  calls inside the block); single result passed as imv_anchor dict.
- _fetch_dkp_corridor and _fetch_anchor_comps hoisted to estimate_quality with
  identical guards.

All 2443 existing tests pass UNMODIFIED. 9 new hermetic unit tests added that
call _price_from_inputs directly with stub callables.
2026-06-27 13:00:21 +03:00
24a80ee0cf wip: extract pricing spine (pre-test) 2026-06-27 12:51:14 +03:00
3b7e1dc27f Merge pull request 'chore(claude-config): self-merge policy (Mera/Ptica) + config-audit fixes' (#1974) from chore/claude-config-audit into main
Reviewed-on: #1974
2026-06-27 09:48:29 +00:00
f1f49176f5 chore(claude-config): self-merge policy (Mera/Ptica) + config-audit fixes
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
- git-pr.md: self-merge разрешён для Mera/Ptica (solo+pipeline, любой scope, зелёный CI);
  balance_platform = stage only; tripwires (secret / guard-files) остаются human-only
- CLAUDE.md: qa-tester + deep-code-reviewer добавлены в subagent-таблицу (rule #7 / merge-authority)
- frontend.md: codegen path src/types/openapi.ts -> src/lib/api-types.ts (реальный out)
- deploy.md: GHA -> Forgejo Actions; синк path-триггеров (caddy/**, data/sql/**, ops/glitchtip-auth-forwarder/**, deploy-tradein.yml)
- work-as-reviewer/analyst: cadence в body синхронизирован с frontmatter (5m->2m / 30m->15m)

Audit 2026-06-27 + решение юзера по split commit/merge-автономии.
2026-06-27 12:44:47 +03:00
ebdbedcabb Merge pull request 'fix(report): EGRN площадь/обновлено + forecast-confidence dedup + ЗОУИТ типизация (#1954 #1958 #1957)' (#1973) from fix/report-quickwins-1953 into main
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Successful in 2m9s
Deploy / build-frontend (push) Successful in 3m14s
Deploy / build-worker (push) Successful in 3m23s
Deploy / deploy (push) Successful in 1m24s
2026-06-27 09:34:44 +00:00
bd1ed4861c docs(zouit): уточнить DISTINCT ON-комментарий + убрать stale cad_zouit из FE-типа (#1957)
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (pull_request) Successful in 1m0s
CI / openapi-codegen-check (pull_request) Successful in 1m51s
CI / backend-tests (pull_request) Successful in 12m4s
Code-review nits (без изменения поведения):
- quarter_dump_lookup.py: комментарий про дедуп ошибочно утверждал, что зоны без
  рег-номера не схлопываются. На деле reg_numb_border — TEXT NOT NULL (0 NULL на
  проде), а DISTINCT ON в PG считает NULL равными. Переформулировано на точное.
- frontend/types/nspd.ts: убран устаревший 'cad_zouit' из списка возможных
  group_key (бэкенд теперь эмитит только protected/engineering/okn/natural/other).
2026-06-27 14:21:01 +05:00
76647bb4cf fix(report): площадь/обновлено EGRN, forecast-confidence dedup, ЗОУИТ типизация (#1953)
#1954 — площадь «—»: COALESCE(land_record_area, specified_area, declared_area)
в EGRN-блоке analyze (land_record_area NULL у 12809/42233 участков, но
specified_area заполнена). area_m2 для 66:41:0205010:287 теперь 106378 (был NULL).

#1954 — «Обновлено» сломано: cost_registration_date — мёртвая колонка (0/42233);
репойнт на updated_at (42233/42233 заполнено). Ключ ответа last_egrn_update_date
не меняется (additive value fix).

#1958 — confidence-фактор «Прогноз спрос/предложение» дублировался ×4
(по фактору на горизонт). Сворачиваем в один weakest-link'ом (MIN ранга
по горизонтам) в _component_confidences до confidence-движка (#990).

#1957 — ЗОУИТ backend:
- _get_cad_zouit_overlaps: DISTINCT ON (reg_numb_border) — дедуп дубль-строк
  (одна физ. зона 2× с разным category_name). group_key cad_zouit→protected.
- _get_zouit_overlaps (dump path): subcategory→RU-тип карта (26→СЗЗ и др.,
  коды сверены кросс-джойном dump↔cad_zouit на проде); type_zone из карты,
  reg из props.options.reg_numb_border. Раньше отдавал blank-строки.
- унификация group_key (protected/engineering/okn/natural/other) + top-level
  reg_numb_border в обоих путях.

UP038-модернизация isinstance в report_assembler (pre-commit ruff 0.7.4).

Frontend note (#1957): nspd_zouit_overlaps теперь всегда group_key из набора
{protected,engineering,okn,natural,other} — сырой 'cad_zouit' больше не отдаётся;
оба пути несут type_zone + reg_numb_border.

Tests: +4 _component_confidences collapse, +6 ЗОУИТ (subcategory map, dump
typing, DISTINCT ON), schema-test обновлён на protected. 451 passed targeted.
2026-06-27 14:14:40 +05:00
1123f38149 Merge pull request 'fix(scrapers): cian full_load anti-zombie + avito changeip-resilience & честный статус (#1949 #1950)' (#1952) from fix/scraper-resilience-cian-zombie-avito-changeip into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 9s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m23s
Deploy Trade-In / build-backend (push) Successful in 49s
Deploy Trade-In / deploy (push) Successful in 48s
Reviewed-on: #1952
2026-06-27 08:38:10 +00:00
e24d3cf4be fix(scrapers): cian full_load anti-zombie — per-fetch wait_for + heartbeat внутри бакета (#1949)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Два связанных фикса из диагностики 2026-06-27:

ФИКС C (run_cian_full_load, per-fetch timeout): monkey-patch _browser.fetch →
asyncio.wait_for(_f(url), timeout=cian_full_load_per_fetch_timeout_s=90s).
BrowserFetcher.fetch имеет httpx-timeout 120s, но browser-сервис иногда виснет
так что httpx не получает ответ (нет EOF) → fetch висит → asyncio.gather
блокирует весь bucket → heartbeat_at не обновляется → reap_zombies убивает живой run.
wait_for(90s) отменяет зависший fetch → TimeoutError → _fetch_page_html ловит
как Exception → None → _one_page → [] → gather завершается нормально.
За флагом (cian_full_load_per_fetch_timeout_s=0 = отключить, >0 = включить).

ФИКС D (background heartbeat): asyncio.create_task(_background_heartbeat())
обновляет heartbeat_at каждые 60s независимо от on_bucket/on_progress прогресса.
Без него пустые bucket или зависший gather замораживает heartbeat на несколько часов.
Task отменяется в finally → не течёт при return/raise/cancel.

Новая настройка (ENV):
  CIAN_FULL_LOAD_PER_FETCH_TIMEOUT_S=90.0  — timeout одного browser-fetch
2026-06-27 11:32:42 +03:00
757f6486f5 fix(scrapers): avito changeip-resilience (retry короткими попытками) + честный статус когда SERP отработал (#1950)
Два связанных фикса из диагностики 2026-06-27:

ФИКС A (_rotate_proxy_ip): вместо одношотного GET с timeout=30s — retry-loop
(proxy_rotate_attempts=3 попыток по proxy_rotate_attempt_timeout_s=8s каждая).
Зависший changeip-сервис больше не блокирует весь sweep на 30s.
Первый успешный attempt → True; все провалились → False (логируется как error).

ФИКС B (run_avito_city_sweep): когда SERP-фаза уже сохранила лоты
(lots_inserted + lots_updated > 0) и упала только detail/houses-фаза
(AvitoBlockedError / RateLimitedError), помечаем run как done (не banned)
при avito_serp_ok_not_banned=True (default). Метрика banned зарезервирована
для случаев где SERP сам заблокирован (0 лотов).

Новые настройки (ENV):
  PROXY_ROTATE_ATTEMPT_TIMEOUT_S=8.0   — timeout одной changeip-попытки
  PROXY_ROTATE_ATTEMPTS=3              — число попыток перед отказом
  AVITO_SERP_OK_NOT_BANNED=true        — флаг нового поведения (default on)
2026-06-27 11:24:54 +03:00
d87641ac29 Merge pull request 'fix(freshness): output-floor on cycle-SUM over fresh_days window (#1945, #1947)' (#1948) from feat/freshness-output-floor into main
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m41s
Deploy / build-worker (push) Successful in 2m38s
Deploy / deploy (push) Successful in 1m24s
2026-06-27 07:50:04 +00:00
c646a71001 fix(freshness): output-floor on cycle-SUM over fresh_days window (#1945, #1947)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m45s
CI / backend-tests (pull_request) Successful in 11m57s
Adversarial review found the single-newest last_success_work basis fragile:
a load cycle = MULTIPLE done-runs (objective per group_name, kn per region +
resume), so the newest run alone misrepresents the cycle. Replace with
SUM(work_col) FILTER(done) over the source's own fresh_days window.

- recent_output = SUM(COALESCE(work_col,0)) FILTER done in fresh_days window
  (make_interval secs => fresh_days*86400; no :: cast; NULL-counter -> 0)
- downgrade when status==ok and recent_output < min_output_rows (strict <)
- registry floors kept: kn_flats=50000 (healthy ~376k, broken sum <=3670),
  objective=1000 (SUM 7d ~946k); comments rewritten to cycle-sum basis +
  kn_flats zombie-resume KNOWN LIMITATION
- window=fresh_days keeps the #1947 aging-FP fix (7-8d run stays in window)
- tests: low-output-cycle->failed, above-floor->ok, boundary==floor->ok,
  weekly-aging->ok, age-stale precedence, registry floors

Verified on prod: domrf_kn_flats latest snapshot 2026-06-22=9 flats vs healthy
2026-05-17=376604 (broken 5+ weeks); objective SUM(rows_lots,7d)=946264.
2026-06-27 12:36:50 +05:00
9c9b50ceca Merge pull request 'feat(freshness): zero-output check + kn_flats source — catch "ran but produced nothing" (#1945)' (#1947) from feat/freshness-zero-output-check into main
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m47s
Deploy / build-worker (push) Successful in 2m59s
Deploy / deploy (push) Successful in 1m27s
Reviewed-on: #1947
2026-06-27 07:02:51 +00:00
bd1adc1f59 feat(freshness): zero-output check + kn_flats source (#1945)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m53s
CI / backend-tests (pull_request) Successful in 12m5s
The data-freshness monitor classified by run RECENCY only, so the domrf_kn
FLATS loader running status=done but extracting 0 flats for ~5 weeks went
undetected — and the kn source watched objects_count (healthy ~1548), not
flats_count (the broken =0 metric).

Add an opt-in zero-output check: an otherwise-fresh run-ledger source (recent
success, would-be fresh by age) that produced 0 work-rows in the 7d window is
downgraded to status="failed" (so scrape_freshness_check alerts), with an
additive "reason". Guards: alert_on_zero_output flag, run-ledger only
(timestamp_col is None), status=="ok" (age-stale/failed already covered), and
upd_7d==0 (SUM of the source's own work_col over done-runs).

Registry: new kn_flats source (kn_scrape_runs, work_col=flats_count, critical,
flag on) — watches the column that was broken; existing kn (objects_count)
unchanged. Flag also enabled on objective (rows_lots, critical). nspd/nspd_geo/
cadastre left unflagged (legitimate-0 / data-table).

JSON additive only (new nullable "reason" key; endpoint is dict[str,Any], no
frontend consumer / no codegen needed). 4 new tests (downgrade, no-false-
positive, age-precedence, registry). code-reviewer APPROVE.

Would have caught #1945 within ~8-14d instead of 5 weeks.
2026-06-27 11:52:29 +05:00
1fb33e20dd Merge pull request 'perf(estimate): trigram expr index for same-building anchor Tier A (~94x)' (#1946) from perf/tradein-estimate-audit into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 9s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m22s
Deploy Trade-In / build-backend (push) Successful in 26s
Deploy Trade-In / deploy (push) Successful in 49s
2026-06-27 06:18:01 +00:00