feat(sf): cross-load ETL tradein→gendesign newbuilding_listings — вариант A (#976) #1185

Merged
lekss361 merged 2 commits from feat/sf-newbuilding-crossload into main 2026-06-12 08:29:55 +00:00
Owner

Summary

  • Сеть (шаг 1): корневой docker-compose.prod.ymlbackend и worker подключены к gendesign_shared (networks: [default, shared]); tradein-postgres уже там.
  • Роль (шаг 2): tradein-mvp/backend/data/sql/101_gendesign_reader_role.sql — идемпотентная read-only роль gendesign_reader (REVOKE-периметр, SELECT только на houses/house_sources/houses_price_dynamics/house_reliability_checks/house_reviews, пароля в SQL нет).
  • Bootstrap пароля (шаг 3): deploy-tradein.yml + новый tradein-mvp/ops/db-bootstrap/set_gendesign_reader_password.sql — паттерн set_config()/format('%L') через stdin (НЕ psql -c :'var' — тот самый задокументированный инцидент 2026-05-24; pre-push review поймал и это исправлено fixup-коммитом). Пустой TRADEIN_READER_PASSWORD → warning, не падение.
  • ETL (шаг 4): backend/app/services/etl/newbuilding_crossload.py — psycopg v3, батчи + SAVEPOINT, upsert ON CONFLICT (source, ext_house_id) DO UPDATE, COALESCE-protect для yandex_jk_id/cian_internal_house_id; маппинг 21 колонки houses→newbuilding_listings (commission_year/cadastral_number в tradein отсутствуют → NULL, задокументировано). Celery task + beat 00:30 UTC (03:30 МСК), admin endpoint (503 при пустом url), disabled-режим без кредов.
  • Тесты: 11 passed (disabled-режим без коннекта, маппинг, ON CONFLICT, COALESCE).

Pre-push review: 🔴 psql-интерполяция → исправлено (04da30b); minor commission_year — источника в tradein нет, follow-up при появлении.

Test plan

  • pytest tests/services/test_newbuilding_crossload.py — 11 passed
  • ruff, YAML compose/workflow valid
  • post-merge (needs-human, после добавления секретов): TRADEIN_READER_PASSWORD в tradein .env.runtime + TRADEIN_DATABASE_URL=postgresql://gendesign_reader:***@tradein-postgres:5432/tradein в gendesign backend/.env.runtime → admin-trigger ETL → счётчики source/target совпадают

⚠️ Миграция 101 авто-применится на прод при деплое (строгий exit-1) — она только CREATE ROLE/GRANT, ревью на идемпотентность пройдено.

Refs #976

## Summary - **Сеть (шаг 1):** корневой `docker-compose.prod.yml` — `backend` и `worker` подключены к `gendesign_shared` (`networks: [default, shared]`); `tradein-postgres` уже там. - **Роль (шаг 2):** `tradein-mvp/backend/data/sql/101_gendesign_reader_role.sql` — идемпотентная read-only роль `gendesign_reader` (REVOKE-периметр, SELECT только на houses/house_sources/houses_price_dynamics/house_reliability_checks/house_reviews, пароля в SQL нет). - **Bootstrap пароля (шаг 3):** `deploy-tradein.yml` + новый `tradein-mvp/ops/db-bootstrap/set_gendesign_reader_password.sql` — паттерн `set_config()`/`format('%L')` через **stdin** (НЕ `psql -c :'var'` — тот самый задокументированный инцидент 2026-05-24; pre-push review поймал и это исправлено fixup-коммитом). Пустой `TRADEIN_READER_PASSWORD` → warning, не падение. - **ETL (шаг 4):** `backend/app/services/etl/newbuilding_crossload.py` — psycopg v3, батчи + SAVEPOINT, upsert `ON CONFLICT (source, ext_house_id) DO UPDATE`, COALESCE-protect для `yandex_jk_id`/`cian_internal_house_id`; маппинг 21 колонки houses→newbuilding_listings (`commission_year`/`cadastral_number` в tradein отсутствуют → NULL, задокументировано). Celery task + beat 00:30 UTC (03:30 МСК), admin endpoint (503 при пустом url), disabled-режим без кредов. - Тесты: 11 passed (disabled-режим без коннекта, маппинг, ON CONFLICT, COALESCE). Pre-push review: 🔴 psql-интерполяция → исправлено (04da30b); minor `commission_year` — источника в tradein нет, follow-up при появлении. ## Test plan - [x] pytest tests/services/test_newbuilding_crossload.py — 11 passed - [x] ruff, YAML compose/workflow valid - [ ] post-merge (needs-human, после добавления секретов): `TRADEIN_READER_PASSWORD` в tradein `.env.runtime` + `TRADEIN_DATABASE_URL=postgresql://gendesign_reader:***@tradein-postgres:5432/tradein` в gendesign `backend/.env.runtime` → admin-trigger ETL → счётчики source/target совпадают ⚠️ Миграция 101 авто-применится на прод при деплое (строгий exit-1) — она только CREATE ROLE/GRANT, ревью на идемпотентность пройдено. Refs #976
lekss361 added 2 commits 2026-06-12 08:27:32 +00:00
- docker-compose.prod.yml: backend + worker добавлены в сеть gendesign_shared
  (нужен прямой TCP к tradein-postgres для psycopg ETL)
- tradein-mvp/data/sql/101_gendesign_reader_role.sql: роль gendesign_reader
  (LOGIN, SELECT на houses/house_sources/houses_price_dynamics/
   house_reliability_checks/house_reviews; пароль через TRADEIN_READER_PASSWORD
   bootstrap в deploy-tradein.yml)
- .forgejo/workflows/deploy-tradein.yml: bootstrap ALTER ROLE gendesign_reader
  PASSWORD из env после миграций; warn (не exit 1) если переменная не задана
- backend/app/core/config.py: settings.tradein_database_url (env TRADEIN_DATABASE_URL)
- backend/app/services/etl/newbuilding_crossload.py: run_crossload() — psycopg
  server-side cursor, batch 500, UPSERT ON CONFLICT (source, ext_house_id),
  SAVEPOINT per-row; disabled-режим если url пуст
- backend/app/workers/tasks/etl_newbuilding_crossload.py: Celery task
- backend/app/workers/beat_schedule.py: nightly 03:30 МСК (00:30 UTC)
- backend/app/api/v1/admin_scrape.py: POST /admin/scrape/newbuilding-crossload
- backend/tests/services/test_newbuilding_crossload.py: 10 тестов без реальной БД

needs-human: TRADEIN_READER_PASSWORD → tradein .env.runtime;
             TRADEIN_DATABASE_URL → gendesign backend/.env.runtime
fix(sf): psql stdin-bootstrap пароля gendesign_reader + COALESCE-protect внешних id (review fixup, #976)
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / changes (push) Successful in 7s
CI / frontend-tests (push) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (push) Successful in 6m23s
CI / backend-tests (pull_request) Successful in 6m21s
04da30b57a
- Создан tradein-mvp/ops/db-bootstrap/set_gendesign_reader_password.sql:
  set_config GUC + format('%L') + \o /dev/null — зеркальный паттерн
  ops/db-bootstrap/set_tradein_fdw_password.sql. Пароль не попадает
  в stdout/logs (leak-protected).
- deploy-tradein.yml: шаг bootstrap переписан на stdin-redirect
  (< ops/db-bootstrap/set_gendesign_reader_password.sql) вместо сломанного
  -c "ALTER ROLE ... PASSWORD :'TRADEIN_READER_PASSWORD'", которое давало
  syntax error at ':' (psql не интерполирует :'var' внутри -c строки).
- newbuilding_crossload.py ON CONFLICT DO UPDATE: yandex_jk_id и
  cian_internal_house_id → COALESCE(EXCLUDED.col, table.col), чтобы
  cian-запись не затирала yandex_jk_id NULL'ом и наоборот.
- Тест test_upsert_sql_coalesce_external_ids проверяет наличие COALESCE
  в DO UPDATE секции для обоих id.
lekss361 added the
GG-форсайт
scope/backend
site-finder
status/review
labels 2026-06-12 08:27:38 +00:00
lekss361 merged commit 9b06377df8 into main 2026-06-12 08:29:55 +00:00
lekss361 deleted branch feat/sf-newbuilding-crossload 2026-06-12 08:29:55 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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#1185
No description provided.