"""Пересчёт состояния прогона парсинга в шкалу загрузки — чистая логика. Стадии прогона несопоставимы по единицам (получено из API источника vs. записано в базу), а шкала в админке нужна одна. Формула живёт здесь одна на всех, чтобы не разъезжаться между списком источников, карточкой прогона и панелью отладки. """ # Прогоны, после которых двигаться уже некуда TERMINAL_STATUSES = frozenset({"done", "partial", "error", "cancelled"}) def run_percent(status: str, stage: str, target: int, fetched: int, processed: int) -> float: """Процент готовности: 0→50% — выборка из источника, 50→100% — индексация. Завершённый прогон — всегда 100%, каким бы ни был исход: шкала показывает «работа окончена», а исход — статус рядом с ней. Пока цель неизвестна (target=0), выборка не даёт прогресса — рисуем 0. """ if status in TERMINAL_STATUSES: return 100.0 fetch_part = min(fetched / target, 1.0) * 50 if target > 0 else 0.0 if stage != "index": return round(fetch_part, 1) index_part = min(processed / fetched, 1.0) * 50 if fetched > 0 else 0.0 return round(fetch_part + index_part, 1)