* fix(frontend): apiFetchWithStatus body stream double-read crash
Per user report 2026-05-14: 'Failed to execute text on Response: body stream
already read' при analyze с non-JSON error response.
apiFetchWithStatus делал .json() (consumes stream), потом .text() в catch
(FAILS — stream already consumed). Fetch API: body — ReadableStream,
consumable один раз.
Fix: read text() once → JSON.parse on string. На fail (HTML / non-JSON)
оборачиваем в {detail: rawText}. Никаких double-reads.
Site-finder cad search больше не крашится на error pages.
* feat(infra): auto-apply data/sql/*.sql migrations in deploy pipeline (#150)
Закрывает root cause production 500 на /api/v1/admin/site-finder/weight-profiles
(а также 2 unapplied migrations: #89/#91).
## Why это нужно
После audit обнаружили что:
- backend/alembic/versions/ пустой (Alembic configured но не используется)
- deploy.yml не имеет migration step
- 47+ raw SQL files накопилось без auto-apply
- 3 файла unapplied: 87 (engineering), 89 (drop brin), 90 (weight profiles)
User report: GET weight-profiles → 500 потому что user_weight_profiles
table не существует на prod.
## Changes
### deploy.yml +39 lines
- Path trigger расширен на data/sql/*.sql
- New step 'Apply DB migrations' между compose pull и compose up -d:
- Idempotent CREATE TABLE _schema_migrations
- Loop по data/sql/*.sql sorted → skip applied → psql with ON_ERROR_STOP=on
- Record applied filename в tracking table
- Exit 1 на failure → containers НЕ обновляются (no partial state)
### NN collision fix
- 87_engineering_networks_191fz.sql → 91_engineering_networks_191fz.sql
(collision с 87_on_demand_indexes.sql, мой renumber 85→87 был неудачным)
### CLAUDE.md +32 lines
- Subsection 'Auto-apply' под Migration pattern
- Bootstrap procedure docs
- Idempotency requirement
- Failure recovery
### scripts/bootstrap_schema_migrations.sh +100 lines
- One-time seed _schema_migrations с 48 already-applied files
- 3 файла intentionally NOT seeded: 89/90/91 (auto-apply на first deploy)
- Usage docs + verification query
## Vault
domains/infra/Runbook_Auto_Apply_Migrations.md NEW
infra-MOC.md updated
## Required manual steps ДО merge
1. SSH gendesign (tunnel)
2. POSTGRES creds export
3. bash scripts/bootstrap_schema_migrations.sh — seed 48 already-applied files
4. Verify: SELECT COUNT(*) FROM _schema_migrations → 48
5. После merge → first deploy auto-apply #89/#90/#91 → 500 closes
Closes#150
---------
Co-authored-by: lekss361 <claudestars@proton.me>
* fix(sql): renumber engineering_networks 85→87, dedup classid, add SRID
Three critical fixes на untracked 85_engineering_networks_191fz.sql per
code review (May 14) — до первого применения на prod.
1. **NN collision fix**: renumber 85_engineering_networks_191fz.sql → 87_
85_pzz_zones_ekb.sql уже занимает 85_ prefix. При alphabetic deploy
order `85_e*` применился бы ДО `85_p*` — но 87_ ставит нас после
86_v_bucket_success_score.sql без зависимости на 85_pzz.
2. **Duplicate classid '907015504'**:
- Был дважды: 'Опора металлическая' + 'Опора железобетонная'.
- Второй ON CONFLICT DO UPDATE молча перетёр бы первый → NSI код
металлической опоры терялся бы.
- Fix: 907015504 остаётся за металлической (lower-numbered subtype
per Минстрой NSI source), железобетонная переезжает на 907015507
(заполняет gap 906→508). Помечено comment-меткой 'verify with
Минстрой NSI table' до prod deploy.
3. **Untyped geometry**:
- Было: `geom GEOMETRY` без SRID constraint → разрешало бы insert
mix MSK-66 (srid=100000) и WGS84 (srid=4326) → silent ST_DWithin
corruption.
- Fix: `geom GEOMETRY(Geometry, 4326)`. Capital G позволяет
mixed Point/LineString/Polygon, SRID 4326 жёстко фиксирует WGS84.
ETL loader должен делать ST_Transform(msk66_raw, 4326) перед
insert (см. backend/app/core/crs.py для CRS utility).
Idempotency: BEGIN...COMMIT, IF NOT EXISTS, ON CONFLICT DO UPDATE
паттерн — уже корректные в файле.
Closes Day-1 critical #2 from code review audit (May 14).
Pre-deploy: verify 907015507 classid assignment against Минстрой
official NSI table; reassign and re-run migration if needed.
* fix(sql): remove provisional 907015507, fix Фонати→Фонари typo
Addresses review blocker on PR #118:
- Provisional classid 907015507 ('Опора железобетонная (столб)') убран
из seed insert. Commented out с TODO(NSI-verify) — следующий шаг
верифицировать против официальной NSI-таблицы Минстроя ДО восстановления
строки. Без верификации ON CONFLICT DO UPDATE name перетёр бы корректное
значение в eng_ref_classid.
- Опечатка 'Фонати электрические' → 'Фонари электрические' (907015602).
- Sort: 904015504/505/506/508 теперь в численном порядке (после удаления
провизорной 507).
Live seed теперь покрывает 504 (металл столб), 505 (металл ферма),
506 (металл фермовый столб), 508 (железобет ферма). Строка 507
restored after Минстрой NSI verification (см. follow-up).
---------
Co-authored-by: lekss361 <claudestars@proton.me>