# Bot + open-data Росреестра — 2026-05-19

Drop-in замена `@rosreestdeals_bot` на официальные открытые данные ФГИС ЕГРН.

> **UPDATE 2026-06-24 — TG-бот фолбэк ОТКЛЮЧЁН (по решению владельца).** Сессия web.telegram.org (`data/telegram_web/storage_state.json`) для @rosreestdeals_bot протухла → ops_dashboard шумел алертами «cookie просрочились» (синтетич. сервис `rosreestr_deals_health`). Локальная open-data (664к записей, refresh пн 3:00 работает) теперь **единственный** источник: `rosreestr_combined` фолбэк загейтен флагом `ROSREESTR_BOT_FALLBACK` (в `.env`=0) → при пустом квартале отдаёт пусто, бота НЕ дёргает. В ops_dashboard `dashboard_config.json` запись `rosreestr_deals_health` **удалена** (иначе через 24ч `silent_decay`→вечный DOWN). Бэкапы `rosreestr_combined.py.bak-nofallback`, `dashboard_config.json.bak-rmrrdeals`. Откат: флаг=1 + рестарт + вернуть запись + перелогин TG-сессии (`setup_telegram_session.py`, ручной вход). Минус: кварталы вне open-data теперь без рыночного фона сделок (раньше их добирал бот).

## Архитектура

```
cadastral.py:4363 → rosreestr_combined.fetch_deals(quarter)
                        │
                        ├─ primary: rosreestr_opendata.fetch_for_quarter (SQLite-индекс)
                        │     └─ если count>0 → возврат с _source="opendata"
                        │
                        └─ fallback: rosreestr_deals.fetch_deals (@rosreestdeals_bot)
                              └─ возврат с _source="bot"
```

## Модули

| Файл | Что |
|---|---|
| `bot/services/rosreestr_opendata.py` | Скачивает CSV.zip с rosreestr.gov.ru/data-sets, SQLite-индекс по `quarter_cad_number` (только ЗУ = `realestate_type_code=002001001000`) |
| `bot/services/rosreestr_combined.py` | Orchestrator primary/fallback. Сигнатура совпадает с `rosreestr_deals.fetch_deals` |
| `bot/services/rosreestr_deals.py` | Старый бот-парсер. **Патч `_AREA_RE`**: разрешены `[.,]\d+` (раньше `728.30м²` парсилось как `30 м²` → завышение цен ×24) |
| `bot/tools/rosreestr_opendata_refresh.py` | Weekly refresh: качает свежий квартал, пересобирает индекс |
| `~/Library/LaunchAgents/local.zemlya-bot-rosreestr-opendata-refresh.plist` | Cron: понедельник 03:00, RunAtLoad=false |

## Окно данных

Последние 3 закрытых квартала ≥ 2025. Динамически — функция `_list_window_quarters()`. На 2026-05-19: `[Q1 2026, Q4 2025, Q3 2025]`.

## Хранилище

```
data/rosreestr_opendata/
  raw/sdelki_q{N}_{YYYY}.csv       # сырые ~90-130 МБ каждый, 3 шт ≈ 350 МБ
  raw/sdelki_q{N}_{YYYY}.zip       # архивы 10-30 МБ
  deals_index.sqlite               # фильтрованный индекс ≈30 МБ, 664k записей ЗУ
```

## Контракт fetch_for_quarter (совместим с _aggregate из rosreestr_deals)

```python
{
    "average_price_per_sotka": float,        # = mean_inflated
    "mean_price_per_sotka_inflated": float,
    "count": int,
    "quarters": list[str],                   # ["Кв. 1 2026", "Кв. 4 2025", "Кв. 3 2025"]
    "year_breakdown": {
        str(year): {"count": int, "mean_per_sotka_raw": float}
    },
    "_source": "opendata" | "bot",          # добавлено в combined
}
```

## Технические тонкости

- SSL до rosreestr.gov.ru: серт от Минцифры РФ, не в CA-bundle macOS → `ssl.CERT_NONE` в `_ssl_ctx()`
- Имена папок на nginx-листинге: `1 квартал 2026г.` в **cp1251** URL-кодировке → `urllib.parse.quote(..., encoding="cp1251")`
- Имя CSV внутри zip: `dataset_СДЕЛКИ_r-r_01-92_y_{year}_q_{q}.csv` (UTF-8 имя файла, cp1251 в URL)
- Поля CSV (разделитель `~`): `number, okato, region_code, district, city, quarter_cad_number, street, realestate_type_code, wall_material_code, year_build, floor, purpose_code, area, period_start_date, deal_price, currency, doc_type`
- Гранулярность: **кадастровый квартал** (12 цифр без последнего блока), не конкретный КН — это нормально для рыночного фона

## Бэкапы

- `bot/services/rosreestr_deals.py.BACKUP-pre-opendata-2026-05-19`
- `bot/handlers/cadastral.py.BACKUP-pre-opendata-2026-05-19`

## Параллельно доступны (не подключены пока, не просил юзер)

- АРЕНДА: `dataset_АРЕНДА_*.csv.zip` — те же 17 полей, `doc_type=ДА`
- КАДАСТРОВАЯ СТОИМОСТЬ: `dataset_КАДАСТРСТОИМОСТЬ_*.csv.zip` — 14 полей, `cost`, `cost_per_area`, `registration_quarter`, `application_quarter`. В одном квартале сотни объектов соседей

## Верификация vs @rosreestdeals_bot

По 5 реальным КН из БД:
- 47:23:0713001 — 12/12 сделок, mean совпадает <1% ✓
- 74:23:0202001 — 7/7 сделок, mean_2025 точно совпадает, mean_2026 разнится в 14× — **из-за бага парсера `_AREA_RE` бота** (теперь исправлен)
- 50:13:0020101 — opendata видит 3, бот 1 — OD полнее (момент снимка БД)
- 66:28:2501004 — opendata пусто (Q2 2025 вне окна), бот сработал как fallback ✓
- 04:01:020211 — opendata 6, бот ничего не сохранял в БД ранее — OD закрыла пробел ✓

**Источник у бота и open-data — один и тот же ЕГРН.** Расхождения объясняются временем снимка и багами парсера, не данными.
