`newbuilding_enrich_backfill` считал свою работу разницей COUNT(*) до и после
сохранения — то есть приростом ЧИСЛА СТРОК, а не числом записанных точек. Вставка в
houses_price_dynamics идёт ON CONFLICT DO UPDATE, поэтому переписывание существующей
точки давало ноль.
Прод 10.08: прогон 3578 отчитался price_dynamics_rows=0, обновив за своё окно 64 строки
по 10 домам — те самые, что вставил прогон 3563 накануне (у него в counters стояло 64).
Ноль читался как «динамика цен снова не пишется».
Соседние два счётчика в том же месте врали по своим причинам: reliability_rows обнулял
_dedup_reliability, схлопывающий строку сразу после вставки, а review_rows игнорировал
число, которое _save_cian_reviews уже возвращал, в пользу разницы COUNT'ов.
Различать вставку и обновление умеет только сам писатель: RETURNING (xmax = 0) —
идиома, уже применяемая в репозитории (services/scrapers/gisogd66.py). Поэтому
save_newbuilding_enrichment возвращает NewbuildingSaveCounts, а вызывающий перестал
восстанавливать число по таблице. Остальные три вызова результат игнорируют.
Ключи counters переименованы (price_dynamics_inserted/_updated, reliability_inserted,
review_upserted): у старых имён в истории прогонов другой смысл, и молча поменять его
под тем же именем — ровно тот дефект, ради которого правка и делается.
Сторож нулевого результата не затронут: mark_backfill_finished судит по
attempted/enriched/gone/blocked, а не по числам записи, — на это добавлен тест.