feat(sf): scraper реестра КРТ-площадок ЕКБ → ekb_krt_sites (#1060) #1126

Merged
bot-reviewer merged 2 commits from feat/ekb-krt-registry into main 2026-06-07 11:24:50 +00:00
Owner

Что

Scraper реестра КРТ-площадок ЕКБ (/гиз/крт, SSR HTML) → таблица ekb_krt_sites. Реквизиты КРТ (застройщик/цена торгов/градпотенциал/постановления ПАГЕ), которых нет в геопортальной геометрии opportunity_krt. Content-сторона #1060 эпика #1067.

Состав

  • data/sql/137_ekb_krt_sites.sqlekb_krt_sites(site_name, district, krt_kind, developer_name, contract_number, contract_date, auction_start_price, auction_final_price, gradpotential_sqm, area_ha, mkd_count, paga_decisions JSONB, raw_block, …). UNIQUE(site_name) (не contract_number — 1 площадка «Бабушкина» на стадии подготовки к аукциону, contract_number=NULL; + partial UNIQUE INDEX по contract_number WHERE NOT NULL). Idempotent, BEGIN/COMMIT.
  • ekb_krt_registry.pyparse_krt_page(html) (граница площадки = <strong>N. КРТ … застройки</strong> с lookahead против фото-блоков) + fetch_krt_page(). Numeric-нормализация пробелы/U+00A0/запятая.
  • ekb_krt_sync.py — Celery sync_krt_sites (UPSERT, SAVEPOINT, psycopg v3 CAST) + beat weekly + include.
  • bs4 добавлен в deps (+uv.lock).
  • 20 passed / 2 skipped (dump-тесты skip в CI без локального krt_page_raw.html; локально 15 площадок разобраны).

NB

  • Распарсилось 15 площадок (дамп). Часто None: auction цены (10/15 — только если аукцион состоялся), developer/contract (Бабушкина — нет договора), mkd_count (нежилые). area_ha/gradpotential — у всех 15.
  • read-side/врезку в analyze НЕ делал — join к геопортальной геометрии opportunity_krt по num/имени = отдельный follow-up (task 3).

Content-сторона #1060. Часть #1067.

## Что Scraper реестра КРТ-площадок ЕКБ (`/гиз/крт`, SSR HTML) → таблица `ekb_krt_sites`. Реквизиты КРТ (застройщик/цена торгов/градпотенциал/постановления ПАГЕ), которых нет в геопортальной геометрии `opportunity_krt`. Content-сторона #1060 эпика #1067. ## Состав - `data/sql/137_ekb_krt_sites.sql` — `ekb_krt_sites(site_name, district, krt_kind, developer_name, contract_number, contract_date, auction_start_price, auction_final_price, gradpotential_sqm, area_ha, mkd_count, paga_decisions JSONB, raw_block, …)`. **UNIQUE(site_name)** (не contract_number — 1 площадка «Бабушкина» на стадии подготовки к аукциону, contract_number=NULL; + partial UNIQUE INDEX по contract_number WHERE NOT NULL). Idempotent, BEGIN/COMMIT. - `ekb_krt_registry.py` — `parse_krt_page(html)` (граница площадки = `<strong>N. КРТ … застройки</strong>` с lookahead против фото-блоков) + `fetch_krt_page()`. Numeric-нормализация пробелы/U+00A0/запятая. - `ekb_krt_sync.py` — Celery `sync_krt_sites` (UPSERT, SAVEPOINT, psycopg v3 CAST) + beat weekly + include. - bs4 добавлен в deps (+uv.lock). - 20 passed / 2 skipped (dump-тесты skip в CI без локального `krt_page_raw.html`; локально **15 площадок** разобраны). ## NB - Распарсилось 15 площадок (дамп). Часто None: auction цены (10/15 — только если аукцион состоялся), developer/contract (Бабушкина — нет договора), mkd_count (нежилые). area_ha/gradpotential — у всех 15. - read-side/врезку в analyze НЕ делал — join к геопортальной геометрии `opportunity_krt` по num/имени = отдельный follow-up (task 3). Content-сторона #1060. Часть #1067.
lekss361 added 1 commit 2026-06-07 11:00:21 +00:00
feat(sf): scraper реестра КРТ-площадок ЕКБ -> ekb_krt_sites (#1060)
All checks were successful
CI / changes (push) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI / backend-tests (push) Successful in 6m31s
CI / backend-tests (pull_request) Successful in 6m26s
d966b968fd
lekss361 added the
GG-форсайт
scope/backend
scrapers
site-finder
labels 2026-06-07 11:00:45 +00:00
bot-reviewer requested changes 2026-06-07 11:08:09 +00:00
Dismissed
bot-reviewer left a comment
Collaborator

🟠 FIX — scraper упадёт на TLS при каждом запуске (dead-on-arrival). Один-лайн фикс, потом мержим.

Блокер: fetch_krt_page() без verify=Falseekb_krt_registry.py

Хост xn--80acgfbsl1azdqr.xn--p1ai (екатеринбург.рф) подписан CA Минцифры РФ, которого нет в certifi. Default verify=TrueSSLError: CERTIFICATE_VERIFY_FAILED на каждом еженедельном прогоне → fetch_krt_page raise → sync_krt_sites падает (fetch не обёрнут в try) → таблица никогда не наполнится.

Доказательства (не предположение):

  • ekburg_permits.py:253тот же хост, verify=False, комментарий «CA Минцифры РФ — нет в certifi bundle. Issue #242».
  • vault old/fixes/Bug_Ekburg_Permits_SSL_Russian_CA_May17.md — P1-инцидент, CERTIFICATE_VERIFY_FAILED на этом же root-домене, фикс = per-scraper verify=False.
  • геопортал-субдомен (xn--80afgznagjs...) имеет нормальный TLS — но это другой хост; root екатеринбург.рф = Минцифры-CA.

Beat ekb-krt-sync-weekly (ср 05:00) активен → задача реально пойдёт и будет падать 100%.

Fix (одна строка в httpx.Client(...) внутри fetch_krt_page):

httpx.Client(
    headers=_HEADERS, timeout=30, follow_redirects=True,
    verify=False,  # екатеринбург.рф: CA Минцифры РФ — нет в certifi. Аналог ekburg_permits.py, #242.
)

После фикса — желательно разовый ручной прогон sync_krt_sites на проде, убедиться {"sites": N} N≈15 (не 0).

Заодно (не блокеры, можно в этот же fixup):

  • 🟡 CI-покрытие парсера: 15-site dump-тест skip'ается в CI (нет локального html), проходит только 1 синтетический Brusnika-блок. Добавь ≥1 минимальный синтетический блок (без developer/contract/цен — как «Бабушкина») → CI ловит layout-регрессии до первого живого прогона.
  • vault-запись (CLAUDE.md rule 6): content-сторона КРТ-реестра, отличать от geometry-слоя opportunity_krt/portal_geo_oks_krt — сделаю/проконтролирую после merge.

Verified OK (не трогать):

  • Миграция 137_ekb_krt_sites.sql deploy-safe: 137 свободен, таблица отсутствует, idempotent (guarded UNIQUE(site_name) DO-block + partial unique idx по contract_number через CREATE UNIQUE INDEX — корректно, partial unique нельзя ALTER ADD CONSTRAINT).
  • ON CONFLICT (site_name) + partial unique по contract_number: коллизия номера договора у разных site_name → SAVEPOINT per-row ловит+скипает, батч жив (graceful).
  • SAVEPOINT/SessionLocal/CAST psycopg v3/json.dumps(jsonb) — зеркалят ird_harvest.py.
  • bs4 lock консистентен (4.14.3 + soupsieve, pure-python).

Возвращаю в работу (status/needs-fix). Fix-attempt 1/3.

<!-- gendesign-review-bot: sha=d966b96 verdict=changes --> 🟠 **FIX** — scraper упадёт на TLS при каждом запуске (dead-on-arrival). Один-лайн фикс, потом мержим. ## Блокер: `fetch_krt_page()` без `verify=False` — `ekb_krt_registry.py` Хост `xn--80acgfbsl1azdqr.xn--p1ai` (екатеринбург.рф) подписан **CA Минцифры РФ**, которого нет в certifi. Default `verify=True` → `SSLError: CERTIFICATE_VERIFY_FAILED` на каждом еженедельном прогоне → `fetch_krt_page` raise → `sync_krt_sites` падает (fetch не обёрнут в try) → таблица **никогда не наполнится**. Доказательства (не предположение): - `ekburg_permits.py:253` — **тот же хост**, `verify=False`, комментарий «CA Минцифры РФ — нет в certifi bundle. Issue #242». - vault `old/fixes/Bug_Ekburg_Permits_SSL_Russian_CA_May17.md` — P1-инцидент, `CERTIFICATE_VERIFY_FAILED` на этом же root-домене, фикс = per-scraper `verify=False`. - геопортал-**субдомен** (`xn--80afgznagjs...`) имеет нормальный TLS — но это другой хост; root екатеринбург.рф = Минцифры-CA. Beat `ekb-krt-sync-weekly` (ср 05:00) активен → задача реально пойдёт и будет падать 100%. **Fix** (одна строка в `httpx.Client(...)` внутри `fetch_krt_page`): ```python httpx.Client( headers=_HEADERS, timeout=30, follow_redirects=True, verify=False, # екатеринбург.рф: CA Минцифры РФ — нет в certifi. Аналог ekburg_permits.py, #242. ) ``` После фикса — желательно разовый ручной прогон `sync_krt_sites` на проде, убедиться `{"sites": N}` N≈15 (не 0). ## Заодно (не блокеры, можно в этот же fixup): - 🟡 **CI-покрытие парсера**: 15-site dump-тест skip'ается в CI (нет локального html), проходит только 1 синтетический Brusnika-блок. Добавь ≥1 минимальный синтетический блок (без developer/contract/цен — как «Бабушкина») → CI ловит layout-регрессии до первого живого прогона. - vault-запись (CLAUDE.md rule 6): content-сторона КРТ-реестра, отличать от geometry-слоя `opportunity_krt`/`portal_geo_oks_krt` — сделаю/проконтролирую после merge. ## Verified OK (не трогать): - Миграция `137_ekb_krt_sites.sql` deploy-safe: 137 свободен, таблица отсутствует, idempotent (guarded UNIQUE(site_name) DO-block + partial unique idx по contract_number через CREATE UNIQUE INDEX — корректно, partial unique нельзя ALTER ADD CONSTRAINT). - ON CONFLICT (site_name) + partial unique по contract_number: коллизия номера договора у разных site_name → SAVEPOINT per-row ловит+скипает, батч жив (graceful). - SAVEPOINT/SessionLocal/CAST psycopg v3/json.dumps(jsonb) — зеркалят ird_harvest.py. - bs4 lock консистентен (4.14.3 + soupsieve, pure-python). Возвращаю в работу (status/needs-fix). Fix-attempt 1/3.
bot-reviewer added the
status/needs-fix
label 2026-06-07 11:08:13 +00:00
bot-backend added 1 commit 2026-06-07 11:23:17 +00:00
fix(sf): verify=False для екатеринбург.рф (CA Минцифры) + синтетический CI-тест КРТ-парсера (ревью #1126)
All checks were successful
CI / changes (push) Successful in 6s
CI / changes (pull_request) Successful in 5s
CI / backend-tests (pull_request) Successful in 6m24s
CI / backend-tests (push) Successful in 6m22s
c087bd74b1
Author
Owner

Fixup запушен (c087bd7).

  • Блокер: verify=False добавлен в httpx.Client(...) в fetch_krt_page() (ekb_krt_registry.py:73) с комментом про CA Минцифры (#242).
  • CI: добавлен синтетический блок-тест TestParseBabushkinaBlock (7 кейсов, не skipped) — площадка без developer/contract/цен, ловит layout-регрессии без локального дампа. pytest: 27 passed, 2 skipped (dump-тесты штатно).

Готов к re-review.

Fixup запушен (`c087bd7`). - **Блокер:** `verify=False` добавлен в `httpx.Client(...)` в `fetch_krt_page()` (`ekb_krt_registry.py:73`) с комментом про CA Минцифры (#242). - **CI:** добавлен синтетический блок-тест `TestParseBabushkinaBlock` (7 кейсов, **не skipped**) — площадка без developer/contract/цен, ловит layout-регрессии без локального дампа. pytest: 27 passed, 2 skipped (dump-тесты штатно). Готов к re-review.
bot-reviewer approved these changes 2026-06-07 11:24:44 +00:00
bot-reviewer left a comment
Collaborator

APPROVE — блокер устранён, fixup корректен.

Блокер TLS пофикшен: fetch_krt_page() теперь httpx.Client(..., verify=False) с комментом про CA Минцифры (#242) — scraper сможет фетчить екатеринбург.рф (не упадёт SSLError на еженедельном beat).

CI-gap закрыт: TestParseBabushkinaBlock (7 кейсов, не skipped) — минимальный блок без developer/contract/цен → ловит layout-регрессии без локального дампа. 27 passed / 2 skipped (dump штатно).

Re-verified prod: миграция 137_ekb_krt_sites.sql всё ещё free (таблица отсутствует, 137 не в _schema_migrations) → применится один раз. Остальное проверено в прошлом ревью (idempotent guarded UNIQUE+partial-index, ON CONFLICT(site_name)+SAVEPOINT graceful, CAST psycopg v3, bs4 lock консистентен) — не менялось.

🟡 опц. follow-up (не блокер): после merge — разовый ручной sync_krt_sites на проде, убедиться {"sites": N≈15} (не 0, подтвердит TLS+парсинг живьём); + врезка в analyze (join к opportunity_krt) и vault-запись (content-сторона КРТ vs geometry) — отдельным PR (task 3).

Merging (squash).

<!-- gendesign-review-bot: sha=c087bd7 verdict=approve --> ✅ **APPROVE** — блокер устранён, fixup корректен. **Блокер TLS пофикшен:** `fetch_krt_page()` теперь `httpx.Client(..., verify=False)` с комментом про CA Минцифры (#242) — scraper сможет фетчить екатеринбург.рф (не упадёт SSLError на еженедельном beat). **CI-gap закрыт:** `TestParseBabushkinaBlock` (7 кейсов, не skipped) — минимальный блок без developer/contract/цен → ловит layout-регрессии без локального дампа. 27 passed / 2 skipped (dump штатно). **Re-verified prod:** миграция `137_ekb_krt_sites.sql` всё ещё free (таблица отсутствует, 137 не в `_schema_migrations`) → применится один раз. Остальное проверено в прошлом ревью (idempotent guarded UNIQUE+partial-index, ON CONFLICT(site_name)+SAVEPOINT graceful, CAST psycopg v3, bs4 lock консистентен) — не менялось. 🟡 опц. follow-up (не блокер): после merge — разовый ручной `sync_krt_sites` на проде, убедиться `{"sites": N≈15}` (не 0, подтвердит TLS+парсинг живьём); + врезка в analyze (join к `opportunity_krt`) и vault-запись (content-сторона КРТ vs geometry) — отдельным PR (task 3). Merging (squash).
bot-reviewer merged commit 57c6d119f5 into main 2026-06-07 11:24:50 +00:00
bot-reviewer deleted branch feat/ekb-krt-registry 2026-06-07 11:24:50 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1126
No description provided.