fix+chore: NSPD migration applied to prod, README trim, analytics backlog +5
- Применена миграция 63_schema_nspd_runs.sql вручную через MCP
(тянут init-скрипты только при первичной инициализации БД).
Чинит 500 на /api/v1/admin/scrape/nspd/{runs,logs,coverage}.
- README: 724→148 строк. Выкинута операционная история апреля,
443-sweep инструкции (теперь через admin UI), таблица 43 таблиц
(есть в графе памяти), step-by-step onboarding (есть в #11).
- 5 новых issues по аналитике: #17 velocity-anomaly, #18 ekb_districts
median refresh (bug), #19 competitor-360, #20 mortgage-rate
sensitivity, #21 cad_buildings фильтры в Site Finder.
- Закрыт #4 (Sentry release tracking — реализовано в 25b7303).
This commit is contained in:
parent
e22f86bc06
commit
bb28f77f66
1 changed files with 88 additions and 664 deletions
752
README.md
752
README.md
|
|
@ -1,724 +1,148 @@
|
|||
# Generative Design + Site Finder
|
||||
|
||||
Две продуктовые линии в одной воронке: **Site Finder** (AI-подбор инвестиционных земельных участков) + **Generative Design** (автоматическая генерация концепций застройки) для девелоперов РФ.
|
||||
Две продуктовые линии в одной воронке: **Site Finder** (AI-подбор инвестиционных земельных участков) + **Generative Design** (автоматическая генерация концепций застройки) для девелоперов РФ. Целевой регион Discovery — Свердловская обл. (ЕКБ, ПЗЗ, МСК-66).
|
||||
|
||||
## North Star
|
||||
|
||||
К концу месяца 3: **3 платящих пилота** (≥30 тыс. ₽/мес каждый) + заявка в реестр российского ПО → право на Раунд А (20–25 млн ₽).
|
||||
|
||||
Acceptance criteria разбиты на 3 блока (функционально / бизнесово / технически). Правило перехода: 3/3 → Раунд А; 2/3 → продление Фазы 0 на 1–2 мес.; 1/3 или меньше → стоп проекта.
|
||||
|
||||
---
|
||||
|
||||
## Текущий статус (27 апреля 2026)
|
||||
## Текущий статус (апрель 2026)
|
||||
|
||||
**Инфраструктура: live на HTTPS.** Прод доступен по [https://gendsgn.ru/](https://gendsgn.ru/), Let's Encrypt cert получен. Полный цикл автодеплоя из GitHub Actions работает. 26.04 прод полностью пересоздан (`down -v` + redeploy) — БД свежая, backend в multi-stage образе под non-root пользователем, healthcheck-и работают, ловит ошибки в Sentry.
|
||||
**Live:** [https://gendsgn.ru/](https://gendsgn.ru/) — HTTP/2+HTTP/3, валидный TLS, [/health](https://gendsgn.ru/health) `{"status":"ok"}`. Полный CI/CD: GitHub Actions → GHCR → SSH-deploy на Beget VPS. Sentry активен (release tracking привязан к git-sha).
|
||||
|
||||
| Что | Где | Статус |
|
||||
|---|---|---|
|
||||
| **Production URL** | [https://gendsgn.ru/](https://gendsgn.ru/) | ✅ HTTP/2 + HTTP/3, валидный TLS |
|
||||
| **Health endpoint** | [https://gendsgn.ru/health](https://gendsgn.ru/health) | ✅ `{"status":"ok","environment":"prod"}` |
|
||||
| **www → apex** | https://www.gendsgn.ru/ | ✅ 301 redirect |
|
||||
| Production VPS | Beget Cloud, Москва, 2 vCPU / 4 GB / 40 GB NVMe | ✅ работает |
|
||||
| IP сервера | `46.173.16.127` | — |
|
||||
| Домен | `gendsgn.ru` (Beget, до 26.04.2027, ЕСИА VERIFIED) | ✅ DNS на 8.8.8.8/1.1.1.1 |
|
||||
| Postgres + PostGIS 3.4 | Docker, в составе compose | ✅ Up (healthy) |
|
||||
| Redis 7 | Docker | ✅ Up (healthy, redis-cli ping) |
|
||||
| Backend FastAPI | Docker, multi-stage, non-root, 1.44 GB | ✅ Up (healthy, curl /health) |
|
||||
| Frontend Next.js 15 | Docker, multi-stage, npm ci | ✅ Up |
|
||||
| Caddy reverse proxy | Docker, ports 80/443 | ✅ Up + auto-TLS Let's Encrypt |
|
||||
| GitHub Actions deploy | `.github/workflows/deploy.yml` | ✅ build → push GHCR → ssh deploy → git pull → caddy reload |
|
||||
| GHCR пакеты | `ghcr.io/lekss361/gendesign-{backend,frontend}` | приватные, сервер авторизуется через PAT |
|
||||
| UFW firewall | 22 / 80 / 443 | ✅ активен |
|
||||
| SSH hardening | root login off, password auth off | ✅ |
|
||||
| Sentry | проект `gendesign-backend` (org `qqq`, free tier) | ✅ DSN на VM, события приходят |
|
||||
| GitHub Issues / Milestones | 12 issues, 3 milestones (Stage 1 / Stage 2 / Infra Hardening) | ✅ backlog засеян |
|
||||
| Бэкапы pg_dump | `ops/backup.sh` готов | ⏸ S3-бакет не настроен (Selectel/Yandex) |
|
||||
|
||||
**Решение по плану разработки: pending.** Антон (второй разработчик) сообщил о доступе к ДДУ-данным. Решается в понедельник 27.04.2026 — выбор между:
|
||||
- **Plan A (Analytics First)** — Stage 1 переезжает на «3 плитки» аналитики (что строить / по какой цене / за какое время), Generative и Site Finder сдвигаются;
|
||||
- **Original plan** — Stage 1 = Generative MVP, Stage 2 = Site Finder, аналитика как enrichment.
|
||||
|
||||
Зависит от 5 факторов канала ДДУ: легальность, поля, покрытие, свежесть, условия коммерческого использования. См. сущности `PlanA_AnalyticsFirst_Apr25` и `SalesTracking_Strategy_Apr25` в графе памяти.
|
||||
|
||||
**26.04 вечер — data discovery разблокировал Plan A независимо от Антона.** Найдены три легальных канала ground-truth-данных:
|
||||
1. **Росреестр CSV-датасет** (`DataSource_Rosreestr_DealsCSV_Apr26`) — per-deal цены сделок ДКП+ДДУ из ФГИС ЕГРН, 9 кварталов 2024–Q1 2026, ~898К сделок РФ/квартал. Открытые данные по 8-ФЗ, разделитель `~`, гранулярность до кадастрового квартала.
|
||||
2. **ДОМ.РФ фронт-API** (`DataSources_Analysis_Apr26` + 12 вкладок) — открытые JSON без auth (либо `Basic MTpqd2U=` хардкоден), per-flat прайс-лист, реализация/готовность как velocity.
|
||||
3. **НСПД MarketAnalytics** (`NSPD_MarketAnalytics_FullArchitecture_Apr26`) — индикаторы цен по кадастровым кварталам, 8 лет longitudinal, GeoJSON-полигоны для heatmap. Архитектура полностью раскрыта в debug-браузере (saveUserFilters → graphEgrn*/tableEgrn/geomDataForHeatMap, серверная сессия фильтров через userFiltersId). ⚠ `graphEgrnCandleMedian`/`LineMedian` (абсолютные ₽/м²) дают 403 для physic-аккаунта — но эти данные мы уже получаем из CSV-датасета Росреестра.
|
||||
|
||||
**27.04 — analytical layer полностью построен.** В прод-БД 43 `domrf_*/cbr_*/yandex_*/ekb_*/sv_*/rosreestr_*` таблицы (см. блок «Аналитический пласт данных» ниже). За день:
|
||||
- Загружено 5 lazy-load страниц ДОМ.РФ (`DomRF_Lazy_Loaded_Apr27_2026`) — `domrf_realization` 9.7K + stat_series XLSX 809K observations + 333 raw JSONB endpoints.
|
||||
- Догружена история 14 месяцев (Янв 2025 - Февр 2026) для realization + sold_ready через `repYear/repMonth` параметры (`DomRF_History_Loaded_Apr27_2026`) — `domrf_realization` итого **143 963** рядов, sold_ready_breakdown расширен до 7 chart-types с типизированными колонками.
|
||||
- Импортирован SQLite Антона (23 МБ) — 13 новых таблиц: `domrf_kn_flats` (27 563 поквартирно), `domrf_kn_objects` (1 516 ЖК), `cbr_mortgage_series` (3 308 ЦБ), `sber_ddf_metrics`, `yandex_realty_zk`, `ekb_districts`, `rosreestr_rent_deals`. Архив девелоперов смерджен (2 734 → 8 629). См. `Anton_Imported_Apr27_2026`.
|
||||
- Подключён MCP `crystaldba/postgres-mcp` (`.mcp.json`) — все DDL/SELECT теперь через `mcp__postgres__execute_sql` без `docker run psql` обёртки.
|
||||
- FK-аудит и cleanup: добавлены 4 missing FK, дропнуты 2 дублирующих индекса и 2 устаревшие таблицы (`sold_out_by_year`, `sold_out_by_progress` ⊂ `domrf_realization`), 8 `COMMENT ON TABLE` для документации источников.
|
||||
- Territory aliases для stat_series ↔ regions name mapping (`v_stat_series_resolved` view): 100% покрытие 809K observations через 101 alias.
|
||||
- Готовы 2 стратегических отчёта: `Sverdl_Quartirography_Report_Apr27` (общий по Свердл) + `PRINZIP_Strategy_Apr27` (что строить и куда смотреть).
|
||||
|
||||
**27.04 вечер — analytics dashboard + DOM.РФ scraper.** Поверх аналитического пласта собран интерактивный дашборд + регулярный парсер для пополнения данных:
|
||||
|
||||
- **Веб-аналитика** (`Analytics_Dashboard_Apr27`) — 3 страницы на ECharts/React Query: `/analytics` (рынок Свердл — KPI + market-pulse + парадокс «строят vs покупают» + pipeline + treemap районов ЕКБ), `/analytics/prinzip` (drill-down PRINZIP с benchmark-velocity, gap-bar к рынку, инсайты из стратегии), `/analytics/developers` (топ-15 + scatter velocity). Backend: `/api/v1/analytics` 11 endpoints поверх `v_developer_full_metrics`.
|
||||
- **DOM.РФ kn-API парсер** (`DomRf_Kn_Scraper_Apr27`) — Playwright in-context fetch (httpx бьётся об ServicePipe TLS-fingerprint, проверено), правильные endpoints `/сервисы/api/kn/object` (place=66, objStatus 0/1/2 = 442+2+1072 = **1516 ЖК Свердл, точно совпало с БД**) + `/portal-kn/api/sales/portal/table` (квартиры). Auth — `Basic MTpqd2U=` (`1:qwe`, не `jwe`!). Серверная фильтрация по `regions/developers` НЕ работает — фильтруем локально по `developer.companyGroup`.
|
||||
- **Extras-эндпойнты** (per-ЖК, того же sweep): `/sale_graph?type=apartments|parking` (time-series продаж), `/sales_agg` (текущие агрегаты 3 типов), `/infrastructure` (POI), `/construction/progress/photo` (мета+опц. binary). PRINZIP probe: 28 ЖК → 36 sale_graph + 6 sales_agg + 5 409 POI + 5 150 фото-метаданных.
|
||||
- **Расписание + ручной триггер** — Celery beat (cron из `SCRAPE_KN_CRON` env, jitter 0..30мин), `POST /api/v1/admin/scrape/kn` с `X-Admin-Token`, UI `/admin/scrape` (чекбоксы extras/photos + журнал ошибок с copy-URL для ручной проверки в браузере + раздел «Очередь и активные» через Celery inspect).
|
||||
- **Stealth** — Playwright fingerprint снят с локальной машины в `data/playwright_state.json` (26 cookies, ServicePipe NSC + Keycloak), закоммичен в git для server-reuse без cold-bootstrap.
|
||||
|
||||
### Запуск 442-sweep (все строящиеся ЖК Свердл)
|
||||
|
||||
После того как PRINZIP-probe прошёл (28 ЖК), масштабируем на все 442 строящихся:
|
||||
|
||||
**Через UI** (рекомендуется):
|
||||
1. Открыть https://gendsgn.ru/admin/scrape, ввести `SCRAPE_ADMIN_TOKEN`.
|
||||
2. Регион: 66, Девелоперы: пусто (все), чекбоксы: ✅ Квартиры, ✅ Extras, ❌ Скачать фото.
|
||||
3. «Запустить сейчас» → таск уйдёт в Redis-очередь, worker подхватит. Прогон ~60 минут.
|
||||
|
||||
**Через CLI на VPS** (если worker оффлайн):
|
||||
```bash
|
||||
ssh gendesign 'cd /opt/gendesign && \
|
||||
docker compose -f docker-compose.prod.yml run --rm worker \
|
||||
python data/sql/50_scrape_kn_flats.py --regions 66 \
|
||||
--load-state data/playwright_state.json -v'
|
||||
```
|
||||
|
||||
**Sanity-чеки после прогона:**
|
||||
```sql
|
||||
SELECT obj_status, COUNT(*) FROM domrf_kn_objects
|
||||
WHERE region_cd=66 AND snapshot_date=CURRENT_DATE GROUP BY 1;
|
||||
-- Ожидаем obj_status=0 (строящиеся): ~442
|
||||
|
||||
SELECT COUNT(DISTINCT obj_id) FROM domrf_kn_sale_graph
|
||||
WHERE snapshot_date=CURRENT_DATE;
|
||||
-- Ожидаем 100-200 ЖК с активными продажами
|
||||
|
||||
SELECT poi_category, COUNT(*) FROM domrf_kn_infrastructure
|
||||
WHERE snapshot_date=CURRENT_DATE GROUP BY 1 ORDER BY 2 DESC;
|
||||
-- Ожидаем ~85 000 POI (442 ЖК × ~190 POI на ЖК)
|
||||
```
|
||||
|
||||
**Возможные проблемы:**
|
||||
- WAF cookie expire — обновить `data/playwright_state.json` через `python data/sql/50_scrape_kn_flats.py --regions 66 --bootstrap-only --save-state data/playwright_state.json` локально, закоммитить, redeploy.
|
||||
- Rate-limit (429) — повысить `SCRAPE_KN_JITTER_SECONDS` в env или поправить `jitter_sleep(min_ms, max_ms)` в `stealth.py`.
|
||||
|
||||
---
|
||||
|
||||
## Аналитический пласт данных (data warehouse) — 27.04.2026
|
||||
|
||||
В прод-БД `gendesign` живёт **43 предметных таблицы** + 5 helper views + Rosreestr partitions. Размер БД ~2.1 ГБ.
|
||||
|
||||
### 🔌 Подключение
|
||||
|
||||
| Метод | Команда |
|
||||
| Слой | Статус |
|
||||
|---|---|
|
||||
| **MCP** (рекомендуется) | конфиг в `.mcp.json` (project root) — `mcp__postgres__execute_sql`, `analyze_db_health`, `list_objects`, `explain_query` и т.д. |
|
||||
| **psql via SSH-туннель** | `ssh -fN -L 15432:127.0.0.1:5432 gendesign` затем `PGPASSWORD=… psql -h 127.0.0.1 -p 15432 -U gendesign gendesign` |
|
||||
| **DBeaver / TablePlus** | localhost:15432 после открытия SSH-туннеля |
|
||||
| Аналитический калькулятор `/analytics/recommend` (Tier 3 per-bucket эластичность, кадастр vs рынок) | ✅ |
|
||||
| DOM.РФ kn-API скрейпер (442 строящихся ЖК Свердл, sale_graph + infra + photos) | ✅ Celery beat + admin UI |
|
||||
| Rosreestr CSV-сделки 2024Q1—2026Q1 (9 квартирных партиций, 7M строк) | ✅ |
|
||||
| NSPD кадастровые кварталы + здания (709 cad-кварталов, 10 590 cad_buildings ЕКБ) | ✅ Celery beat + admin UI |
|
||||
| OSM ekb_districts_geom (PostGIS-полигоны 8 районов ЕКБ) | ✅ |
|
||||
| Concept page (`/concept`) — Stage 1 Generative | 🟡 skeleton |
|
||||
| Site Finder (`/site-finder`) — Stage 2 | 🔴 заглушка |
|
||||
|
||||
⚠️ Туннель уже открыт на машинах команды (gendesign@46.173.16.127). `.mcp.json` в `.gitignore` (содержит пароль).
|
||||
|
||||
### 📊 Карта таблиц (43 предметных)
|
||||
|
||||
**ROSREESTR per-deal (1.1 ГБ)**
|
||||
| Таблица | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `rosreestr_deals` (+ 7 партиций по кварталам 2024Q3-2026Q1) | 4 975 398 | Сырые сделки ДКП+ДДУ из ФГИС ЕГРН, 89 регионов |
|
||||
| `rosreestr_deals_staging` | UNLOGGED | буфер для `\copy` из CSV |
|
||||
| `rosreestr_rent_deals` | 1 071 | Договоры аренды Свердл Q1 2026 (Антон) |
|
||||
|
||||
**ROSREESTR materialized views (305 МБ)**
|
||||
| MV | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `rr_agg_cad_quarter` | 952 799 | (region, cad_quarter, period, doc_type, area_bucket) → p10/median/p90 ₽/м² |
|
||||
| `rr_agg_settlement` | 422 852 | (region, settlement, period, doc_type, realestate_type) |
|
||||
| `rr_agg_region` | 3 549 | percentile-агрегаты по регионам |
|
||||
|
||||
**DOM.РФ справочники / dimensions**
|
||||
| Таблица | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `domrf_regions` | 90 | PK region_id (0=РФ + 89 субъектов). FK target для 4 таблиц |
|
||||
| `domrf_developers` | **8 629** | реестр + архив (после merge с Антоном). PK developer_id ('6208_0'=PRINZIP) |
|
||||
| `domrf_developers_full` | 2 563 | XLSX-выгрузка с метриками area/permits/flats/share |
|
||||
| `domrf_snapshots` | 18 | snapshot_date dimension (4 базовых + 14 history Янв 2025-Февр 2026) |
|
||||
| `domrf_federal_districts` | 8 | 8 ФО как first-class lookup |
|
||||
| `domrf_territory_aliases` | 101 | text→(level, region_id\|fo_id) для нормализации имён |
|
||||
| `domrf_stat_series_indicators` | 123 | XLSX sheet codes → indicator_name |
|
||||
|
||||
**DOM.РФ realization & sold_ready (history)**
|
||||
| Таблица | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `domrf_realization` | **143 963** | 4 endpoint_types (total/housing/ready_year/developer) × 89 регионов × 14 monthly snapshots |
|
||||
| `domrf_sold_ready_index` | 15 | РФ-итог helio метрики per snapshot |
|
||||
| `domrf_sold_ready_breakdown` | 40 044 | 7 chart_types × dim_value × 14 snapshots (regionChart/devChart/foChart/cityPopulationChart/classChart/devValueChart/readyYearChart) с типизированными колонками |
|
||||
| `domrf_sold_ready_dynamics` | 3 450 | timeseries 2020-period × 4 chart_types |
|
||||
|
||||
**DOM.РФ JSON dashboard**
|
||||
| Таблица | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `domrf_region_aggregates` | 439 | (region, room_count_type) → flat_count/area/percent |
|
||||
| `domrf_developer_aggregates` | 11 063 | (developer × region × room_count) |
|
||||
| `domrf_flat_area_distribution` | 40 | РФ-уровень бакеты площади |
|
||||
|
||||
**DOM.РФ XLSX/PDF снапшоты**
|
||||
| Таблица | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `domrf_developers_full` | 2 563 | реестр девелоперов из XLSX |
|
||||
| `domrf_escrow_banks` | 28 | банки эскроу (Сбер 61%) |
|
||||
| `domrf_guaranty_regions` | 99 | РФ + 8 ФО + 89 регионов разбивка |
|
||||
| `domrf_housing_summary` | 58 | разбивка по механизмам |
|
||||
| `domrf_planned_commissioning` | 10 | план ввода 2026-2035 |
|
||||
| `domrf_sold_out` | 9 | РФ + 8 топ-регионов (PDF, ради unique `attracted_funds_mln_rub`) |
|
||||
| `domrf_mortgage_rates` | 20 | ставки топ-20 банков (PDF) |
|
||||
|
||||
**DOM.РФ launch / commissioning**
|
||||
| Таблица | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `domrf_launch_top` | 1 410 | топ-N developer/region/area/fo по запускам/вводу |
|
||||
| `domrf_launch_obj_class` | 4 | Типовой/Комфорт/Бизнес/Элитный |
|
||||
| `domrf_launch_monthly` | 75 | timeseries 2020-2026 |
|
||||
| `domrf_commissioning` | 1 | факт ввод (multifamily/private) |
|
||||
| `domrf_project_finance` | 99 | ПР финансирование РФ/ФО/регионы |
|
||||
|
||||
**DOM.РФ stat_series (parsed XLSX 254 МБ)**
|
||||
| Таблица | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `domrf_stat_series_observations` | **809 236** | long-form panel: 99 территорий × 123 индикатора × ~70 месяцев (2020-2026) |
|
||||
| `domrf_xlsx_files` | 9 | BYTEA-архив исходных XLSX (8 МБ) |
|
||||
|
||||
**DOM.РФ ipoteka / mortgage**
|
||||
| Таблица | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `domrf_mortgage_dashboard` | 1 | агрегат рынка ипотеки РФ |
|
||||
| `domrf_mortgage_details` | 2 | RUB/OTH валюты |
|
||||
|
||||
**DOM.РФ /сервисы/api/kn/* (от Антона + наш scraper)**
|
||||
| Таблица | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `domrf_kn_flats` | 28 389+ | поквартирный прайс. Антоновский snapshot 2026-04-27 + наши новые snapshot'ы (UNIQUE по `(id, snapshot_date)`) |
|
||||
| `domrf_kn_objects` | 1 516+ | реестр ЖК Свердл (1285 ЕКБ + 28 PRINZIP) с GPS, классом, эскроу. Наш scraper подтвердил 442+2+1072=1516 для региона 66 |
|
||||
| `domrf_dev_growth` | 139 | динамика 2024→2025 девелоперов |
|
||||
| `domrf_kn_sale_graph` ⭐ | 36+ | time-series продаж per-ЖК (apartments/parking × месяц). PRINZIP Парк Победы продал 95 квартир в 2025 |
|
||||
| `domrf_kn_sales_agg` ⭐ | 6+ | текущие агрегаты per-ЖК (apartments/nonliv/parking × total/realised/perc). Парк Победы 145/291=49% |
|
||||
| `domrf_kn_infrastructure` ⭐ | 5 409 | нормализованная POI вокруг ЖК (Спорт, Продукты, Образование, Медицина, Развлечения) с lat/lon/dist_m |
|
||||
| `domrf_kn_photos` ⭐ | 5 150 | метаданные фото прогресса стройки (URL + period_dt + size_bytes), binary в `data/raw/domrf_photos/` опционально |
|
||||
| `kn_scrape_runs` | 3 | журнал прогонов парсера: regions, developers, objects_count, flats_count, requests_count, status |
|
||||
| `kn_scrape_failures` | 78 | per-request ошибки с full_url для ручной проверки в браузере (404 для сданных ЖК = ожидаемо) |
|
||||
|
||||
**PRINZIP CRM (analytics.prinzipevent.ru)**
|
||||
| Таблица | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `prinzip_leads` ⭐ | до 102 000 | заявки 8 лет. PII (phone/email) хешированы. Импорт через `data/sql/52_import_prinzip_crm.py --sqlite data/raw/prinzip_crm.sqlite` |
|
||||
| `prinzip_deals` ⭐ | до 9 000 | сделки 8 лет, sum revenue ≈ 39.9 млрд ₽. FK к `prinzip_leads` и soft-FK к `domrf_kn_objects` |
|
||||
| `prinzip_funnel_monthly` ⭐ | MV | воронка по месяцам × source (REFRESH после импорта) |
|
||||
| `prinzip_funnel_by_object` ⭐ | MV | conversion per ЖК: leads → deals → revenue |
|
||||
|
||||
**ЦБ РФ ипотека (от Антона)**
|
||||
| Таблица | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `cbr_mortgage_series` | 3 308 | 47 серий × {rf,sverdl} × Jan 2019 - Feb 2026 |
|
||||
| `cbr_mortgage_files` | 19 | XLSX-источники |
|
||||
|
||||
**Внешние источники**
|
||||
| Таблица | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `sber_ddf_metrics` | 20 | Сбер ДомКлик DDF nov2025: affordability, demand index, миграция, повторные ипотечники, price gap |
|
||||
| `yandex_realty_zk` | 12 | Yandex Realty ЖК ЕКБ snapshot |
|
||||
| `yandex_realty_class_prices` | 3 | разбивка по классам econom/comfort/elite |
|
||||
|
||||
**Geo Свердл/ЕКБ**
|
||||
| Таблица | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `ekb_districts` | 9 | 9 районов ЕКБ с zk_count/flat_count/area |
|
||||
| `ekb_metro` | 10 | станции метро ЕКБ |
|
||||
| `sv_cities` | 24 | не-ЕКБ города Свердл (Н.Тагил, Первоуральск, Арамиль) |
|
||||
| `sv_dev_sales` | 50 | ТОП-50 Свердл девелоперы Март 2026 |
|
||||
|
||||
**Universal raw**
|
||||
| Таблица | Рядов | Описание |
|
||||
|---|---:|---|
|
||||
| `domrf_raw_endpoints` | 349 | universal JSONB store: каждый /api/* response с GIN-index на payload |
|
||||
|
||||
### 🔍 Helper views (9 шт)
|
||||
|
||||
**Базовые (legacy):**
|
||||
| View | Описание |
|
||||
|---|---|
|
||||
| `v_market_pulse_rf` | one-row live-метрика РФ: 118.7M м² стройка / sold 30.91% / Цена 216 316 ₽/м² / ставка 19.9% vs 6.83% |
|
||||
| `v_developer_master` | полный профиль девелопера (legacy). PRINZIP sold% 48.4% — ЛИДЕР Свердл |
|
||||
| `v_region_master` | one-row-per-region: квартирография + sold_out + sold_ready + project_finance + guaranty + launch |
|
||||
| `v_rosreestr_market_by_region` | Rosreestr p10/median/p90 + DOM.РФ имена |
|
||||
| `v_stat_series_resolved` | stat_series_observations + alias + indicator_name (`WHERE region_id=66` → Свердл) |
|
||||
|
||||
**Связи квартиры↔ЖК↔застройщики (NEW 27.04 evening):**
|
||||
| View | Описание |
|
||||
|---|---|
|
||||
| `v_jk_with_developer` | ЖК Свердл + canonical dev_name + region_name (через FK `fk_kn_obj_developer/region`). 1 516 строк. |
|
||||
| `v_jk_sales_summary` | flats GROUP BY obj_name: total/sold/booked/free + sold_pct + avg_ppm + avg_area + total_value. Standalone-вид поквартирных снапшотов из `domrf_kn_flats`. |
|
||||
| `v_developer_portfolio` | Девелопер + все его корпуса/ЖК Свердл: count, flats_total, sqm + фильтры ekb/problem/escrow/completed/in_progress + earliest/latest ready_dt. |
|
||||
| `v_developer_full_metrics` | **⭐ ULTIMATE QUERY**: portfolio (ЖК) + квартирография (1к/2к/3к/4+) с процентами + realization Свердл sold%/цена. Лучший view для PRINZIP/Брусника/конкурентов сравнения. |
|
||||
|
||||
### 🔗 FK relationships
|
||||
|
||||
**35 FK constraints** (после relations v2):
|
||||
- `domrf_snapshots`: 24 child FKs (все таблицы со snapshot_date)
|
||||
- `domrf_regions`: 5 (developer_aggregates, flat_area_distribution, region_aggregates, realization, kn_objects⭐, rent_deals⭐, territory_aliases)
|
||||
- `domrf_developers`: 3 (developer_aggregates, **kn_objects** ⭐ NEW, **sv_dev_sales** ⭐ NEW)
|
||||
- `domrf_federal_districts`: 1 (territory_aliases)
|
||||
|
||||
**Цепочка анализа продаж:**
|
||||
```
|
||||
domrf_kn_flats (поквартирно) ─── obj_name ──→ domrf_kn_objects (ЖК)
|
||||
│ dev_id (FK)
|
||||
▼
|
||||
domrf_developers (справочник)
|
||||
│ developer_id (FK)
|
||||
▼
|
||||
domrf_realization (sold%/price 14 мес)
|
||||
│
|
||||
│ region_code=66 ─→ domrf_regions
|
||||
```
|
||||
|
||||
⚠ **Известное ограничение**: `domrf_kn_flats.obj_id` НЕ имеет FK на `domrf_kn_objects.obj_id` — два API endpoint используют разные ID-системы (27 563/27 563 orphans, текстовый match по `obj_name`/`comm_name` тоже даёт только 2 совпадения). Поквартирные снапшоты обрабатываются standalone через `v_jk_sales_summary`.
|
||||
|
||||
### 📜 ETL artefacts (data/sql/)
|
||||
|
||||
```
|
||||
01_schema_rosreestr_deals.sql partitioned table + staging
|
||||
02_load_all_quarters.sh \copy CSV (sep ~ и ;)
|
||||
03_materialized_views.sql 3 MV для аналитики
|
||||
|
||||
10_schema_domrf.sql domrf_regions/developers/snapshots/aggregates
|
||||
11_load_domrf.py JSON dashboard loader
|
||||
20_schema_domrf_extras.sql XLSX/PDF schemas
|
||||
21_load_domrf_extras.py XLSX/PDF loader
|
||||
|
||||
30_scrape_domrf.py Playwright headless Chromium с per-page direct API triggers
|
||||
31_schema_domrf_full.sql launch/sold_ready/etc normalized
|
||||
31_schema_domrf_raw.sql universal JSONB store
|
||||
32_load_domrf_full.py normalize scraper output
|
||||
32_load_domrf_raw.py JSONB raw store loader
|
||||
|
||||
33_schema_domrf_lazy.sql domrf_realization + xlsx_files (5 lazy pages)
|
||||
34_load_domrf_lazy.py realization/xlsx/raw loader
|
||||
35_schema_domrf_stats.sql stat_series_indicators + observations
|
||||
36_parse_xlsx_stats.py parse 7 XLSX → 809K observations
|
||||
37_cleanup_redundant.sql DROP sold_out_by_year/progress
|
||||
|
||||
38_relations_fix.sql +4 FK, миграция -1→0 RF, drop dup indexes, COMMENT'ы
|
||||
39_territory_aliases.sql federal_districts + territory_aliases + v_stat_series_resolved
|
||||
40_relations.sql оригинальные FK + 4 helper views (legacy)
|
||||
40_scrape_history.py 14-month history scraper для realization+sold_ready
|
||||
41_load_history.py loader с SNAPSHOT_DATE = end of period
|
||||
42_schema_breakdown_widen.sql drop JSONB extra → 6 typed dimension columns
|
||||
43_anton_import.sql 13 new tables for Anton's SQLite
|
||||
44_import_anton_db.py sqlite3 → postgres migration
|
||||
45_relations_v2.sql +4 FK (kn_obj→regions/developers, sv_dev_sales→developers, rent_deals→regions) + 4 analytical views (jk_with_developer, jk_sales_summary, developer_portfolio, developer_full_metrics)
|
||||
|
||||
50_schema_kn_extensions.sql UNIQUE (id, snapshot_date) для kn_flats/kn_objects + индексы (region_cd, snapshot_date) + kn_scrape_runs (журнал прогонов)
|
||||
50_scrape_kn_flats.py CLI scraper для /сервисы/api/kn/object + portal/table; флаги --regions/--developers/--no-extras/--download-photos/--probe URL/--save-state/--load-state
|
||||
51_schema_kn_extras.sql 4 extras-таблицы: sale_graph (time-series продаж), sales_agg (агрегаты), infrastructure (POI норм.), photos (мета+local_path) + kn_scrape_failures
|
||||
52_schema_prinzip_crm.sql prinzip_leads (102K) + prinzip_deals (9K) + 2 materialized views (funnel_monthly, funnel_by_object). PII-hashing
|
||||
52_import_prinzip_crm.py SQLite → Postgres importer. Use --inspect-only для discovery колонок, потом правь LEAD_FIELD_MAP/DEAL_FIELD_MAP
|
||||
```
|
||||
|
||||
Парсер живёт в `backend/app/services/scrapers/{stealth.py, domrf_kn.py}`, Celery-таск в `backend/app/workers/tasks/scrape_kn.py`, admin endpoint в `backend/app/api/v1/admin_scrape.py` (POST /kn + GET /runs + GET /failures + GET /queue + POST /revoke с auth `X-Admin-Token`). UI ручного запуска: `frontend/src/app/admin/scrape/page.tsx`. Per-объект drill-in: `/analytics/objects/[id]` (галерея фото + POI-таблица + sale-chart) + `PrinzipObjectsTable` с sparkline на `/analytics/prinzip`.
|
||||
|
||||
### 🎯 Аналитика — что делать с этими данными
|
||||
|
||||
**Готовые отчёты в графе памяти:**
|
||||
- `Sverdl_Quartirography_Report_Apr27` — полный отчёт по квартирографии Свердл (что строится, что продаётся, тренды 14 мес, топ-15 девелоперов)
|
||||
- `PRINZIP_Strategy_Apr27` — стратегические рекомендации для PRINZIP (что строить, куда смотреть, BENCHMARK с Брусникой/Форум-групп)
|
||||
|
||||
**Использование (паттерны):**
|
||||
```sql
|
||||
-- Свердл realization timeseries за год
|
||||
SELECT TO_CHAR(snapshot_date,'YYYY-MM') AS p, total_square, sold_perc, price_avg
|
||||
FROM domrf_realization
|
||||
WHERE region_code=66 AND endpoint_type='total' AND type_square='total'
|
||||
ORDER BY snapshot_date;
|
||||
|
||||
-- ЕКБ ДДУ-сделки по площадным сегментам
|
||||
SELECT
|
||||
CASE WHEN area<30 THEN 'studio' WHEN area<45 THEN '1k' ... END,
|
||||
COUNT(*), AVG(price_per_sqm)
|
||||
FROM rosreestr_deals
|
||||
WHERE region_code=66 AND city ILIKE '%катеринбург%'
|
||||
AND period_start_date >= '2025-04-01' AND doc_type='ДДУ';
|
||||
|
||||
-- Свердл stat_series (МКД 214-ФЗ, временной ряд) через resolved view
|
||||
SELECT obs_date, value
|
||||
FROM v_stat_series_resolved
|
||||
WHERE region_id=66 AND sheet_code='01_01_01';
|
||||
|
||||
-- Поквартирный прайс ЕКБ (Антон)
|
||||
SELECT obj_name, rooms, total_area, price_per_m2, status
|
||||
FROM domrf_kn_flats WHERE city='Город Екатеринбург' AND status='free';
|
||||
|
||||
-- Топ ЖК ЕКБ (Антон)
|
||||
SELECT comm_name, dev_name, flat_count, square_living, ready_dt
|
||||
FROM domrf_kn_objects WHERE is_ekb=true ORDER BY flat_count DESC;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Структура репозитория
|
||||
|
||||
```
|
||||
gendesign/
|
||||
├── backend/ # Python 3.12 / FastAPI / PostGIS
|
||||
│ ├── pyproject.toml # uv-проект, ruff/mypy/pytest конфиги
|
||||
│ ├── Dockerfile # с системными deps под Geo + WeasyPrint
|
||||
│ ├── .env.example
|
||||
│ ├── app/
|
||||
│ │ ├── main.py # FastAPI + Sentry + /health
|
||||
│ │ ├── core/ # config (Pydantic Settings), db (SQLAlchemy)
|
||||
│ │ ├── api/v1/ # concepts.py, parcels.py
|
||||
│ │ ├── schemas/ # Pydantic-контракты для openapi-typescript
|
||||
│ │ ├── services/
|
||||
│ │ │ ├── generative/ # Stage 1: geometry, teap, financial
|
||||
│ │ │ ├── site_finder/ # Stage 2: parser, scorer, filters
|
||||
│ │ │ └── exporters/ # Stage 1c: pdf, excel, dxf
|
||||
│ │ ├── models/parcel.py # GeoAlchemy2 + PostGIS
|
||||
│ │ └── workers/celery_app.py # Stage 2a: daily PKK sync
|
||||
│ └── tests/
|
||||
├── frontend/ # Next.js 15 / React 19 / TypeScript / Leaflet
|
||||
│ ├── package.json # включая `npm run codegen` → openapi-typescript
|
||||
│ ├── Dockerfile # multi-stage, output: standalone
|
||||
│ ├── public/ # static assets (gitkeep пока пусто)
|
||||
│ └── src/app/
|
||||
│ ├── page.tsx # / — главная
|
||||
│ ├── concept/page.tsx # /concept — Generative Design
|
||||
│ └── site-finder/page.tsx # /site-finder — Site Finder
|
||||
├── memory/
|
||||
│ └── memory-gendesign.jsonl # хард-линк на C:\mcp\.aim — общий граф памяти
|
||||
├── docs/ # стратегические документы (бизнес-план, цели MVP)
|
||||
├── ops/ # эксплуатация
|
||||
│ ├── backup.sh # pg_dump cron + опциональный S3 upload
|
||||
│ └── restore.sh # восстановление из дампа (RESTORE_CONFIRM=yes)
|
||||
├── backend/
|
||||
│ ├── alembic.ini, alembic/ # миграции БД (versions/ пуст до Stage 2a)
|
||||
│ └── db/init/ # init-скрипты, монтятся в /docker-entrypoint-initdb.d
|
||||
│ └── 99_drop_unused_extensions.sql # дроп TIGER/topology/fuzzystrmatch на свежем volume
|
||||
├── .env.example # шаблон prod env (POSTGRES_*, IMAGE_TAG)
|
||||
├── docker-compose.yml # dev: Postgres + Redis + backend + frontend
|
||||
├── docker-compose.prod.yml # prod: образы из GHCR + Caddy + Postgres+Redis
|
||||
├── Caddyfile # авто-TLS Let's Encrypt для gendsgn.ru
|
||||
├── Makefile # make up / migrate / migration / test / codegen / pre-commit-install
|
||||
├── .pre-commit-config.yaml # ruff + prettier + housekeeping hooks
|
||||
└── .github/workflows/
|
||||
├── ci.yml # ruff + mypy(core) + pytest + frontend build
|
||||
└── deploy.yml # build → push в GHCR → ssh: git fetch + reset --hard + caddy reload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Технический стек (v2.2)
|
||||
|
||||
**Backend:** Python 3.12, FastAPI, SQLAlchemy 2.0, GeoAlchemy2, Pydantic v2, Alembic, Celery + Redis, httpx
|
||||
**Geo:** Shapely 2.0, GeoPandas, Pyproj (WGS84 ↔ МСК-66 для Свердловской обл.)
|
||||
**ML:** scikit-learn (XGBoost — отложен на Фазу 1, в Discovery эвристики)
|
||||
**БД:** PostgreSQL 16 + PostGIS 3.4, Redis 7
|
||||
**Frontend:** Next.js 15, React 19, TypeScript 5, Tailwind 4, TanStack Query, Leaflet (Mapbox в Stage 2b), react-leaflet 5
|
||||
**Экспорт:** WeasyPrint (PDF), ezdxf (DXF), openpyxl (Excel)
|
||||
**Dev:** uv (lockfile зафиксирован), ruff (lint+format), mypy (strict только на core), pytest, pre-commit (hooks: ruff, prettier, detect-private-key)
|
||||
|
||||
---
|
||||
|
||||
## Команда и порядок работы
|
||||
|
||||
**2 фуллстек-разработчика, оба part-time 20 ч/нед** — итого ~40 person-hours/нед, эффективных с координацией ~33.
|
||||
|
||||
**Ритм:**
|
||||
- 1 синк/нед (30 мин, понедельник)
|
||||
- daily-async в Telegram
|
||||
- PR review обязательный
|
||||
- Trunk-based: feature ветки ≤ 3 дней
|
||||
- Codegen TS-типов из Pydantic (`npm run codegen`) — без этого 2 фуллстека плодят расхождения в API-контракте
|
||||
|
||||
---
|
||||
|
||||
## Этапы MVP (12 недель, базовый план)
|
||||
|
||||
### Stage 0 — Скелет (нед. 1) ✅ выполнено
|
||||
Монорепо backend+frontend, Docker с PostGIS+Redis, FastAPI hello, Next.js hello, openapi-typescript codegen, GitHub Actions CI, ruff + mypy(core) + pytest. Без JWT (single-tenant + `X-Demo-User` до Раунда А). Production VPS поднят, GHCR-деплой работает.
|
||||
|
||||
### Stage 1 — Generative MVP (нед. 2–7) ⏸ pending решения по Plan A
|
||||
- **1a (нед. 2–3):** геометрия + API-контракт. Shapely-сервис (отступы, сетка), Pydantic-схемы, `POST /api/v1/concepts` с заглушкой. Фронт: страница `/concept`, Leaflet, импорт GeoJSON, форма параметров, 3 варианта в табах.
|
||||
- **1b (нед. 4–5):** алгоритм размещения. Жадное заполнение, 3 стратегии (max площадь / max инсоляция / balanced), STRtree для коллизий. Цель ≤10 сек, fallback 15 сек.
|
||||
- **1c (нед. 6–7):** ТЭП + финмодель + экспорты. WeasyPrint PDF, ezdxf, openpyxl. UI кнопок скачивания.
|
||||
|
||||
**Acceptance Stage 1 (конец нед. 7):** полигон → 3 варианта → ТЭП + финмодель → PDF/Excel/DXF.
|
||||
|
||||
### Stage 2 — Site Finder MVP (нед. 8–12)
|
||||
- **2a (нед. 8–9):** data pipeline. Парсер ПКК Rosreestr (httpx async, retries), модель `Parcel` с GeoAlchemy2, ≥1000 участков Екатеринбурга. Overpass API для POI, ДОМ.РФ/ЦИАН для цен м², кеш в Redis. Опционально: воркер `sales_tracker.py` — ежесуточный мониторинг шахматок застройщиков (Velocity Score).
|
||||
- **2b (нед. 10–11):** скоринг + API + UI. 3 критерия (расстояние до центра, плотность POI в 1 км, цена района), `POST /api/v1/parcels/search`. Карта с градацией по рейтингу, фильтры, карточка.
|
||||
- **2c (нед. 12):** интеграция + полировка. Кнопка «Обсчитать концепцию», демо-наполнение 50 участков, бэкапы БД, подача в реестр ПО.
|
||||
|
||||
### Альтернативный план A (Analytics First)
|
||||
|
||||
Если Антон в понедельник подтвердит легальный канал ДДУ-данных — Stage 1 переезжает на **«3 плитки» аналитики** (что строить / по какой цене / за какое время), Generative переходит на Stage 2 со Smart Defaults на основе аналитики, Site Finder — на Stage 3 с Velocity Score из ДДУ. См. `PlanA_AnalyticsFirst_Apr25` в графе.
|
||||
|
||||
### Что урезано из v2.2 из-за part-time × 2
|
||||
- XGBoost-скоринг → Фаза 1 (с реальными данными от пилотов)
|
||||
- JWT-auth с refresh → перед Раундом А
|
||||
- mypy --strict — только на core (`generative/*`, `site_finder/scorer.py`)
|
||||
- Время генерации ≤ 15 сек как fallback (а не 10 сек)
|
||||
**Подробности и текущий backlog** — [Issues](https://github.com/lekss361/-gendesign/issues), milestones по этапам.
|
||||
|
||||
---
|
||||
|
||||
## Quick start (локально)
|
||||
|
||||
**Требования:** Docker Desktop, Node.js 20+, [uv](https://docs.astral.sh/uv/) (для backend dev вне Docker).
|
||||
**Требования:** Docker Desktop, Node.js 20+, [uv](https://docs.astral.sh/uv/) для backend dev.
|
||||
|
||||
```bash
|
||||
# 1. Скопировать env-файлы
|
||||
cp backend/.env.example backend/.env
|
||||
cp frontend/.env.example frontend/.env
|
||||
cp .env.example .env # опционально, для prod compose
|
||||
|
||||
# 2. Запустить всё
|
||||
docker compose up -d --build
|
||||
# или: make up
|
||||
|
||||
# 3. Проверить
|
||||
curl http://localhost:8000/health
|
||||
# → {"status":"ok","environment":"dev"}
|
||||
|
||||
# Frontend → http://localhost:3000
|
||||
# Swagger docs → http://localhost:8000/docs
|
||||
curl http://localhost:8000/health # → {"status":"ok","environment":"dev"}
|
||||
# Frontend: http://localhost:3000
|
||||
# Swagger: http://localhost:8000/docs
|
||||
```
|
||||
|
||||
**Полезное:**
|
||||
```bash
|
||||
make logs # tail логов всех сервисов
|
||||
make backend-shell # bash внутри backend контейнера
|
||||
make migrate # alembic upgrade head (когда добавим миграции)
|
||||
make test # pytest
|
||||
make lint # ruff check
|
||||
make typecheck # mypy на core
|
||||
make codegen # обновить TS-типы из OpenAPI
|
||||
Полезные `make`-таргеты: `logs`, `backend-shell`, `migrate`, `test`, `lint`, `typecheck`, `codegen` (TS-типы из OpenAPI).
|
||||
|
||||
---
|
||||
|
||||
## Архитектура
|
||||
|
||||
**Backend:** Python 3.12, FastAPI, SQLAlchemy 2.0, GeoAlchemy2, Pydantic v2, Celery + Redis, httpx, Shapely 2, scikit-learn.
|
||||
**БД:** PostgreSQL 16 + PostGIS 3.4 (партиционированные `rosreestr_deals`, GIST на geom-полях, ~50 предметных таблиц).
|
||||
**Frontend:** Next.js 15, React 19, TypeScript 5, Tailwind 4, TanStack Query, Leaflet/Mapbox, ECharts.
|
||||
**Экспорт:** WeasyPrint (PDF), ezdxf (DXF), openpyxl (Excel).
|
||||
**Dev:** uv lockfile, ruff, mypy strict (на `core` + `generative`), pytest, pre-commit hooks.
|
||||
|
||||
Структура монорепо:
|
||||
```
|
||||
backend/ FastAPI app, services (analytics_queries, scrapers, generative, site_finder), workers (Celery)
|
||||
frontend/ Next.js app router (analytics, admin, concept, site-finder)
|
||||
data/sql/ ETL артефакты — миграции схемы, загрузчики rosreestr/anton/nspd
|
||||
ops/ backup.sh + deploy скрипты
|
||||
docs/ стратегия (снимок) — актуальное состояние в графе памяти
|
||||
memory/ hardlink на C:\mcp\.aim\memory-gendesign.jsonl (knowledge graph)
|
||||
.github/workflows/ deploy.yml (build → GHCR → SSH-deploy)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Production хостинг — Beget VPS
|
||||
## Аналитика — что доступно
|
||||
|
||||
**Текущая live-конфигурация:** Beget Cloud, Москва, Standard 3+ GHz, 2 vCPU / 4 GB / 40 GB NVMe.
|
||||
В прод-БД ~50 предметных таблиц:
|
||||
|
||||
**Стоимость:** ~33 ₽/день сервер + ~5 ₽/день IPv4 = **~1140 ₽/мес**. Плюс домен .ru (~17 ₽/мес).
|
||||
- **`domrf_kn_*`** — DOM.РФ объекты, квартиры, sale_graph, infrastructure, photos (1500+ ЖК Свердл).
|
||||
- **`rosreestr_deals`** — партиционированные ДДУ/ДКП-сделки 2024Q1—2026Q1, ~7M строк, 27 мес истории.
|
||||
- **`cad_quarters_geom` + `cad_buildings`** — NSPD кадастровые кварталы и здания (cost_value, year_built, floors, geom).
|
||||
- **`ekb_districts_geom`** — PostGIS-полигоны OSM для spatial-join.
|
||||
- **`cbr_mortgage_series`, `yandex_realty_zk`, `domrf_realization`, `sber_ddf_metrics`** — макро/cross-validation.
|
||||
|
||||
**Что крутится на VM** (`/opt/gendesign`):
|
||||
- 5 контейнеров через `docker-compose.prod.yml`: postgres, redis, backend, frontend, caddy
|
||||
- Backend и frontend образы пуллятся из приватного GHCR
|
||||
- Persistent volumes: `gendesign_postgres_data`, `gendesign_redis_data`, `gendesign_caddy_*`
|
||||
- Backend и frontend биндятся на `127.0.0.1:8000` и `127.0.0.1:3000` — наружу выходит только Caddy на `:80`/`:443`
|
||||
**Калькулятор `/analytics/recommend`** считает квартирографию (5 size-бакетов) с per-bucket эластичностью через регрессию sale_graph, нормировкой на район/класс/конкурентов, кадастр-vs-рынок cross-check, и инверсным режимом «требуемая цена для срока X мес».
|
||||
|
||||
**Доступ:** SSH-only по ключу к non-root пользователю `gendesign`. Root login отключён, password auth отключён, fail2ban Beget активен.
|
||||
|
||||
### Триггеры миграции на Yandex Cloud
|
||||
|
||||
Переезжаем на YC Managed PostgreSQL + Compute + Container Registry, когда:
|
||||
- Подписан 3-й платящий пилот (~ конец мес. 3)
|
||||
- Закрылся Раунд А
|
||||
- Идёт переговор с госзаказчиком (нужен реестр ПО + солидная инфра)
|
||||
- БД > 10 GB или появилось > 50 RPS
|
||||
|
||||
Стоимость production на YC: ~10–15 тыс ₽/мес.
|
||||
|
||||
### Что осталось до «infra-ready»
|
||||
|
||||
- [x] ~~Купить домен~~ — `gendsgn.ru` зарегистрирован в Beget 26.04.2026 (199₽/420₽). DNS на NS Beget, A-записи прописаны
|
||||
- [x] ~~ЕСИА-верификация админа домена~~ — пройдено 26.04, статус `REGISTERED, DELEGATED, VERIFIED`
|
||||
- [x] ~~Caddy TLS на `gendsgn.ru`~~ — Let's Encrypt cert получен, HTTPS живёт
|
||||
- [x] ~~Sentry DSN~~ — проект `gendesign-backend` создан в org `qqq`, DSN в `/opt/gendesign/backend/.env`, тестовое событие прошло
|
||||
- [x] ~~GitHub Issues / Milestones / Labels~~ — backlog засеян (см. [Issues](https://github.com/lekss361/-gendesign/issues))
|
||||
- [ ] Настроить ежесуточный `pg_dump` → Object Storage. Скрипт `ops/backup.sh` готов, осталось создать Selectel/Yandex bucket и положить креды в `/etc/default/gendesign-backup` на VM ([#3](https://github.com/lekss361/-gendesign/issues/3))
|
||||
- [ ] Прокинуть `SENTRY_RELEASE` в deploy.yml для tracking релизов в Sentry ([#4](https://github.com/lekss361/-gendesign/issues/4))
|
||||
- [ ] Подключить Антона к knowledge-graph ([#11](https://github.com/lekss361/-gendesign/issues/11))
|
||||
- [ ] При следующем wipe `down -v` — TIGER/topology/fuzzystrmatch автоматом дропнутся (init-скрипт `backend/db/init/99_drop_unused_extensions.sql`)
|
||||
|
||||
### Sensitive операционные грабли
|
||||
|
||||
- **`docker compose restart` НЕ перечитывает `env_file:`** — после правки `backend/.env` всегда `docker compose -f docker-compose.prod.yml up -d --force-recreate --no-deps backend`
|
||||
- **`deploy.yml` использует `git reset --hard origin/main`** — любые ручные правки в `/opt/gendesign` будут стёрты при следующем деплое. `.env` файлы переживут (в `.gitignore`)
|
||||
- **`docker compose up` без `-f docker-compose.prod.yml`** на проде поднимет dev compose (с локальной сборкой образов и без Caddy). Признаки: `Image ... Built` в выводе и WARN про orphan caddy. Всегда указывай `-f`
|
||||
- **`caddy reload` не сбрасывает retry-queue.** Если Caddy пытался получить cert для несуществующего hostname (например, плейсхолдера из старого Caddyfile), фоновые попытки могут переключить его в Let's Encrypt staging fallback. Рецепт чистого старта: `docker volume rm gendesign_caddy_data && docker compose up -d caddy`
|
||||
Подробное описание схемы БД, helper-views, FK и 27.04 history — в графе памяти, сущности `Analytics_Dashboard_Apr27`, `DomRF_Lazy_Loaded_Apr27_2026`, `Anton_Imported_Apr27_2026`, `NSPD_Scrape_Complete_Apr30`. См. также `data/sql/*.sql` (DDL артефакты).
|
||||
|
||||
---
|
||||
|
||||
## GitHub Actions deploy
|
||||
## Production хостинг
|
||||
|
||||
Workflow `.github/workflows/deploy.yml`:
|
||||
1. **Build** обоих Docker-образов (backend ~2.5 мин с нуля, frontend ~1 мин с нуля; повторно из кеша — секунды)
|
||||
2. **Push** в GHCR (`ghcr.io/lekss361/gendesign-backend`, `gendesign-frontend`) с тегами `latest` и `<sha7>`
|
||||
3. **SSH deploy:** `docker compose -f docker-compose.prod.yml pull && up -d`, потом `curl /health`
|
||||
**Beget Cloud, Москва, 2 vCPU / 4 GB / 40 GB NVMe.** Стоимость ~1 140 ₽/мес сервер + ~17 ₽/мес домен.
|
||||
|
||||
**GitHub Secrets (Settings → Secrets and variables → Actions):**
|
||||
5 контейнеров через `docker-compose.prod.yml`: `postgres` + `redis` + `backend` (FastAPI) + `worker` + `beat` (Celery, Chromium только в worker) + `frontend` + `caddy` (auto-TLS Let's Encrypt). Backend/frontend bind на `127.0.0.1` — наружу только Caddy.
|
||||
|
||||
| Имя | Значение |
|
||||
|---|---|
|
||||
| `DEPLOY_HOST` | `46.173.16.127` |
|
||||
| `DEPLOY_USER` | `gendesign` |
|
||||
| `DEPLOY_SSH_KEY` | приватный ключ `~/.ssh/gendesign_deploy` целиком |
|
||||
| `DEPLOY_PORT` | `22` (или другой, если поменяли) |
|
||||
**SSH-only по ключу к non-root пользователю `gendesign`**, fail2ban активен. UFW: 22/80/443.
|
||||
|
||||
`GITHUB_TOKEN` встроенный — отдельный secret для push в GHCR не нужен.
|
||||
**GitHub Actions deploy** ([`.github/workflows/deploy.yml`](.github/workflows/deploy.yml)):
|
||||
1. Build обоих образов (backend lean + worker-with-chromium + frontend)
|
||||
2. Push в приватный GHCR
|
||||
3. SSH `git pull`, пишет `SENTRY_RELEASE=$IMAGE_TAG` в `backend/.env.runtime`, `compose pull && up -d`, `caddy reload`, `curl /health`
|
||||
|
||||
**Авторизация сервера в GHCR (приватные пакеты):**
|
||||
**GitHub Secrets:** `DEPLOY_HOST`, `DEPLOY_USER`, `DEPLOY_SSH_KEY`, `DEPLOY_PORT`. Сервер авторизуется в GHCR однократно через PAT с `read:packages`.
|
||||
|
||||
Сервер авторизуется однократно через PAT с `read:packages`:
|
||||
**Триггеры миграции на Yandex Cloud:** 3-й платящий пилот / Раунд А / госзаказ (нужен реестр ПО) / БД >10 GB / >50 RPS. На YC ~10–15 тыс ₽/мес.
|
||||
|
||||
```bash
|
||||
# На сервере, как пользователь gendesign
|
||||
read -s GHCR_PAT && echo "$GHCR_PAT" | docker login ghcr.io -u lekss361 --password-stdin && unset GHCR_PAT
|
||||
```
|
||||
### Операционные грабли
|
||||
|
||||
Auth сохраняется в `~/.docker/config.json` и переживает рестарт. Ротировать PAT раз в год.
|
||||
- `docker compose restart` НЕ перечитывает `env_file:` — после правки `.env` всегда `up -d --force-recreate --no-deps backend`.
|
||||
- `deploy.yml` использует `git reset --hard origin/main` — ручные правки в `/opt/gendesign` будут стёрты при следующем деплое (`.env`-файлы переживут — в `.gitignore`).
|
||||
- На проде всегда указывать `-f docker-compose.prod.yml` (без него поднимет dev compose без Caddy).
|
||||
- `caddy reload` не сбрасывает retry-queue для ACME. При смене hostname с битым cert-state: `docker volume rm gendesign_caddy_data && up -d caddy`.
|
||||
|
||||
---
|
||||
|
||||
## Память проекта (knowledge graph)
|
||||
## Roadmap (12 недель)
|
||||
|
||||
Все решения, факты, риски, KPI, протоколы валидации хранятся **только в графе памяти**, не в .md-документах. Это решает проблему расхождения версий между документами.
|
||||
| Stage | Сроки | Что | Acceptance |
|
||||
|---|---|---|---|
|
||||
| **0** ✅ | нед. 1 | Скелет монорепо, CI, прод | curl /health = ok |
|
||||
| **Sprint 1** ✅ | конец апр 2026 | NSPD-скрейпер + per-bucket эластичность + кадастр cross-check + Sentry release tracking | recommend_mix Tier 3 в проде |
|
||||
| **1a** | нед. 2–3 | Shapely-сервис, отступы, API-контракт `/concept` ([#5](https://github.com/lekss361/-gendesign/issues/5)) | GeoJSON → 3 заглушки |
|
||||
| **1b** | нед. 4–5 | Greedy-алгоритм, 3 стратегии, STRtree ([#12](https://github.com/lekss361/-gendesign/issues/12)) | 3 варианта ≤15с на 1 га |
|
||||
| **1c** | нед. 6–7 | ТЭП + финмодель + PDF/Excel/DXF ([#6](https://github.com/lekss361/-gendesign/issues/6)) | Продаваемый артефакт |
|
||||
| **2a** | нед. 8–9 | ПКК-парсер + sales_tracker ([#7](https://github.com/lekss361/-gendesign/issues/7)) | ≥1000 участков ЕКБ |
|
||||
| **2b** | нед. 10–11 | Скоринг + API + UI Site Finder ([#8](https://github.com/lekss361/-gendesign/issues/8)) | Карта с цветовой градацией |
|
||||
| **2c** | нед. 12 | Site Finder ↔ Generative + реестр ПО ([#9](https://github.com/lekss361/-gendesign/issues/9)) | 3 платящих пилота |
|
||||
|
||||
**Как устроено:**
|
||||
- MCP-сервер `mcp-knowledge-graph` пишет в `C:\mcp\.aim\memory-gendesign.jsonl`
|
||||
- Этот файл — **хард-линк** на `<repo>/memory/memory-gendesign.jsonl`
|
||||
- Изменения попадают в git автоматически
|
||||
- Синхронизация между коллегами — через `git pull` / `git push`
|
||||
|
||||
**Правило проекта (`Feedback_MemoryFirst`):**
|
||||
- Все факты и решения сохраняем в граф (`context: "gendesign"`)
|
||||
- `.md` / `.docx` / `.pdf` Claude **не создаёт сам** — только по явному запросу
|
||||
- Источник правды = граф, документы = производные срезы
|
||||
|
||||
**Рабочий цикл:**
|
||||
```bash
|
||||
git pull # перед началом работы
|
||||
# ... сессия с Claude ...
|
||||
git add memory/memory-gendesign.jsonl
|
||||
git commit -m "Memory: <что добавили>"
|
||||
git push
|
||||
```
|
||||
|
||||
Конфликты в `.jsonl` обычно простые (файл построчный): `git pull --rebase`, оставить **обе** стороны, `git rebase --continue`.
|
||||
|
||||
### Подключение коллеги
|
||||
|
||||
Краткая инструкция для нового участника команды:
|
||||
|
||||
1. Установить Claude Desktop, Node.js 20+, Git
|
||||
2. Клонировать репо в `C:\Users\<имя>\source\repos\gendesign`
|
||||
3. `mkdir C:\mcp\.aim`
|
||||
4. Создать хард-линк:
|
||||
```powershell
|
||||
New-Item -ItemType HardLink `
|
||||
-Path C:\mcp\.aim\memory-gendesign.jsonl `
|
||||
-Value $PWD\memory\memory-gendesign.jsonl
|
||||
```
|
||||
5. Добавить в `%APPDATA%\Claude\claude_desktop_config.json`:
|
||||
```json
|
||||
"knowledge-graph": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "mcp-knowledge-graph", "--memory-path", "C:\\mcp\\.aim"]
|
||||
}
|
||||
```
|
||||
6. Перезапустить Claude Desktop полностью (через трей)
|
||||
7. Проверить: «Покажи память контекста gendesign»
|
||||
|
||||
Полная версия процедуры — в графе, сущность `Onboarding_Colleague_KG`.
|
||||
Аналитический backlog: [#17](https://github.com/lekss361/-gendesign/issues/17) velocity-anomaly, [#18](https://github.com/lekss361/-gendesign/issues/18) ekb_districts median refresh, [#19](https://github.com/lekss361/-gendesign/issues/19) competitor-360, [#20](https://github.com/lekss361/-gendesign/issues/20) mortgage-rate sensitivity, [#21](https://github.com/lekss361/-gendesign/issues/21) cad_buildings filters в Site Finder.
|
||||
|
||||
---
|
||||
|
||||
## Документы стратегии
|
||||
## Команда и память проекта
|
||||
|
||||
В `docs/`:
|
||||
- `MVP_Goals_Python.md` — цели Discovery MVP, Python-стек (v2.2, апрель 2026)
|
||||
- `Бизнес-план v2.1 - Site Finder + Roadmap 12 месяцев.md` — бизнес-план
|
||||
**2 фуллстек-разработчика, part-time 20 ч/нед**, ритм: 1 синк/нед, daily-async в Telegram, PR-review обязательный, trunk-based. Codegen TS-типов из Pydantic — `npm run codegen`.
|
||||
|
||||
⚠️ Эти документы — **снимок состояния на момент написания**. Свежее состояние стратегии — в графе памяти. При расхождении доверяем графу.
|
||||
Все решения, факты, KPI, протоколы валидации хранятся в **графе памяти** (`memory/memory-gendesign.jsonl`, hardlink на `C:\mcp\.aim\memory-gendesign.jsonl`), не в .md-документах. Правило (`Feedback_MemoryFirst`): источник правды = граф, документы = снимки. Синхронизация между коллегами — через git pull/push.
|
||||
|
||||
Релевантные сущности графа на 27.04.2026:
|
||||
|
||||
**Аналитика и отчёты (27.04):**
|
||||
- `DomRF_Lazy_Loaded_Apr27_2026` ⭐ — итог дня: 5 lazy-load страниц + stat_series + cleanup + territory aliases + MCP
|
||||
- `DomRF_History_Loaded_Apr27_2026` ⭐ — 14 месяцев истории realization+sold_ready (143K рядов realization)
|
||||
- `Anton_Imported_Apr27_2026` ⭐ — 13 таблиц от Антона + merge developers (8 629 итого)
|
||||
- `Sverdl_Quartirography_Report_Apr27` 📊 — полный отчёт по Свердл квартирографии
|
||||
- `PRINZIP_Strategy_Apr27` 📊 — стратегия PRINZIP: что строить и куда смотреть
|
||||
- `Analytics_Dashboard_Apr27` ⭐ — 3 страницы /analytics с ECharts (Свердл рынок, PRINZIP drill-down, девелоперы)
|
||||
- `DomRf_Kn_Scraper_Apr27` ⭐ — парсер DOM.РФ kn-API + 4 extras-эндпойнта (sale_graph, sales_agg, infrastructure, photos), Playwright in-context fetch, Celery beat cron + admin trigger UI
|
||||
|
||||
Сущности 26.04.2026:
|
||||
|
||||
**Стратегия / план:**
|
||||
- `Project_GenDesign` — общая шапка
|
||||
- `TechStack_v2.2` — Python + Next.js стек
|
||||
- `NorthStar_DiscoveryMVP` — 3 пилота + реестр ПО → Раунд А
|
||||
- `Stages_MVP_v1` — базовая разбивка по этапам
|
||||
- `Team_Setup` — 2 фуллстека part-time
|
||||
- `WorkSplit_Generative_Stage1` — кто что делает в Generative
|
||||
- `PlanA_AnalyticsFirst_Apr25` — pending pivot решение (ждём 27.04 — Антон по ДДУ)
|
||||
- `SalesTracking_Strategy_Apr25` — шахматки застройщиков (downgrade-нуты после находок 26.04)
|
||||
- `DataSources_Pricing_Apr25` — источники цен и площадей (общий обзор)
|
||||
- `DataSources_Analysis_Apr26` — глубокий анализ ДОМ.РФ как ground truth (заменяет ожидание ДДУ-канала)
|
||||
- `Validation_Findings_Apr25` — валидация бизнес-плана
|
||||
|
||||
**Источники данных (обновлено 26.04 вечер):**
|
||||
- `DataSource_Rosreestr_DealsCSV_Apr26` ⭐ — per-deal цены сделок ДКП+ДДУ из ФГИС ЕГРН, 9 кварталов 2024–Q1 2026, ~898К сделок РФ/квартал
|
||||
- `DataSource_Rosreestr_DealsCSV_Schema_Apr26` — схема CSV (разделитель `~`, 17 полей)
|
||||
- `DataSource_Rosreestr_DealsCSV_Sverdl_Q1_2026` — реальные цифры по Свердл.обл (ДКП median 82,594 ₽/м², ДДУ median 156,314 ₽/м²)
|
||||
- `DataSource_Rosreestr_DealsCSV_Limits_Apr26` — ограничения (нет покупателя, period=начало квартала)
|
||||
- `DataSource_Rosreestr_ETL_Plan_Apr26` — план интеграции в Stage 2a
|
||||
- `DataSource_PerDeal_Reality_Apr26` — иерархия источников (1. Росреестр CSV → 2. ДОМ.РФ → 3. ЦБ ипотека → 4. Fedstat → 5. УПН Екб)
|
||||
- `DataSource_UPN_Ekb_Apr26` — Уральская Палата Недвижимости (19 лет longitudinal по Екб)
|
||||
|
||||
**ДОМ.РФ фронт-API (по вкладкам):**
|
||||
- `DomRF_Architecture_Apr26` · `DomRF_Host_Apr26` · `DomRF_API_PortalAnalytics_Apr26` · `DomRF_KeyAndLegal_Apr26` (ключ Basic MTpqd2U=)
|
||||
- `DomRF_PlanA_Mapping_Apr26` — маппинг endpoints на 3 плитки Plan A
|
||||
- 12 вкладок аналитики: `DomRF_Tab1_HousingIndicators_Apr26` ... `DomRF_Tab11_MortgageStats_Apr26`
|
||||
- `DomRF_Spec_APF_v22` — официальный B2B API (только для банков)
|
||||
- `DomRF_OfficialVsFront_Comparison_Apr26` — почему фронт незаменим даже с банк-партнёром
|
||||
|
||||
**НСПД (nspd.gov.ru, новый портал Росреестра):**
|
||||
- `Rosreestr_SiteMap_Apr26` — карта ресурсов Росреестра/НСПД
|
||||
- `NSPD_Architecture_Apr26` — React SPA, micro-frontends, OAuth через ESIA
|
||||
- `NSPD_OAuth_Architecture_Apr26` — auth flow, JWT TTL=30 мин
|
||||
- `NSPD_PhysicAccess_Apr26` — что доступно физик-аккаунту (18 сервисов, marketAnalytics ✓)
|
||||
- `NSPD_MarketAnalytics_FullArchitecture_Apr26` ⭐ — полный API-флоу разобран в debug-браузере 26.04: saveUserFilters → graphEgrn*, tableEgrn, geomDataForHeatMap. Серверная сессия фильтров (userFiltersId)
|
||||
- `NSPD_OrgOnly_Endpoints_Apr26` — graphEgrnCandleMedian/LineMedian возвращают 403 (абсолютные ₽/м² скрыты от physic, дублируют CSV-датасет)
|
||||
- `NSPD_DataValue_For_GenDesign_Apr26` — value: 8 лет longitudinal, гранулярность до кадастрового квартала, GeoJSON-полигоны для heatmap
|
||||
- `NSPD_AEGGIS_WMTS_Apr26` — WMTS-тайлы для embed в Mapbox/Leaflet
|
||||
- `NSPD_PII_Leak_Apr26` · `Rosreestr_Recon_Findings_Apr26` · `Rosreestr_TLS_Note_Apr26` — security/инфра наблюдения
|
||||
|
||||
**Инфраструктура:**
|
||||
- `Hosting_Pricing_Apr25` — сравнение провайдеров
|
||||
- `Hosting_Decision_Apr25` — решение по Yandex Cloud (для будущей миграции)
|
||||
- `Runbook_VPS_Setup_Beget` — пошаговая настройка production VM
|
||||
- `SSH_Setup_Apr25` — локальная настройка SSH
|
||||
- `DockerOps_Apr26` — multi-stage Dockerfile, npm ci, healthcheck fix, same-origin API
|
||||
- `Production_Wipe_Apr26` — clean re-deploy с нуля (event)
|
||||
- `Domain_Setup_Apr26` — gendsgn.ru, Beget, DNS, ESIA-правило
|
||||
- `Sentry_Setup_Apr26` — проект, DSN, тестовое событие
|
||||
- `OpsBundle_Apr26` — Alembic + TIGER cleanup + pre-commit + pg_dump
|
||||
- `HTTPS_Live_Apr26` — milestone: HTTPS прод заработал (включая lessons learned по ACME retry-state)
|
||||
|
||||
**Процесс / правила:**
|
||||
- `Onboarding_Colleague_KG` — подключение нового участника к графу
|
||||
- `Feedback_MemoryFirst` — правило: память first, docs только по запросу
|
||||
**Подключение коллеги к knowledge-graph** ([#11](https://github.com/lekss361/-gendesign/issues/11)) — пошаговая инструкция в issue.
|
||||
|
||||
---
|
||||
|
||||
## Полезные ссылки
|
||||
|
||||
- **🌐 Production:** https://gendsgn.ru/
|
||||
- **🩺 Health:** https://gendsgn.ru/health
|
||||
- **📚 OpenAPI:** https://gendsgn.ru/openapi.json · [Swagger UI](https://gendsgn.ru/docs)
|
||||
- **Репо:** https://github.com/lekss361/-gendesign
|
||||
- **Issues backlog:** https://github.com/lekss361/-gendesign/issues
|
||||
- **Milestones:** https://github.com/lekss361/-gendesign/milestones
|
||||
- **GitHub Actions:** https://github.com/lekss361/-gendesign/actions
|
||||
- **GHCR пакеты:** https://github.com/lekss361?tab=packages
|
||||
- **Sentry:** https://qqq-5y.sentry.io/insights/projects/gendesign-backend/
|
||||
- **Production VPS:** Beget Cloud → `cp.beget.com/cloud/servers/jolly-yakov-3`
|
||||
- **DNS-панель домена:** https://cp.beget.com/dns/14193206
|
||||
- **Целевой регион Discovery:** Свердловская обл. (Екатеринбург, ПЗЗ, МСК-66)
|
||||
- **Конкуренты:** rTIM (только концепт-стадия, нет site-selection), TestFit (нет РФ-данных)
|
||||
| | |
|
||||
|---|---|
|
||||
| 🌐 Production | https://gendsgn.ru/ |
|
||||
| 🩺 Health | https://gendsgn.ru/health |
|
||||
| 📚 OpenAPI | https://gendsgn.ru/openapi.json · [Swagger UI](https://gendsgn.ru/docs) |
|
||||
| Backlog | [Issues](https://github.com/lekss361/-gendesign/issues) · [Milestones](https://github.com/lekss361/-gendesign/milestones) |
|
||||
| Деплой | [GitHub Actions](https://github.com/lekss361/-gendesign/actions) · [GHCR пакеты](https://github.com/lekss361?tab=packages) |
|
||||
| Мониторинг | [Sentry](https://qqq-5y.sentry.io/insights/projects/gendesign-backend/) |
|
||||
| Инфра | Beget VPS [`cp.beget.com/cloud/...`](https://cp.beget.com/cloud/servers/jolly-yakov-3) · [DNS](https://cp.beget.com/dns/14193206) |
|
||||
|
||||
**Стратегические документы (снимки):** [`docs/MVP_Goals_Python.md`](docs/MVP_Goals_Python.md), [`docs/Бизнес-план v2.1 - Site Finder + Roadmap 12 месяцев.md`](docs/). При расхождении с графом — доверяем графу.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue