diff --git a/tradein-mvp/backend/app/tasks/avito_detail_backfill.py b/tradein-mvp/backend/app/tasks/avito_detail_backfill.py index 77332fa6..10041b4f 100644 --- a/tradein-mvp/backend/app/tasks/avito_detail_backfill.py +++ b/tradein-mvp/backend/app/tasks/avito_detail_backfill.py @@ -1023,6 +1023,12 @@ async def run_avito_detail_backfill( counters.duration_sec = time.monotonic() - start current_counters = counters.to_dict() + # #3288 п.5: перепись диагнозов блоков — в counters ЛЮБОГО финала, а не только + # 'banned' (mark_backfill_finished кладёт её лишь на ветке бана). Прогоны + # 7193/7264/7302 (1600 попыток, ~1599 блоков, status='done') ушли без диагноза + # в БД: чей был отказ — площадки или нашего тракта — SQL'ем не узнать. + if block_ban_kinds: + current_counters["ban_kinds"] = dict(block_ban_kinds) # type: ignore[assignment] # #3184: гистограмма длин пачек блоков (streak -> сколько раз встретилась) -- # иначе эффект правки на #2674-статистике нечем измерить постфактум. finalize() # досчитывает хвостовую пачку, если прогон оборвался посреди серии. diff --git a/tradein-mvp/backend/tests/test_3288b_block_kind_ratio.py b/tradein-mvp/backend/tests/test_3288b_block_kind_ratio.py index 61fd0460..7d3ec9c9 100644 --- a/tradein-mvp/backend/tests/test_3288b_block_kind_ratio.py +++ b/tradein-mvp/backend/tests/test_3288b_block_kind_ratio.py @@ -229,6 +229,25 @@ async def test_sidecar_run_walks_the_whole_snapshot_and_is_not_banned() -> None: ) +@pytest.mark.asyncio +async def test_done_run_keeps_ban_kinds_in_counters() -> None: + """#3288 п.5: диагноз доезжает в counters и у прогона, который не 'banned'. + + Прогоны 7193/7264/7302 (1600 попыток, ~1599 отказов сайдкара, status='done') легли + в БД без ban_kinds: перепись писалась в counters только на ветке бана. + """ + fetches: list[Any] = [AvitoSidecarUnavailableError("sidecar 503") for _ in range(20)] + fetches += [MagicMock() for _ in range(10)] + + _, runs = await _run_avito(fetches, snapshot_size=30) + + written = runs.mark_backfill_finished.call_args.args[2] + assert written.get("ban_kinds") == {BAN_KIND_INFRA: 20}, ( + f"counters={written}: прогон без обрыва уходит в mark_done с этими counters — " + "без переписи диагноз отказов теряется" + ) + + @pytest.mark.asyncio async def test_real_platform_bans_still_abort_by_ratio() -> None: """Контроль: 14 настоящих AvitoBlockedError из 20 рвут прогон как раньше."""