---
name: zemlya-report-e2e
description: >
  End-to-end проверка цикла «Земля без риска» (zemlya-bot/zemlya-web, zbrcheck.ru)
  после ЛЮБОЙ доработки: прогон проверки участка + Free/AI-отчёта на реальном КН,
  скриншоты страниц для контроля вёрстки, проверка что новые поля рендерятся в нужных
  секциях отчёта, очистка тестовых данных, и актуализация документации (на сайте, в боте,
  в Obsidian) — «где что есть». Брать когда: меняли анализатор участка, ai_report_generator,
  infrastructure/poi, rosreestr_lk, секции AI-отчёта, тарифы, или просят «протестировать
  проверку участка / AI-отчёт», «снять скриншоты отчёта», «проверить что вёрстка не уплыла».
---

# zemlya-report-e2e — тестирование цикла отчёта + актуализация доков

**Принцип (от юзера):** каждая доработка zemlya → обязательно протестировать весь цикл
реальным прогоном со скриншотами, и при изменении фич — актуализировать документацию там,
где перечисляется «что где есть»: на сайте, в боте и в Obsidian. «Готово» = я сам видел,
что цикл проходит и вёрстка цела, И доки сверены.

## Когда брать
- После правок: `cadastral.py`, `ai_report_generator.py`, `infrastructure.py`, `poi_local.py`,
  `rosreestr_lk.py`, `nspd*.py`, секций AI-отчёта, тарифов, формы `/dashboard/new`.
- Когда просят протестировать проверку участка / AI-отчёт / снять скриншоты / проверить вёрстку.

## Среда (константы)
- mini: `ssh -i ~/.ssh/id_ed25519 cebers_mini@Cebers-mini.local`
- Сайт локально: Next `127.0.0.1:20141` (через него и `/api/...`), backend `127.0.0.1:20140`.
  **AI-HTML тяни через 20141 + браузерный UA** — прямой 20140 и headless без UA блокирует antibot
  (`{"error":"bot_blocked"}`).
- Admin: telegram_id `1005954706`, bot_user_id `3`. Токен сессии: backend venv →
  `from app.auth.jwt import issue; issue(1005954706, 3)`.
- Cookie `zw_session` для Playwright: домены `127.0.0.1` + `localhost`, `secure=False` (иначе
  по http не уйдёт), `httpOnly`, `sameSite=Lax`.
- DB: `~/Services/zemlya-bot/data/db/zemlya_bot.db`. Таблица `reports(user_id, cadastral_number,
  created_at, is_paid, report_mode)`. report_mode ∈ free|pro|ai. Worker `web_report_worker`
  поллит каждые 5с (free: report_mode='free'; pro/ai: is_paid=1). AI готов когда
  `ai_reports.status='completed'`.
- Playwright на mini: user-site `python3.9` → запускать `/usr/bin/python3` (НЕ venv бота).
- **reuse-cache 24ч**: повторный КН копирует прежний result_json. Для проверки СВЕЖИХ полей
  бери КН без отчёта за 24ч (проверь: `SELECT ... WHERE cadastral_number=? AND created_at>datetime('now','-24 hours')`),
  либо считай `cache_used` в result_json (None = свежий анализ).

## Шаги (оркестратор делает всё автоматически)

Запуск из `tools/`:
```
bash tools/zw_report_e2e.sh "<КН>" "free,ai"      # КН + режимы
```
Что делает:
1. mint admin-токен (backend venv).
2. INSERT тестовые reports-строки на КН для каждого режима → ids.
3. polling до готовности (result_json + ai_reports.status=completed).
4. дамп ключевых полей result_json (borders_established, infrastructure негативы/locality, cache_used).
5. staged Playwright-скрипт (`tools/shot_e2e.py`) на mini → скриншоты /dashboard, /dashboard/new,
   /reports/{id} (free), /reports/{id}+ai-html (ai) в `/tmp/zw_e2e_shots`.
6. scp PNG локально в `/tmp/zw_e2e_shots_local/` + grep маркеров в сыром ai-html.
7. **cleanup** тестовых reports/ai_reports/PNG (чтобы не засорять кабинет админа).

После прогона:
- **Read каждый PNG** — глазами проверить вёрстку (нет overflow, секции/таблицы/футер целы,
  hero-флаги, нет битых стилей). Сравнить с прошлым прогоном если есть.
- Проверить, что НОВОЕ поле реально в нужной секции (скрипт грепает ai-html и печатает контекст).
- Если меняли классификатор POI / индекс → сперва ребилд: `ssh ... 'cd /Volumes/Ext/mac-mini/poi && nohup bash refresh_poi.sh &'`
  (≈40 мин, фон), затем **рестарт бота** (poi_local кэширует conn к файлу — новый инод подхватится
  только после рестарта), затем прогон на СВЕЖЕМ КН.
- Рестарт бота: `launchctl kickstart -k gui/$(id -u)/local.zemlya-bot`. Health: `launchctl list | grep zemlya-bot` (статус 0).

## Актуализация документации (ОБЯЗАТЕЛЬНО при изменении фич)
Если добавили/убрали/переименовали то, что отображается пользователю — синхронизировать «где что есть»:
1. **Сайт** (`~/Services/zemlya-web/frontend`): лендинг `/` (сравнение Free/Pro/AI), `/help`,
   описания секций отчёта, тексты тарифов. Сборка `PATH=/opt/homebrew/bin:... npm run build` + рестарт frontend.
2. **Бот** (`~/Services/zemlya-bot`): `/help`, `/start`, описания тарифов, перечни фич в сообщениях.
3. **Obsidian** `Projects/Земля без риска check/`: `Changelog.md` (всегда), `Decisions.md` (если
   архитектурное), при необходимости `Overview.md`/`Scenarios.md` (таблица тарифов, список фич отчёта).
4. Свериться, что сайт и бот говорят ОДНО И ТО ЖЕ про фичи (парность). Чек-лист: `tools/DOCS_CHECKLIST.md`.

## Грабли (проверено вживую)
- antibot: всегда реальный `user_agent`, ai-html только через 20141.
- backups через переменную `$SSH` в zsh ломаются — вызывай `ssh` напрямую. Откат — через git (zemlya-bot под git).
- iframe AI-отчёта (AIReportInline) авто-высота: на скрине /reports/{id} (04) тело может быть пустым из-за
  тайминга — смотри сырой ai-html (05) как источник истины по контенту, а /reports/{id} — по обёртке (PDF-кнопка, снимок, LeadCTA).
- borders_established: True=полигон в ЕГРН (границы есть); False=декларированные; ключ отсутствует=объект не получен.
- **Проверка промпт-/build-/render-правок БЕЗ траты LLM-квоты (важно).** Оркестратор на шаге 7
  удаляет свежесгенерированный отчёт (`reports` + `ai_reports`) — вычитать его контент после прогона
  уже нельзя. Поэтому не гоняй полный e2e ради проверки контента; проверяй детерминированно (0 токенов LLM):
  - **GENERATION / prompt** (мастер-промпт, `_build_finance_section`, что уходит в LLM, какие ключи в схеме):
    возьми реальный `reports.result_json` и прогони
    `G._build_report_payload_enriched(rp)` → смотри `report_sections`; `G._build_verdict_prompt(enriched)`
    (есть ли ключ в JSON-входе); `G._load_master_prompt()` (текст промпта). Всё без вызова модели.
  - **RENDER** (`_render_report_html`, `_hero_risk_level`, новые секции/мета): пере-рендери сохранённый
    `ai_reports.ai_content` (`doc=json.loads(...)`) через `G._render_report_html(doc['payload'], doc['verdicts'])`
    и грепай нужный фрагмент. Тоже 0 токенов.
  - **NB:** build-time секции (finance/comparables/quartal) хранятся в `payload.report_sections` на МОМЕНТ
    генерации → re-render старого отчёта покажет СТАРЫЕ build-поля, но НОВЫЙ рендер-код (риск-метр, блок
    квартала, мета). Для проверки именно build-полей → собирай заново из `result_json` (см. выше).
  - Полный e2e нужен лишь чтобы подтвердить ЖИВОЙ бот end-to-end (каскад LLM не падает). Если надо вычитать
    ИМЕННО его свежий вывод — забери `reports.result_json`/`ai_reports.ai_content` ДО шага 7 (или временно убери cleanup).
