_query_power_points и _query_water_latest делали db.execute без SAVEPOINT, тогда
как четыре соседних _query_* в том же файле его имеют.
Пункт эпика указывал на другое место — отсутствие db.rollback() в except-блоке
_get_connection_capacity (full_report_pdf.py:217). Но плоский rollback на ОБЩЕЙ
сессии запрещён: он снёс бы работу вызывающего. Чинить надо там, где сбой
происходит, — как во всей волне #2464.
Цепочка целиком:
сбой в _query_power_points
→ исключение всплывает (в get_connection_capacity try/except нет)
→ его глотает _get_connection_capacity ради деградации §3
→ транзакция остаётся aborted
→ падает уже СЛЕДУЮЩИЙ запрос — рыночная цена в _generate_concept_result
→ концепция молча уходит в class_norm-фолбэк
То есть отказ проявляется не там, где случился, и выглядит как «нет рыночных
данных». Ровно этот класс и описывает эпик.
Третье место (SET LOCAL statement_timeout в _query_nearby_network_zones) при
беглом поиске выглядит незащищённым, но лежит ВНУТРИ savepoint'а — проверено по
отступам, трогать не нужно.
Тест на PostgresLikeSession (tests/support/pg_session.py) — двойнике с настоящей
семантикой aborted-транзакции. На MagicMock он был бы зелёным по построению.
Против origin/main:
следующий запрос после проглоченного сбоя → AbortedTransactionError, ×2
savepoints_entered = 0 вместо >=1 → падает, ×2
двойник сам воспроизводит отравление — контроль, зелёный с обеих сторон
Последний контроль не для симметрии: без него зелёные тесты нельзя отличить от
«двойник ничего не моделирует».
Прогоны: tests/services/site_finder + tests/services/exporters — 868 passed rc=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>