Skip to main content

ТЗ на разработку Система планирования и диспетчеризации поставок товарного бетона «CementFlow» (Предварительная версия)

1. ВВЕДЕНИЕ

1.1. Наименование проекта

Система планирования и диспетчеризации поставок товарного бетона «CementFlow» с поддержкой каскадного перерасчёта.
1.2. Краткое описание
Разрабатываемая система предназначена для оптимального распределения грузовиков-миксеров для выполнения заказов на доставку товарного бетона в течение 12-часовой рабочей смены. Система обеспечивает автоматическое планирование поставок, мониторинг исполнения в реальном времени и интеллектуальный перерасчёт расписания при возникновении сбоев (поломки техники, ДТП, задержки).
Система использует гибридную слотовую модель планирования: визуальный интерфейс диспетчера представляет расписание в виде сетки фиксированных временных интервалов (слотов) для удобства восприятия и ручного управления, в то время как ядро системы работает с непрерывной временной шкалой для точных вычислений.
Система предназначена для работы с несколькими производственными базами (ЦБЗ). Автоматическое распределение заказов между ними осуществляется по взвешенному географическому принципу, который учитывает не только расстояние до объекта клиента, но и текущую загрузку каждой базы для балансировки производственной мощности.
Водитель является активным участником системы через мобильное приложение, получает задания (поставки) и имеет возможность отклонять назначенную ему поставку, что немедленно инициирует процедуру перепланирования данного рейса и связанных с ним задач.
1.3. Область применения
Система предназначена для использования:
  • Бетонными заводами и производственными комплексами
  • Логистическими компаниями, специализирующимися на перевозке строительных материалов
  • Диспетчерскими службами строительных компаний
1.4. Цели разработки
  1. Повышение эффективности использования парка машин на 25-30%
  2. Снижение простоев грузовиков-миксеров на 35-40%
  3. Минимизация потерь бетона из-за превышения времени жизни
  4. Обеспечение 95% своевременности доставок
  5. Сокращение времени реакции на сбои с 30-60 минут до 5-10 минут
2. ОБЩИЕ ТРЕБОВАНИЯ
2.1. Функциональное назначение
Система должна обеспечивать:
2.1.1. Основные функции:
  1. Планирование смены - формирование оптимального расписания поставок (слотов) на 12-часовую смену с распределением заказов между несколькими ЦБЗ.
  2. Распределение техники - автоматический подбор грузовиков-миксеров (АБС) и автобетононасосов (АБН) для выполнения поставок с учётом их синхронизации.
  3. Мониторинг исполнения - отслеживание статуса поставок и местоположения техники в реальном времени.
  4. Полуавтоматический каскадный перерасчёт - интеллектуальное перепланирование при возникновении сбоев с генерацией вариантов и обязательным утверждением изменений диспетчером.
  5. Визуализация - отображение расписания в виде интерактивной диаграммы Ганта и на карте.
2.1.2. Вспомогательные функции:
  1. Управление справочниками (машины, водители, клиенты, объекты)
  2. Формирование отчётности и аналитика
  3. Интеграция с внешними системами (GPS, ERP, CRM)
  4. Уведомления и оповещения
2.2. Пользователи системы


Роль пользователя Основные функции Доступ
Диспетчер Полный доступ ко всем функциям планирования и мониторинга Веб-интерфейс
Руководитель смены Просмотр, утверждение расписания, анализ эффективности Веб-интерфейс
Водитель Получение заданий, отметка о выполнении этапов, отклонение заказа с указанием причины, сообщение о проблемах Мобильное приложение
Администратор Управление пользователями, настройка системы, техническая поддержка Веб-интерфейс
Клиент (опционально) Просмотр статуса своего заказа, получение уведомлений Веб-портал/SMS
3. ТЕХНОЛОГИЧЕСКИЕ ТРЕБОВАНИЯ
3.1. Архитектурные требования
  1. Микросервисная архитектура с разделением на независимые модули
  2. Событийно-ориентированная архитектура для обработки событий в реальном времени
  3. REST API для интеграции с внешними системами
  4. Веб-сокеты для обновления интерфейса в реальном времени
  5. Поддержка горизонтального масштабирования
3.2. Технологический стек
Компонент
Технологии
Бэкенд
.NET 8, C# 12, ASP.NET Core
Фронтенд
React 18 + TypeScript, Redux Toolkit/ Svelte SvelteKit + TypeScript
База данных
PostgreSQL 16 (основная), Redis 7 (кэш)
Очереди сообщений
RabbitMQ
Контейнеризация
Docker, Docker Compose
Оркестрация
Kubernetes (опционально)
Мониторинг
Grafana, Prometheus
CI/CD
GitLab CI/CD или GitHub Actions
3.3. Требования к производительности


Параметр Требование
Время отклика API ≤ 200 мс для 95% запросов
Время планирования смены ≤ 30 секунд для 100 заказов
Время каскадного перерасчёта ≤ 10 секунд
Поддержка одновременных пользователей ≥ 50 диспетчеров
Доступность системы 99.5% (SLA)
Время восстановления ≤ 15 минут
4. ДЕТАЛЬНОЕ ОПИСАНИЕ ФУНКЦИОНАЛА
4.1. Модуль управления заказами
4.1.1. Создание и редактирование заказов
  • Форма создания заказа с полями:
    • Клиент (выбор из справочника)
    • Объект доставки (адрес, контактное лицо, телефон)
    • Объём бетона (м³)
    • Марка бетона, подвижность
    • Желаемое время доставки (окно или точное время)
    • Приоритет (нормальный, высокий, критический)
    • Особые требования (непрерывная заливка, бетононасос и т.д.)
    • Предмет заливки (выбор из справочника) - влияет на нормативное время разгрузки.
    • Требуется автобетононасос (АБН) (флажок Да/Нет).
    • Приоритет клиента (выбор: нормальный, высокий, критический) - определяет допустимый интервал доставки (например, ±30 мин, ±60 мин, ±120 мин).
4.1.1a. Синхронизация слотов АБС и АБН

Если заказ требует автобетононасоса (флаг concretePumpRequired = true), система при планировании поставки должна создавать двойную структуру слотов (linked slots):

  • Слот АБС (ScheduleSlot с type="TRUCK_CONCRETE_MIXER") — определяет график доставки и разгрузки бетона

  • Слот АБН (ScheduleSlot с type="CONCRETE_PUMP") — определяет график работы насоса на объекте

Эти слоты связаны двусторонней ссылкой через поле linkedSlotId.

Правило синхронизации (Hard Constraint):

ABN.setupEnd ≤ ABS.unloadingStart AND
ABS.unloadingStart = ABN.workStart AND
ABS.unloadingEnd = ABN.workEnd AND
ABS.unloadingEnd ≤ ABS.loadingStart + 120 (время жизни бетона)

 

Если это правило нарушено — система не может создать валидную пару слотов и переходит в режим Tier 2 (полуавтоматический кризис) с предложением вариантов диспетчеру.

4.1.2. Автоматическое разбиение заказов на поставки
  • Алгоритм разбиения учитывает:
    • Вместимость доступных машин
    • Требование непрерывной заливки
    • Оптимальное заполнение миксера (минимизация остатков)
4.1.3. Управление сменами и доменными сущностями
Система оперирует следующими ключевыми бизнес-сущностями, которые необходимо учитывать при планировании и диспетчеризации:
  • Смена (Shift): Центральный агрегат, объединяющий все данные на рабочий день (12 часов). Атрибуты: уникальный ID, дата, время начала и окончания, статус (в планировании, активна, завершена, отменена). Смена является контейнером для заказов, поставок, назначений машин и слотов расписания.
  • Слот (ScheduleSlot): Базовый элемент планирования. Представляет собой временной интервал, выделенный конкретной машине (АБС/АБН) для выполнения конкретной задачи. Атрибуты: уникальный ID, время начала, время окончания, статус (свободен, забронирован, в процессе, выполнен, отменен), привязка к машине (Truck/ConcretePump), привязка к поставке (Delivery), привязка к смене (Shift).
  • Производственная база (ЦБЗ) (ProductionBase): Объект производства и отправки продукции. Атрибуты: уникальный ID, название, юридический и фактический адрес, геокоординаты, статус (активна, на обслуживании), привязка к графику работы (PlantSchedule). Влияет на распределение заказов.
  • График работы завода (PlantSchedule): Расписание доступности производственной базы. Атрибуты: уникальный ID, привязка к ProductionBase, день недели, время начала и окончания рабочего дня, перерывы (опционально). Определяет временные окна для погрузки.
  • Событие (Event): Запись о любом значимом отклонении от плана или изменении статуса. Атрибуты: уникальный ID, тип (поломка, ДТП, задержка, отклонение водителем, изменение статуса ТС и т.д.), дата и время возникновения, источник (система, диспетчер, водитель), описание, критичность, привязка к связанным сущностям (машина, поставка, слот). Является триггером для алгоритмов перерасчета и основой для аналитики.
4.1.3a. Двойная привязка слотов в сущности смены

Каждая смена (Shift) содержит набор ScheduleSlots, который может включать как одиночные слоты (для заказов без АБН), так и парные слоты (для заказов с АБН).

Парные слоты имеют:

  • Взаимные ссылки (linkedSlotId)

  • Синхронизированные временные интервалы

  • Общую метрику риска (riskScore)

  • Единый статус (оба слота переходят в "completed" одновременно)

4.1.4. Иерархическая структура сущностей
Наглядное представление того, как заказ разбивается на поставки, которые порождают двойные слоты — для АБС (автобетономесителя) и АБН (автобетононасоса) с явными ссылками между ними.
Order (Заказ)
├── concretePumpRequired: true/false
├── continuousPouring: true/false
├── maxInterval: PT30M (если непрерывная заливка)
│
└─── Delivery[1..N] (Поставка)
     ├── volume: 5.0 м³
     ├── desiredTimeWindow: {start: T1, end: T2}
     │
     └─── ScheduleSlot[1..2]  ← ДВОЙНАЯ СВЯЗЬ
          ├── ScheduleSlot (АБС — Truck)
          │   ├── id: "SLOT-ABS-12345"
          │   ├── type: "TRUCK_CONCRETE_MIXER"
          │   ├── machineId: "TRUCK-001"
          │   ├── driverId: "DRIVER-042"
          │   ├── linkedPumpSlotId: "SLOT-ABN-12345"  ← ССЫЛКА НА СЛОТ АБН
          │   ├── status: "planned" | "in_progress" | "completed"
          │   │
          │   └─── Временная шкала АБС:
          │       ├── loadingStart: 08:45
          │       ├── loadingEnd: 09:00
          │       ├── departFromPlantTime: 09:00
          │       ├── concreteLifeExpires: 10:30  ← 90 мин от loadingStart
          │       ├── arrivalAtSite: 09:20
          │       ├── unloadingStart: 09:20  ← КРИТИЧНО! Совпадает с началом работы АБН
          │       ├── unloadingEnd: 09:26    ← 6 мин для 5м³
          │       └── returnToPlanTime: 09:40
          │
          └─── ScheduleSlot (АБН — Pump)
              ├── id: "SLOT-ABN-12345"
              ├── type: "CONCRETE_PUMP"
              ├── machineId: "PUMP-007"
              ├── operatorId: "OPERATOR-15"
              ├── linkedTruckSlotId: "SLOT-ABS-12345"  ← ССЫЛКА НА СЛОТ АБС
              ├── status: "planned" | "in_progress" | "completed"
              │
              └─── Временная шкала АБН:
                  ├── arrivalAtSite: 09:15  ← ДОЛЖНО БЫТЬ ≤ unloadingStart АБС (09:20)
                  ├── setupTime: 5 мин
                  ├── setupEnd: 09:20
                  ├── workStart: 09:20      ← ДОЛЖНО СОВПАДАТЬ с unloadingStart АБС
                  ├── workEnd: 09:26        ← ДОЛЖНО СОВПАДАТЬ или ПОЗЖЕ unloadingEnd АБС
                  ├── dismantleStart: 09:26
                  ├── dismantleEnd: 09:35
                  └── departFromSite: 09:35
4.1.5. Правила синхронизации (Constraints)
Четыре жестких правила:
  • Временная вложенность слотов

  • Прибытие АБН на объект

  • Минимальное время на разгрузку

  • Ограничение по времени жизни бетона

Правило 1: Временная вложенность (Temporal Nesting)
ABN.setupEnd ≤ ABS.unloadingStart ≤ ABS.unloadingEnd ≤ ABN.workEnd

Если нарушено:
  → Слот АБН исключается из кандидатов
  → Система ищет другой доступный АБН
Правило 2: Прибытие АБН на объект
ABN.arrivalAtSite + ABN.setupTime ≤ ABS.unloadingStart

Проверка:
  if ABN.arrivalAtSite + ABN.setupTime > ABS.unloadingStart:
    penalty = INFINITE  # Слот невозможен
Правило 3: Минимальное время на разгрузку
ABS.unloadingEnd - ABS.unloadingStart ≥ estimatedUnloadingTime(volume)

Пример:
  volume = 5.0 м³
  unloadingRate = 1 м³/мин
  estimatedTime = 5-6 мин  # Минимум на разгрузку через насос
Правило 4: Время жизни бетона (Hard Constraint)
ABS.unloadingEnd ≤ ABS.loadingStart + CONCRETE_LIFE_TIME (120 мин)

Если нарушено:
  → Слот отклоняется
  → Поставка возвращается в пул проблемных
4.1.6. Пошаговый алгоритм подбора парного слота (Paired Slot Selection)
С примерами расчетов для конкретного объема бетона и времени разгрузки.
Входные данные:
- Поставка (Delivery) с volume, site, requiredTimeWindow, concreteType- Требуется АБН = true- Список доступных АБС (машин-миксеров)- Список доступных АБН (насосов)- Матрица времени в пути между объектами
Этапы:
Шаг 1: Определить минимальное время разгрузки
concreteType = "M300"volume = 5.0unloadingRate = 1.0 м³/мин (зависит от типа бетона и оборудования)MIN_UNLOADING_TIME = volume / unloadingRate = 5 минACTUAL_UNLOADING_TIME = MIN_UNLOADING_TIME + buffer(0.5 мин) = 5.5 мин
Шаг 2: Для каждого кандидата АБС рассчитать требуемое время прибытия АБН
// Задано: ABS планируется на unloadingStart = 09:20requiredABNArrival = unloadingStart - ABN.setupTime - travelBuffer                  = 09:20 - 5 мин - 3 мин  # 3 мин буфер на непредвиденные задержки                  = 09:12// АБН должен прибыть на объект не позже 09:12, чтобы быть готов к 09:20
Шаг 3: Найти доступные АБН
FOR EACH pump IN availablePumps:  IF pump.status == "available"      AND pump.currentLocation.distanceTo(site) / avgSpeed ≤ requiredABNArrival:    candidates.append({      pumpId: pump.id,      possibleArrival: pump.currentLocation.distanceTo(site) / avgSpeed,      setupEnd: possibleArrival + pump.setupTime,      riskScore: calculateRisk(possibleArrival, requiredABNArrival)    })
Шаг 4: Ранжировать кандидатов АБН по критериям
FOR EACH candidate IN candidates:  score = (W1 × timeScore) + (W2 × loadScore) + (W3 × distanceScore)    WHERE:    timeScore = (requiredABNArrival - possibleArrival) / 10  // минуты в резерве    loadScore = 1 / (pump.currentLoad + 1)  // менее загруженные насосы предпочтительнее    distanceScore = 1 / (distanceToSite + 1)  // ближе = лучше  // Жесткий фильтр: если possibleArrival + setupTime > unloadingStart + buffer:  if setupEnd > unloadingStart + 2:  // 2 мин буфер на непредвиденное    score = INFINITE_PENALTY    continue  candidates_ranked.append((candidate, score))// Выбрать TOP 3 кандидатаbestPumps = sort(candidates_ranked, by=score)[:3]
Шаг 5: Создать связанные слоты
FOR EACH selectedPump IN bestPumps:
  
  // Создать слот АБС
  truckSlot = ScheduleSlot(
    id: generateId(),
    type: "TRUCK_CONCRETE_MIXER",
    machineId: truck.id,
    deliveryId: delivery.id,
    linkedPumpSlotId: pumpSlot.id,  ← СВЯЗЬ 1
    unloadingStart: 09:20,
    unloadingEnd: 09:20 + ACTUAL_UNLOADING_TIME,
    status: "planned"
  )
  
  // Создать слот АБН
  pumpSlot = ScheduleSlot(
    id: generateId(),
    type: "CONCRETE_PUMP",
    machineId: selectedPump.id,
    deliveryId: delivery.id,
    linkedTruckSlotId: truckSlot.id,  ← СВЯЗЬ 2
    arrivalAtSite: selectedPump.estimatedArrival,
    setupEnd: selectedPump.estimatedArrival + setupTime,
    workStart: truckSlot.unloadingStart,  ← СИНХРОНИЗАЦИЯ
    workEnd: truckSlot.unloadingEnd,      ← СИНХРОНИЗАЦИЯ
    status: "planned"
  )
  
  // Сохранить оба слота в БД
  db.saveScheduleSlot(truckSlot)
  db.saveScheduleSlot(pumpSlot)
  
  // Обновить ссылки (обратные связи)
  db.updateScheduleSlot(truckSlot.id, linkedPumpSlotId=pumpSlot.id)
  db.updateScheduleSlot(pumpSlot.id, linkedTruckSlotId=truckSlot.id)

4.1.7. Обработка трех критических конфликтов при синхронизации

Сценарий
1:
  • АБН не может прибыть вовремя

     
  • Ситуация:
      

    - АБС запланирован на 09:20 - АБН может прибыть не раньше 09:35 - Требуется буфер 3 мин Решение: 1. Отклонить этот АБН 2. Перейти к следующему кандидату 3. Если кандидатов нет → Уровень 2 (полуавтоматический кризис) → Предложить диспетчеру: a) Сдвинуть начало разгрузки АБС на 09:38 b) Назначить другого АБН (если есть) c) Выполнить заказ БЕЗ насоса (если клиент согласен)

    Сценарий 2: Конфликт времени жизни бетона

  • Ситуация:
      
  • -

    Загрузка бетона: 08:45 - Время жизни: 120 мин → Истекает в 10:45 - Планируемая разгрузка: 10:50 - АБН готов к 10:40, но работа закончится в 11:00 Решение: 1. Проверка: 10:50 > 10:45 → НАРУШЕНИЕ 2. Пересчитать: unloadingEnd должна быть ≤ 10:45 3. unloadingStart = 10:45 - ACTUAL_UNLOADING_TIME = 10:39 4. Провечрить: ABN.setupEnd ≤ 10:39? - Если ДА → Синхронизация возможна - Если НЕТ → Отклонить слот → Tier 2

  • Сценарий 3: АБН требуется сдля другой поставки в то же время
    Ситуация:
      - Поставка 1: АБН требуется 09:20-09:30 на объект A
      - Поставка 2: АБН требуется 09:25-09:35 на объект B (10 км от A)
      - Время в пути между объектами: 20 мин
    
    Решение:
      1. Проверка конфликта:
         if pump.workEnd (Поставка 1) + travelTime(A→B) ≤ pump.workStart (Поставка 2):
           → Может выполнить обе (если scheduleSlot.dismantleEnd + travel ≤ next.workStart)
      
      2. Условие совместимости:
         dismantleEnd_1 = 09:30 + 5 мин = 09:35
         travel_time = 20 мин
         arrival_at_B = 09:35 + 20 = 09:55
         required_at_B = 09:25
         
         09:55 > 09:25 → НЕВОЗМОЖНО
         → Нужен другой

    АБН для
    Поставки
    2

    4.1.8. SQL-схема таблицы ScheduleSlots

    С ключевым полем linked_slot_id для двусторонней связи слотов.

    CREATE TABLE schedule_slots (
      id UUID PRIMARY KEY,
      shift_id UUID FOREIGN KEY,
      delivery_id UUID FOREIGN KEY,
      machine_id UUID NOT NULL,  -- может быть truck_id или pump_id
      linked_slot_id UUID NULLABLE,  -- ← ССЫЛКА НА ПАРНЫЙ СЛОТ
      slot_type ENUM('TRUCK_CONCRETE_MIXER', 'CONCRETE_PUMP'),
      
      -- Временные параметры (зависят от типа)
      start_time TIMESTAMP NOT NULL,
      end_time TIMESTAMP NOT NULL,
      buffer_time INTEGER DEFAULT 5,  -- в минутах
      
      -- Для АБС
      truck_loading_start TIMESTAMP,
      truck_loading_end TIMESTAMP,
      truck_unloading_start TIMESTAMP,
      truck_unloading_end TIMESTAMP,
      truck_depart_time TIMESTAMP,
      
      -- Для АБН
      pump_arrival_time TIMESTAMP,
      pump_setup_time INTEGER,  -- в минутах
      pump_work_start TIMESTAMP,  ← СИНХРОНИЗИРОВАН С truck_unloading_start
      pump_work_end TIMESTAMP,    ← СИНХРОНИЗИРОВАН С truck_unloading_end
      pump_dismantle_time INTEGER,  -- в минутах
      
      -- Статусы и метаданные
      status ENUM('planned', 'reserved', 'confirmed', 'in_progress', 'completed', 'cancelled'),
      risk_score DECIMAL(3,2),
      created_at TIMESTAMP,
      updated_at TIMESTAMP,
      
      CONSTRAINT valid_linked_slot:
        linked_slot_id IS NULL OR 
        (SELECT slot_type FROM schedule_slots WHERE id = linked_slot_id) 
        != slot_type  -- Слот АБС не может быть связан с другим слотом АБС
    );
    
    -- Индексы для быстрого поиска
    CREATE INDEX idx_linked_slot ON schedule_slots(linked_slot_id);
    CREATE INDEX idx_delivery ON schedule_slots(delivery_id);
    CREATE INDEX idx_machine ON schedule_slots(machine_id);
    

    4.1.9. JSON API для создания парных слотов

    Полный пример запроса и ошибок с вариантами решений.

    {
      "createLinkedSlots": {
        "request": {
          "deliveryId": "DEL-2024-001",
          "truckId": "TRUCK-005",
          "truckSlot": {
            "unloadingStart": "2024-01-15T09:20:00",
            "unloadingEnd": "2024-01-15T09:26:00",
            "loadingStart": "2024-01-15T08:45:00",
            "concreteLifeExpires": "2024-01-15T10:45:00"
          },
          "pumpRequired": true,
          "targetSite": {
            "coordinates": [55.7558, 37.6176],
            "name": "Объект строительства №1"
          }
        },
        "response": {
          "success": true,
          "truckSlot": {
            "id": "SLOT-ABS-12345",
            "machineId": "TRUCK-005",
            "linkedPumpSlotId": "SLOT-ABN-12345",
            "unloadingStart": "2024-01-15T09:20:00",
            "unloadingEnd": "2024-01-15T09:26:00",
            "status": "planned"
          },
          "pumpSlot": {
            "id": "SLOT-ABN-12345",
            "machineId": "PUMP-007",
            "linkedTruckSlotId": "SLOT-ABS-12345",
            "arrivalAtSite": "2024-01-15T09:15:00",
            "setupEnd": "2024-01-15T09:20:00",
            "workStart": "2024-01-15T09:20:00",
            "workEnd": "2024-01-15T09:26:00",
            "status": "planned"
          },
          "synchronizationStatus": {
            "valid": true,
            "riskScore": 0.15,
            "warnings": []
          }
        },
        "errorResponse": {
          "success": false,
          "error": "NO_COMPATIBLE_PUMP",
          "reason": "Ни один доступный АБН не может прибыть на объект до требуемого времени",
          "suggestions": [
            {
              "option": 1,
              "description": "Отложить начало разгрузки на 12 минут",
              "newUnloadingStart": "2024-01-15T09:32:00",
              "availablePump": "PUMP-007"
            },
            {
              "option": 2,
              "description": "Выполнить без АБН (если клиент согласен)",
              "impact": "Нарушит условия заказа"
            }
          ]
        }
      }
    }
    

    4.1.10. Псевдокод налгоритма Pythonсинхронизации

    Готовые к внедрению функции синхронизации.

    def synchronize_truck_and_pump_slots(delivery, truck, target_unloading_start):
        """
        Синхронизирует слоты АБС и АБН для одной поставки.
        Возвращает (truckSlot, pumpSlot) или (None, error_message)
        """
        
        # Расчет времени разгрузки
        estimated_unloading_duration = calculate_unloading_time(
            volume=delivery.volume,
            concrete_type=delivery.concrete_type
        )
        
        target_unloading_end = target_unloading_start + estimated_unloading_duration
        
        # Проверка времени жизни бетона
        concrete_expiration = delivery.loading_time + CONCRETE_LIFE_TIME
        if target_unloading_end > concrete_expiration:
            return None, "CONCRETE_LIFE_EXCEEDED"
        
        # Поиск совместимых АБН
        compatible_pumps = find_compatible_pumps(
            site_coordinates=delivery.site.coordinates,
            required_arrival_time=target_unloading_start - PUMP_SETUP_BUFFER,
            required_work_duration=estimated_unloading_duration,
            delivery_id=delivery.id,
            concrete_type=delivery.concrete_type
        )
        
        if not compatible_pumps:
            return None, "NO_COMPATIBLE_PUMP"
        
        # Выбор лучшего АБН по скорингу
        best_pump = rank_and_select_pump(compatible_pumps)
        
        # Создание слота АБС
        truck_slot = ScheduleSlot(
            id=generate_uuid(),
            machine_id=truck.id,
            delivery_id=delivery.id,
            slot_type="TRUCK_CONCRETE_MIXER",
            unloading_start=target_unloading_start,
            unloading_end=target_unloading_end,
            status="planned"
        )
        
        # Создание слота АБН с синхронизацией
        pump_slot = ScheduleSlot(
            id=generate_uuid(),
            machine_id=best_pump.id,
            delivery_id=delivery.id,
            slot_type="CONCRETE_PUMP",
            arrival_at_site=best_pump.estimated_arrival,
            setup_end=best_pump.estimated_arrival + best_pump.setup_time,
            work_start=truck_slot.unloading_start,  # ← СИНХРО
            work_end=truck_slot.unloading_end,      # ← СИНХРО
            status="planned"
        )
        
        # Установка взаимных ссылок
        truck_slot.linked_slot_id = pump_slot.id
        pump_slot.linked_slot_id = truck_slot.id
        
        # Проверка финальной консистентности
        if not validate_slot_synchronization(truck_slot, pump_slot):
            return None, "SYNCHRONIZATION_FAILED"
        
        return (truck_slot, pump_slot)
    
    
    def find_compatible_pumps(site_coordinates, required_arrival_time, 
                               required_work_duration, delivery_id, concrete_type):
        """
        Находит все АБН, которые могут выполнить работу.
        """
        compatible = []
        
        for pump in get_available_pumps(concrete_type):
            # Рассчитать время в пути до объекта
            travel_time = calculate_travel_time(
                from_coords=pump.current_location,
                to_coords=site_coordinates
            )
            
            estimated_arrival = pump.earliest_available_time + travel_time
            setup_end = estimated_arrival + pump.setup_time
            work_end = setup_end + required_work_duration
            
            # Проверка совместимости
            if setup_end <= required_arrival_time + ARRIVAL_BUFFER:
                compatible.append({
                    'pump': pump,
                    'estimated_arrival': estimated_arrival,
                    'setup_end': setup_end,
                    'work_end': work_end,
                    'risk_score': calculate_risk_score(estimated_arrival, required_arrival_time)
                })
        
        return compatible
    

    4.1.11. ASCII-диаграмма временной шкалы

    Визуальный пример с критическими моментами синхронизации.

    4.1.12.
    ОБЪЕКТ СТРОИТЕЛЬСТВА: ул. Строитекомлей, 15
    ПОСТАВКА: 5м³ М300 (Непрерывндации для редзаливктирования)
    
    АБС (Truck-005):
    08:45 ├─ ПОГРУЗКА ─┤09:00
              │
              ├─ ПУТЬ ─┤09:20
                        │
                        ├─ РАЗ

    ГРУЗКА

    Точные(5.5 местаин) в─┤09:25:30 │ ├─ ВОЗВРАТ ─┤09:40 АБН (Pump-007): 09:00 ├─ ПУТЬ ─┤09:15 │ ├─ УСТАНОВКА (5 мин) ─┤09:20 │ ▼◄─ СИНХРОНИЗАЦИЯ! ─►▼ │ ├─ РАБОТА (разделгрузках) ─┤09:25:30 │ ├─ ДЕМОНТАЖ (8-10 мин) ─┤09:35 КРИТИЧЕСКИЕ МОМЕНТЫ СИНХРОНИЗАЦИИ: ┌──────────────────────────────────────────────────────────────┐ │ 1. Pump.setupEnd (09:20) ≤ Truck.unloadingStart (09:20) ✓ │ 2. Truck.unloadingStart (09:20) = Pump.workStart (09:20) ✓ │ 3. Truck.unloadingEnd (09:25:30) ≤ Pump.workEnd (09:25:30) ✓ │ 4.1, Truck.unloadingEnd (09:25:30) ≤ ConcreteLife (10:45) ✓ │ 5.1, 5.2Truck.unloadingEnd (09:25:30) ≤ ConcreteLife (120 мин) п✓ │ │ СТАТУС СИНХРОНИЗАЦИИ: ✓ ВАЛИДНА │ RISK_SCORE: 0.12 (低 - низкий риложенияхск) для└──────────────────────────────────────────────────────────────┘ вставки новой информации.

    4.2. Модуль планирования

    4.2.1. Алгоритм первоначального распределения

    Детальное описание приведено в Приложении №1.

    Входные данные:

    Этапы алгоритма:

    1. Группировка заказов по приоритету и временным окнам

    2. Привязка заказов к ЦБЗ и проверка графика: при выборе ЦБЗ для заказа система должна проверить, что желаемое время доставки клиента может быть обеспечено с учетом графика работы выбранного завода. Если нет — необходимо предложить альтернативные слоты или ЦБЗ.
    3. производственной базой на основе минимального расстояния до объекта и текущей загрузки базы.
    4. Разбиение на поставки с учётом непрерывности заливки

    5. Распределение по машинам по критериям:

    6. Расчёт временных окон с учётом:

    7. Формирование диаграммы Ганта  - визуализирует распределение слотов (ScheduleSlot), принадлежащих конкретной смене (Shift), по машинам (АБС/АБН) на временной шкале.

    4.2.2. Критерии выбора машины

    Формула оценки приоритета машины:

    Score = (W1 × DistanceScore) + (W2 × CapacityUtilization) + (W3 × TimeWindowFit) + PriorityBonus

    где:

    4.3. Модуль каскадного перерасчёта

    4.3.1. Типы обрабатываемых событий

    Все перечисленные события (поломка, ДТП, задержка, отклонение водителем и пр.) фиксируются в системе как сущности Event. Каждое событие имеет тип, временную метку, источник и привязку к затронутой сущности (машина, поставка, слот).

    1. Поломка машины - машина выходит из строя

    2. ДТП - авария с участием машины

    3. Задержка на разгрузке - превышение планового времени разгрузки

    4. Пробки - увеличение времени в пути

    5. Изменение заказа - корректировка клиентом

    6. Новая доступная машина - ввод машины в эксплуатацию

    7. Отклонение заказа водителем - водитель отказался от выполнения через мобильное
    8. Изменение статуса машины "На линии" - ручное снятие машины с эксплуатации.

    4.3.2. Алгоритм перерасчёта

    Алгоритм является полуавтоматическим (автоматическим расчет с ручным утверждением рассчитанного сценария диспетчером). Система анализирует воздействие, генерирует варианты перепланирования, но не применяет их автоматически. Окончательное решение и утверждение изменённого расписания остаётся за диспетчером. 

    Детально приведен в Приложении №2.

    Алгоритм активируется в режиме реального времени при поступлении события сбоя (TruckStatusChanged, DeliveryDelayReported, TruckBreakdown) или отклонении заказа водителем.

    А. Принцип Двухуровневой Реакции

    Система разделяет сбои на две категории, чтобы обеспечить достижение целевого времени реакции ($\le 10$ секунд) для некритических случаев.

    1. Tier 1: Автоматическая Коррекция (Мягкое Каскадирование)

    2. Tier 2: Полуавтоматический Кризис (Жесткое Каскадирование)

    Б. Этапы Обработки Сбоя
    1. Изоляция Сбоя и Расчёт Задержки (Δt)
    1. Определение $\Delta t$: Фиксация фактического времени задержки или оценка минимального времени простоя при поломке.

    2. Изоляция: Затронутый слот (и все последующие слоты данной машины) блокируются.

    3. Критическое Ограничение: Проверка первого затронутого слота: Новое_Время_Разгрузки $\le$ Время_начала_погрузки + Время_жизни_бетона.

    2. Решение Tier 1: Автоматическая Коррекция
    3. Решение Tier 2: Полуавтоматический Кризис
    Ограничение Цель
    Жёсткий Лимит Сдвига (Shift Limit) Предотвращение бесконечного каскада. Если автоматический сдвиг Sn превышает 60 минут от оригинального времени, система обязательно переключается на Tier 2 для утверждения.
    Контроль Водителя При назначении нового слота на другое ТС, в скоринге обязательно проверяется, что Driver.RemainingDutyTime >= Продолжительность_слота. Если нет, ТС исключается из пула кандидатов.
    Учет АБН Если заказ требует АБН, система ищет связанный слот АБН (с учетом времени переезда насоса) и присваивает бесконечный штраф в скоринге, если синхронизация невозможна.

    4.4. Модуль визуализации

    4.4.1. Диаграмма Ганта

    Требования к отображению:

    Интерактивные возможности:

    4.4.2. Карта перемещений

    4.5. Модуль интеграций

    4.5.1. Внешние системы

    Система Тип интеграции Назначение
    GPS/телематика REST API/WebSocket Отслеживание местоположения, статуса машин
    Картографические сервисы API (Яндекс.Карты/Google Maps) Расчёт маршрутов и времени в пути с учётом актуальных пробок.
    1C:ERP REST API Получение финансовых данных: цена заказа, финансовое состояние клиента, стоимость продукта.
    CRM система REST API Получение информации о клиентах
    СМС-сервис REST API Уведомления водителей и клиентов
    Погодный сервис REST API Учёт погодных условий в планировании

    5. ТРЕБОВАНИЯ К ДАННЫМ

    5.1. Структура базы данных

    5.1.1. Основные таблицы:

    1. Shifts - Смены. Атрибуты: id, date, start_time, end_time, status, created_at.

    2. Orders - заказы клиентов. Атрибуты: id, shift_id, client_id, construction_site_id, ...

    3. Deliveries - поставки (части заказов). Атрибуты: id, order_id, truck_id, ...

    4. Trucks - грузовики-миксеры (АБС). Атрибуты: id, production_base_id, license_plate, capacity, status, ...

    5. Drivers - водители.

    6. Clients - клиенты.

    7. ConstructionSites - объекты строительства.

    8. ScheduleSlots - слоты в расписании. Атрибуты: id, shift_id, truck_id (или concrete_pump_id), delivery_id, start_time, end_time, status, type (for АБС/АБН).

    9. Events - события системы. Атрибуты: id, type, severity, source, description, related_entity_type (truck, delivery, slot), related_entity_id, occurred_at, resolved_at.

    10. RoutePoints - точки маршрутов.

    11. ConcretePumps - автобетононасосы (АБН). Атрибуты: id, production_base_id, type, status, ...

    12. ProductionBases - производственные базы (ЦБЗ). Атрибуты: id, name, address, coordinates, status.

    13. PlantSchedules - графики работы заводов. Атрибуты: id, production_base_id, day_of_week, start_time, end_time, is_active.

    14. PouringItems - предметы заливки.

    15. TruckOnDutyStatus - история статусов ТС "На линии".

    5.1.2. Требования к хранению:

    5.2. Форматы данных

    5.2.1. JSON API форматы:

    {
      "order": {
        "id": "ORD-2024-001",
        "client": {
          "id": "CL-001",
          "name": "ООО СтройГрад",
          "priority": "high"
        },
        "site": {
          "address": "ул. Строителей, 15",
          "coordinates": {"lat": 55.7558, "lng": 37.6176}
        },
        "details": {
          "volume": 15.0,
          "concreteGrade": "M300",
          "slump": 15,
          "pouringItem": "Фундамент",
          "concretePumpRequired": true,
          "clientPriority": "high",
          "allowedDeliveryIntervalMinutes": 60,
          "requiredWindow": {
            "start": "2024-01-15T09:00:00",
            "end": "2024-01-15T12:00:00"
          },
          "continuousPouring": true,
          "maxInterval": "PT30M"
        }
      }
    }

    5.3. Аспекты требующие дальнейшего уточнения и проработки в рамках бизнес-процесса

    Категория Триггер Допустимое Действие Время Реакции
    Уровень 1: Некритический Сдвиг (Автоматический) Задержка на предыдущем слоте < Буферного времени текущего слота (например, задержка 10 мин при буфере 15 мин). Автоматический сдвиг всего последующего расписания данной машины. Все сдвиги остаются в пределах временных окон (SLA) клиентов. <= 10 секунд (выполняется алгоритмом)
    Уровень 2: Критический Сбой (Полуавтоматический) Поломка ТС, ДТП, задержка > Буферного времени, сдвиг выводит слот за SLA клиента, или требуется переназначение слота на другую машину. Алгоритм генерирует 3 лучших варианта (переназначить, отменить, сдвинуть с нарушением SLA) и ожидает подтверждения диспетчера. <= 10 секунд (генерация вариантов) + Ручное утверждение (3–5 минут)

    5.3. Риски и рекомендации

    * - соглашение об уровне обслуживания (Service Level Agreement), формальный договор между заказчиком и поставщиком услуг, определяющий параметры качества, такие как доступность сервиса, время реакции на проблемы и восстановления. В контексте вашего текста о рисках в заливке бетона и работе с АБН (автобетононасосами), нарушение SLA подразумевает несоблюдение гарантированных сроков поставок или времени жизни материалов, что приводит к массовым сбоям.

    6. ТРЕБОВАНИЯ К ИНТЕРФЕЙСУ

    6.1. Веб-интерфейс диспетчера

    6.1.1. Основной экран:

    6.1.2. Экран планирования:

    6.1.3. Экран мониторинга:

    6.2. Мобильное приложение водителя

    6.2.1. Основные экраны:

    1. Задания на день - список поставок

    2. Текущее задание - детали, карта маршрута, при наличии – отображается информация о необходимом автобетононасосе (АБН) и времени его работы.

    3. Отметки о выполнении - кнопки для отметки этапов

    4. Сообщения - связь с диспетчером

    5. Проблемы - форма сообщения о сбоях

    6.2.2. Функционал:

    6.2.3. Отклонение заказа:

    Водитель имеет кнопку для отклонения текущего/назначенного задания с обязательным указанием причины из списка (болезнь, поломка ТС и т.д.). Отклонение передаётся в систему и инициирует процедуру перерасчёта.

    7. ТРЕБОВАНИЯ К НАДЁЖНОСТИ И БЕЗОПАСНОСТИ

    7.1. Надёжность

    7.2. Безопасность

    8. ТРЕБОВАНИЯ К РАЗВЁРТЫВАНИЮ И ЭКСПЛУАТАЦИИ

    8.1. Развёртывание

    8.2. Эксплуатация

    9. ЭТАПЫ РАЗРАБОТКИ

    Этап 1: Прототип и архитектура (1-2 месяца)

    Этап 2: Ядро системы (3-4 месяца)

    Этап 3: Доработка и тестирование (2-3 месяца)

    Этап 4: Внедрение и поддержка (постоянно)

    10. КРИТЕРИИ ПРИЁМКИ

    10.1. Функциональные критерии

    1. Система корректно планирует смену для 100+ заказов

    2. Автоматический перерасчёт выполняется за ≤10 секунд

    3. Диаграмма Ганта отображает все поставки без пересечений

    4. Интеграция с GPS работает в реальном времени

    5. Мобильное приложение работает офлайн

    10.2. Нефункциональные критерии

    1. Время отклика интерфейса ≤200 мс

    2. Система выдерживает нагрузку 50+ одновременных пользователей

    3. Доступность 99.5% в рабочее время

    4. Данные сохраняются при перезапуске системы

    10.3. Приёмочные испытания

    1. Тестирование на тестовых данных - 7 дней

    2. Пилотная эксплуатация - 14 дней

    3. Стресс-тестирование - имитация сбоев и высокой нагрузки

    4. Проверка безопасности - пентест

    11. ГАРАНТИИ И ПОДДЕРЖКА

    11.1. Гарантийный период

    11.2. Техническая поддержка

    ПРИЛОЖЕНИЯ

    Приложение A: Словарь терминов

    Приложение B: Схемы интеграций

    (Диаграммы последовательности и архитектурные схемы)

    Приложение C: Макеты интерфейсов

    (Визуальные макеты всех экранов системы)


    ИСПОЛНИТЕЛЬ: [Наименование организации-исполнителя]

    ЗАКАЗЧИК: [Наименование организации-заказчика]

    СРОК РАЗРАБОТКИ: 8-12 месяцев

    СТОИМОСТЬ РАЗРАБОТКИ: [Сумма] руб.

    СРОК ДЕЙСТВИЯ ТЗ: до [Дата]


    Документ утверждён:
    [Подпись Заказчика] [Подпись Исполнителя]
    [Дата] [Дата]


    Приложение №1

    ДЕТАЛЬНОЕ ОПИСАНИЕ АЛГОРИТМА ПЕРВОНАЧАЛЬНОГО РАСПРЕДЕЛЕНИЯ

    1. ОБЩИЙ ПРИНЦИП РАБОТЫ АЛГОРИТМА

    Алгоритм первоначального распределения — это многоуровневая жадная эвристика с обратной связью, которая преобразует список заказов клиентов в оптимизированное расписание поставок для всего парка машин на 12-часовую смену. Алгоритм работает по принципу "наиболее ограниченные задачи решаются первыми".

    Ключевая идея: Сначала назначаются самые сложные и критичные поставки, затем менее сложные, что позволяет избежать ситуаций, когда критичные задачи невозможно выполнить из-за того, что все ресурсы уже заняты менее важными задачами.

    2. ПОШАГОВЫЙ АЛГОРИТМ С РАСЧЁТАМИ

    Этап 0: Подготовка данных (Pre-processing)

    Входные данные:

    Шаг 0.1: Валидация и нормализация данных


    Для каждого заказа:
      1. Проверить корректность данных
      2. Рассчитать минимальное и максимальное время доставки
      3. Привести желаемое окно клиента к стандартному формату
      4. Вычислить коэффициент срочности = (дедлайн - текущее время) / общее время смены

    Шаг 0.2: Сортировка заказов по сложности выполнения


    Сложность_заказа = (Приоритет_клиента × 0.4) + 
                       (Коэффициент_срочности × 0.3) + 
                       (Объём_заказа / Стандартный_объём × 0.2) + 
                       (Сложность_логистики × 0.1)
                       
    Сортируем заказы по убыванию Сложности_заказа

    Этап 1: Разбиение заказов на атомарные поставки

    Шаг 1.1: Определение оптимального размера поставки


    Для каждого заказа:
      Если Объём_заказа ≤ Максимальная_вместимость_машины:
        Количество_поставок = 1
        Объём_поставки = Объём_заказа
      Иначе:
        Найти все доступные вместимости машин: C1, C2, ..., Ck
        Найти оптимальное разбиение, минимизирующее:
          Целевая_функция = α × Количество_поставок + 
                            β × (Сумма_остатков_в_машинах) + 
                            γ × (Максимальный_интервал_между_поставками)

    Пример расчёта для заказа 15 м³:


    Доступные вместимости машин: 5 м³, 7 м³, 10 м³
    Варианты разбиения:
      1) 3 × 5 м³ = 15 м³ (остаток 0) ✓
      2) 2 × 7 м³ = 14 м³ (остаток 1 м³ - требует доп. машину 5 м³)
      3) 1 × 10 м³ + 1 × 5 м³ = 15 м³ (остаток 0) ✓
      
    Выбираем вариант 3, так как минимизирует количество поставок

    Шаг 1.2: Создание сущностей поставок


    Для каждой поставки:
      1. Присвоить уникальный ID
      2. Унаследовать данные от родительского заказа
      3. Рассчитать параметры:
         - Минимальное время выполнения = Погрузка + Путь_туда + Разгрузка + Возврат
         - Время жизни бетона = 90 минут (для стандартного бетона)
         - Окончательный дедлайн = MIN(Дедлайн_клиента, Время_начала + Время_жизни)

    Этап 1а: Привязка заказов к ЦБЗ

     Для каждого заказа рассчитывается расстояние/время до каждой доступной ЦБЗ (с учётом графика работы).
     Выбирается ЦБЗ с минимальным значением (время_доставки * Коэффициент_загрузки_базы).
     Заказ закрепляется за выбранной ЦБЗ. Все поставки по нему будут планироваться от этой базы.

    Этап 2: Группировка поставок для параллельной обработки

    Шаг 2.1: Создание групп по ограничениям


    Группа А: Поставки с жёсткими временными окнами (нельзя сдвигать)
    Группа Б: Поставки, требующие непрерывной заливки
    Группа В: Поставки для VIP-клиентов
    Группа Г: Стандартные поставки
    Группа Д: Поставки с гибкими временами
    
    Приоритет обработки: А → Б → В → Г → Д

    Шаг 2.2: Внутригрупповая сортировка


    Для каждой группы сортировка по:
      1. Времени начала окна (от более раннего к более позднему)
      2. Длительности окна (от более узкого к более широкому)
      3. Объёму (от большего к меньшему)

    Этап 3: Распределение поставок по машинам

    Цикл обработки поставок:


    Для каждой поставки в порядке приоритета групп:
      Шаг 3.1: Формирование пула кандидатов
        Для каждой машины проверяем:
          1. Вместимость ≥ Объём_поставки
          2. Техническая исправность = true
          3. Машина доступна в течение смены
          4. Машина может прибыть на завод к нужному времени
          5. Доступное время работы водителя (Drivers.RemainingDutyTime) ≥ Продолжительность_слота
          6. ТС должна быть в статусе "На линии" (Да) и приписана к ЦБЗ, 
             за которой закреплён заказ (допускается привлечение машин с других баз в случае дефицита).
          7. Для поставок, требующих АБН, формируется отдельный пул кандидатов среди доступных АБН.
          
      Шаг 3.2: Предварительная оценка кандидатов
        Для каждой машины-кандидата рассчитываем:
          Время_начала_минимальное = MAX(Текущее_свободное_время, Время_прибытия_на_завод)
          Время_окончания = Время_начала + Длительность_поставки
          
        Отсеиваем кандидатов, у которых:
          - Время_окончания > Окончательный_дедлайн
          - Время_окончания > Конец_смены
          - Время_разгрузки > Время_начала + Время_жизни_бетона
          
      Шаг 3.3: Детальный расчёт для каждого кандидата
        Для каждого оставшегося кандидата:
          1. Рассчитать точное время всех этапов:
             - Выезд на завод: Время_начала - Путь_до_завода
             - Погрузка: 15-20 минут (зависит от объёма)
             - Путь к клиенту: Расстояние / Средняя_скорость × Коэффициент_пробок
             - Разгрузка: 2-3 минуты на м³
             - Возврат: Расстояние_обратно / Средняя_скорость
             
          2. Проверить пересечения с существующими слотами машины
          3. Рассчитать буферы:
             - Операционный буфер: 10% от времени в пути
             - Рисковый буфер: 5-15 минут (зависит от сложности объекта)
             
      Шаг 3.4: Оценка и выбор лучшего кандидата
        Оценочная_функция(Машина, Поставка) = 
          W1 × Оценка_времени + 
          W2 × Оценка_расстояния + 
          W3 × Оценка_использования + 
          W4 × Оценка_качества
        
        Где:
          Оценка_времени = 1 / (Время_начала - Идеальное_время_начала)²
          Оценка_расстояния = 1 / (Порожний_пробег + 1)
          Оценка_использования = Объём_поставки / Вместимость_машины
          Оценка_качества = Коэффициент_приоритета_клиента
          
        W1, W2, W3, W4 — настраиваемые веса (по умолчанию: 0.4, 0.3, 0.2, 0.1)
        
      Шаг 3.5: Назначение и фиксация
        Если найден подходящий кандидат:
          - Закрепляем поставку за машиной
          - Создаём слот с рассчитанными временами
          - Обновляем доступное время машины
          - Добавляем в расписание
        Иначе:
          - Перемещаем поставку в очередь проблемных
          - Записываем причину неудачи
       Шаг 3.6: Резервирование буфера. 
         После фиксации слота за ТС, следующий временной интервал длиной TIME_BUFFER_BETWEEN_SLOTS 
         блокируется и не доступен для других назначений этой же машины.
          

    Этап 4: Обработка поставок, требующих непрерывной заливки

    Особый алгоритм для заказов с непрерывной заливкой:


    Для каждого заказа с требованием непрерывности:
      1. Собрать все поставки этого заказа в группу
      2. Определить требуемый интервал между поставками (например, ≤30 минут)
      3. Найти машины, которые могут выполнить несколько поставок подряд:
         - Проверить возможность выполнения N поставок одной машиной
         - Рассчитать суммарное время и проверить попадание в окно клиента
         
      4. Если одной машиной невозможно:
         - Найти несколько машин с максимально близкими временами разгрузки
         - Синхронизировать их расписание для минимизации интервалов
         
      5. Провести корректировку:
         - Сдвинуть поставки для соблюдения интервала
         - Убедиться, что сдвиг не нарушает другие ограничения

    Этап 5: Пост-обработка и оптимизация

    Шаг 5.1: Балансировка нагрузки

    text
    Для каждой машины рассчитываем:
      Коэффициент_загрузки = Суммарное_время_работы / Длительность_смены
      
    Средний_коэффициент = SUM(Коэффициент_загрузки) / Количество_машин
    
    Для машин с Коэффициент_загрузки > Средний_коэффициент + 0.2:
      Попробовать перенести часть поставок на менее загруженные машины

    Шаг 5.2: Минимизация порожних пробегов


    Для каждой последовательности поставок у машины:
      Рассчитать маршрут: База → Завод → Объект1 → Завод → Объект2 → ...
      Оценить порожний пробег между объектами
      
    Попробовать переставить поставки в последовательности для:
      - Уменьшения расстояния между объектами
      - Группировки поставок по географическим зонам

    Шаг 5.3: Добавление защитных буферов


    Для каждого слота в расписании:
      Если поставка критичная или объект сложный:
        Добавить_буфер = 15-20 минут
      Иначе если время в пути > 60 минут:
        Добавить_буфер = 10% от времени в пути
      Иначе:
        Добавить_буфер = 5 минут
        
    Распределить буфер:
      - 50% добавить перед разгрузкой (на случай задержек в пути)
      - 50% добавить после разгрузки (на случай задержек на объекте)

    Этап 6: Обработка проблемных поставок

    Шаг 6.1: Анализ причин неудачи


    Типичные причины:
      1. Нехватка машин нужной вместимости
      2. Отсутствие временных окон
      3. Противоречивые ограничения
      4. Невозможность соблюсти время жизни бетона

    Шаг 6.2: Попытка перераспределения


    Для каждой проблемной поставки:
      1. Ослабить ограничения (если возможно):
         - Расширить временное окно
         - Разрешить использование машин большей вместимости
         - Увеличить максимальный интервал для непрерывной заливки
         
      2. Попробовать занять резервное время других машин
      3. Предложить разбить поставку на меньшие части

    Шаг 6.3: Эскалация к диспетчеру

    text
    Если автоматическое распределение невозможно:
      1. Сформировать отчёт с причинами
      2. Предложить варианты решения:
         - Добавить дополнительную машину
         - Перенести заказ на другую смену
         - Увеличить временное окно
         - Изменить параметры заказа
         
      3. Передать задачу диспетчеру для ручного решения

    Этап 7: Синхронизация с автобетононасосами (АБН)

    1. Для каждой поставки, требующей АБН, проверяется наличие забронированного слота у выбранного АБН.
    2. Если слот не забронирован, осуществляется поиск свободного АБН, который сможет прибыть на объект к расчетному времени разгрузки АБС.
    3. Время работы АБН привязывается ко времени начала разгрузки первой АБС на объекте.

    3. МАТЕМАТИЧЕСКАЯ ФОРМАЛИЗАЦИЯ

    Основная целевая функция:


    Минимизировать: Z = α×Z₁ + β×Z₂ + γ×Z₃ + δ×Z₄
    
    Где:
      Z₁ = Σ(Время_доставки - Идеальное_время)²  # Минимизация отклонений
      Z₂ = Σ(Порожний_пробег)                    # Минимизация пробегов
      Z₃ = Σ(1 - Коэффициент_заполнения)²        # Максимизация использования
      Z₄ = Σ(Штрафы_за_нарушение_ограничений)    # Минимизация нарушений

    Ограничения:


    1. ∀i: Время_начала_слота_i ≥ Время_окончания_слота_{i-1} + Время_перехода
    2. ∀i: Время_разгрузки_i ≤ Время_погрузки_i + Время_жизни_бетона
    3. ∀j: Σ(Объём_поставок_заказа_j) = Объём_заказа_j
    4. ∀k: Σ(Время_работы_машины_k) ≤ Длительность_смены
    5. ∀l,m: |Время_разгрузки_l - Время_разгрузки_m| ≤ Максимальный_интервал, 
       если l и m принадлежат одному заказу с непрерывной заливкой

    4. ПРИМЕР РАСЧЁТА ДЛЯ КОНКРЕТНОГО СЦЕНАРИЯ

    Исходные данные:

    Заказ 1:

    Расчёт алгоритма:

    Шаг 1: Разбиение заказа


    Оптимальное разбиение: 10 м³ + 5 м³
    Обоснование: Минимизация количества поставок (2 вместо 3)

    Шаг 2: Расчёт временных параметров для поставки 10 м³


    Время погрузки: 10 м³ × 3 мин/м³ = 30 минут
    Время в пути: 10 км / 40 км/ч = 15 минут
    Время разгрузки: 10 м³ × 2.5 мин/м³ = 25 минут
    Возврат: 10 км / 40 км/ч = 15 минут
    Итого: 30 + 15 + 25 + 15 = 85 минут
    Буфер: 10% = 9 минут
    Общее время слота: 94 минуты ≈ 1 час 34 минуты

    Шаг 3: Поиск подходящей машины


    Кандидаты по вместимости: машины 7 м³ и 10 м³
    Машина 1 (7 м³): не подходит - объём 10 > 7
    Машина 2 (7 м³): не подходит
    Машина 3 (10 м³): свободна с 07:00
    
    Расчёт для машины 3:
      Выезд с базы: 07:00
      Прибытие на завод: 07:15
      Погрузка: 07:15 - 07:45
      Путь к клиенту: 07:45 - 08:00
      Разгрузка: 08:00 - 08:25
      Возврат: 08:25 - 08:40
      
    Проверка ограничений:
      ✓ Попадание в окно клиента (08:00 ∈ [09:00-12:00] - требуется корректировка)
      ✓ Время жизни бетона: 08:00 - 07:45 = 15 минут < 90 минут
      ✓ Конец слота 08:40 < конец смены 19:00

    Шаг 4: Корректировка времени


    Требуется сдвинуть на 1 час позже:
      Погрузка: 08:15 - 08:45
      Разгрузка: 09:00 - 09:25 (попадает в окно)
      
    Обновлённый слот: 08:00 - 09:40

    Шаг 5: Аналогичный расчёт для поставки 5 м³


    Выбираем машину 5 м³, начинаем на 30 минут позже первой поставки
    для соблюдения непрерывной заливки:
      Разгрузка: 09:25 - 09:40 (интервал 0 минут ✓)

    5. ОСОБЕННОСТИ РЕАЛИЗАЦИИ

    5.1. Эвристические правила, используемые в алгоритме:

    1. Правило ближайшего соседа:

    2. Правило заполнения до конца:

    3. Правило резервирования VIP:

    4. Правило избегания пиков:

    5.2. Обработка конфликтов и тупиковых ситуаций:

    Тупиковая ситуация: Нельзя назначить поставку без нарушения ограничений

    Стратегии выхода:

    1. Откат (Backtracking): Отменить последнее назначение и попробовать другой вариант

    2. Ослабление ограничений: Временно разрешить небольшое нарушение

    3. Разделение проблемы: Разделить сложную поставку на части

    4. Перенос на потом: Отложить решение до обработки других поставок

    5.3. Оптимизационные улучшения:

    1. Локальный поиск: После построения расписания пытаться улучшить его небольшими изменениями

    2. Табу-поиск: Запрещать возврат к недавно рассмотренным решениям

    3. Имитация отжига: Иногда разрешать ухудшения для выхода из локальных оптимумов

    6. ПРОИЗВОДИТЕЛЬНОСТЬ И МАСШТАБИРУЕМОСТЬ

    Временная сложность:


    O(N × M × K × L)
    Где:
      N - количество поставок
      M - количество машин
      K - среднее количество временных окон у машины
      L - сложность проверки ограничений
    
    Для типичного сценария (50 поставок, 10 машин):
      ≈ 50 × 10 × 5 × 10 = 25,000 операций
      Время выполнения: ~2-3 секунды

    Память:


    Требуется хранить:
      - Матрицу назначений: N × M элементов
      - Расписание каждой машины: M списков по ~10 элементов
      - Кэш рассчитанных расстояний: ~N² элементов
      
    Общий объём: O(N² + M × N) ≈ 100-500 КБ для типичного сценария

    7. ВАЛИДАЦИЯ И ТЕСТИРОВАНИЕ АЛГОРИТМА

    Тестовые сценарии:

    1. Идеальный случай: Все поставки могут быть назначены без конфликтов

    2. Предельная загрузка: Количество поставок на пределе возможностей парка

    3. Конфликт ограничений: Противоречивые требования клиентов

    4. Каскадный сбой: Отказ ключевой машины в начале смены

    Метрики качества расписания:

    text
    1. Коэффициент использования машин: Должен быть 70-90%
    2. Среднее отклонение от идеального времени: Должно быть < 30 минут
    3. Количество нарушений ограничений: Должно быть 0
    4. Суммарный порожний пробег: Минимизируется
    5. Баланс загрузки: Разница загрузки машин < 20%

    8. ИНТЕГРАЦИЯ С ДРУГИМИ СИСТЕМАМИ

    Алгоритм получает данные из:

    1. CRM-системы — информация о клиентах и их приоритетах

    2. ERP-системы — данные о заказах и производственных мощностях

    3. GPS-трекера — текущее положение машин

    4. Картографических сервисов — актуальные данные о пробках и маршрутах

    5. Погодных сервисов — прогноз для корректировки времени в пути

    9. НАСТРОЙКА И КАЛИБРОВКА

    Алгоритм имеет настраиваемые параметры:

    python
    # Веса в оценочной функции
    WEIGHTS = {
        'time_deviation': 0.4,      # Важность соблюдения времени
        'distance': 0.3,            # Важность минимизации пробега
        'capacity_utilization': 0.2, # Важность заполнения машины
        'client_priority': 0.1      # Важность приоритета клиента
    }
    
    # Буферы и допуски
    TOLERANCES = {
        'time_window': 15,          # Допустимое отклонение от окна (мин)
        'continuous_pouring': 5,    # Допуск по интервалу непрерывности (мин)
        'concrete_lifetime': 10     # Запас по времени жизни бетона (мин)
    }
    
    # Стратегические параметры
    STRATEGY = {
        'reserve_vip_capacity': 0.1, # Резерв для VIP-клиентов (10%)
        'max_backtracking_depth': 3, # Макс. глубина отката
        'optimization_iterations': 100 # Количество итераций оптимизации
    }

    10. ПРАКТИЧЕСКИЕ РЕКОМЕНДАЦИИ ПО РЕАЛИЗАЦИИ

    1. Начинать с простого: Сначала реализовать базовый жадный алгоритм без оптимизации

    2. Добавлять сложность постепенно: Поэтапно вводить дополнительные ограничения и оптимизации

    3. Использовать кэширование: Кэшировать результаты расчёта расстояний и проверок

    4. Реализовать инкрементальное обновление: При изменении одной поставки не пересчитывать всё расписание

    5. Предусмотреть ручное вмешательство: Возможность диспетчера корректировать автоматические решения

    ЗАКЛЮЧЕНИЕ

    Алгоритм первоначального распределения представляет собой сбалансированную комбинацию эвристических правил и оптимизационных техник, которая позволяет быстро строить качественное расписание даже для сложных сценариев. Его ключевые преимущества:

    1. Гарантированное нахождение решения (если оно существует)

    2. Учёт всех технологических ограничений цементной логистики

    3. Баланс между оптимальностью и скоростью работы

    4. Возможность каскадной обработки — сначала сложные, потом простые задачи

    5. Интеграция с системами реального времени для учёта текущей обстановки

    Алгоритм является основой всей системы планирования и обеспечивает стабильную работу даже в условиях высокой нагрузки и неопределённости.


    Приложение №2

    ДЕТАЛЬНОЕ ОПИСАНИЕ АЛГОРИТМА КАСКАДНОГО ПЕРЕРАСЧЕТА

    1. ФИЛОСОФИЯ АЛГОРИТМА: УПРАВЛЕНИЕ ХАОСОМ В РЕАЛЬНОМ ВРЕМЕНИ

    Алгоритм каскадного перерасчёта — это интеллектуальная система экстренного реагирования, которая превращает хаотичные сбои в управляемые изменения расписания. Он работает по принципу "минимального необходимого вмешательства" — как хирург, который делает точечные разрезы, а не ампутирует конечности. Алгоритм реализует полуавтоматический подход. Система выполняет анализ, изоляцию проблемы и генерацию вариантов решений, но финальное утверждение любого изменения расписания всегда остаётся за диспетчером. Это особенно важно при обработке таких событий, как отклонение заказа водителем или ручное снятие ТС с линии.

    Ментальная модель: Представьте домино. Одно падающее костяшка (сбой) может уронить всю цепь (расписание). Наш алгоритм — это рука, которая подхватывает упавшие костяшки и расставляет их на новые места, не давая упасть остальным.

    2. КЛАССИФИКАЦИЯ СОБЫТИЙ И СТРАТЕГИИ РЕАКЦИИ

    2.1. Матрица событий и их каскадных эффектов:



    Событие Непосредственный эффект Каскадный эффект Время реакции Приоритет обработки
    Полный отказ машины Машина выбывает до конца смены Все будущие слоты освобождаются 0-2 минуты Критический (1)
    Частичная поломка Машина встанет на 2-4 часа Слоты в периоде простоя сдвигаются 2-5 минут Высокий (2)
    ДТП без пострадавших Машина задержана на 1-3 часа Текущая поставка отменяется, следующие сдвигаются 5-10 минут Высокий (2)
    Задержка на разгрузке Текущий слот удлиняется на X минут Последующие слоты этой машины сдвигаются 1-3 минуты Средний (3)
    Пробки на маршруте Увеличение времени в пути на Y% Риск превышения времени жизни бетона 2-5 минут Средний (3)
    Отмена заказа клиентом Освобождается N слотов Появляется резерв для других поставок 1-2 минуты Низкий (4)
    Новая доступная машина Увеличивается мощность парка Возможность оптимизации нагрузки 3-7 минут Низкий (4)
    Отклонение заказа водителем Освобождается конкретный слот у конкретного ТС. Сдвиг последующих слотов этого ТС. Риск срыва времени доставки. 1-3 мин Высокий (2)
    Изменение статуса ТС "На линии" на "Нет" ТС исключается из пула доступных. Все будущие слоты этой ТС освобождаются. Требуется перераспределение. 2-5 мин Критический (1)

    2.2. Жизненный цикл обработки события:

    text
    1. ДЕТЕКЦИЯ (0-30 секунд)
       │
    2. ВАЛИДАЦИЯ (30-60 секунд)
       │
    3. ОЦЕНКА ВОЗДЕЙСТВИЯ (60-90 секунд)
       │
    4. РАЗРАБОТКА СТРАТЕГИИ (90-120 секунд)
       │
    5. ВЫПОЛНЕНИЕ ПЕРЕРАСЧЕТА (120-300 секунд)
       │
    6. ВАЛИДАЦИЯ РЕЗУЛЬТАТА (300-360 секунд)
       │
    7. УВЕДОМЛЕНИЕ СТОРОН (360-420 секунд)

    3. ДЕТАЛЬНЫЙ АЛГОРИТМ ДЛЯ КРИТИЧЕСКОГО СОБЫТИЯ: ПОЛНАЯ ПОЛОМКА МАШИНЫ

    ФАЗА 1: ЭКСПРЕСС-ДИАГНОСТИКА (0-60 секунд)

    python
    def emergency_assessment(event):
        """
        Молниеносная оценка ситуации
        """
        # Шаг 1.1: Определение точки невозврата
        breakdown_time = event.occurred_at
        truck = event.related_truck
        
        # Какие поставки УЖЕ НЕВОЗМОЖНО выполнить
        immediate_impact = []
        for slot in truck.schedule:
            if slot.start_time <= breakdown_time <= slot.end_time:
                # Текущая поставка - экстренная ситуация
                current_delivery = slot.delivery
                if current_delivery.is_in_transit():
                    # Бетон уже в пути - критично!
                    cement_age = breakdown_time - current_delivery.mixing_start_time
                    remaining_lifetime = current_delivery.concrete_lifetime - cement_age
                    
                    if remaining_lifetime < TimeSpan.FromMinutes(30):
                        # Бетон скоро схватится - нужна срочная замена
                        immediate_impact.append({
                            'delivery': current_delivery,
                            'type': 'CRITICAL_CEMENT_AT_RISK',
                            'remaining_minutes': remaining_lifetime.total_minutes,
                            'required_action': 'IMMEDIATE_REASSIGNMENT'
                        })
        
        # Шаг 1.2: Быстрая инвентаризация ресурсов
        available_trucks = get_available_trucks(
            from_time=breakdown_time,
            min_capacity=5,  # минимальная вместимость
            proximity_to_plant=True
        )
        
        # Шаг 1.3: Расчет окна возможностей
        opportunity_window = calculate_opportunity_window(
            broken_truck=truck,
            available_trucks=available_trucks,
            time_now=breakdown_time
        )
        
        return {
            'immediate_crises': immediate_impact,
            'available_resources': available_trucks,
            'window_of_opportunity': opportunity_window,
            'severity_level': calculate_severity(immediate_impact)
        }

    ФАЗА 2: СТРАТЕГИЧЕСКОЕ ПЛАНИРОВАНИЕ (60-120 секунд)

    python
    def develop_rescue_strategy(assessment):
        """
        Разработка многоуровневой стратегии спасения
        """
        strategy = {
            'phase_1': [],  # Действия в ближайшие 15 минут
            'phase_2': [],  # Действия в ближайший час
            'phase_3': [],  # Действия на оставшуюся смену
            'fallback_plans': []  # Резервные варианты
        }
        
        # УРОВЕНЬ 1: Спасение бетона, который уже в пути
        for crisis in assessment['immediate_crises']:
            if crisis['type'] == 'CRITICAL_CEMENT_AT_RISK':
                rescue_plan = create_cement_rescue_plan(
                    delivery=crisis['delivery'],
                    time_limit=crisis['remaining_minutes'],
                    available_trucks=assessment['available_resources']
                )
                
                if rescue_plan['feasible']:
                    strategy['phase_1'].append({
                        'action': 'EMERGENCY_TRANSFER',
                        'plan': rescue_plan,
                        'priority': 'CRITICAL'
                    })
                else:
                    # Невозможно спасти - план минимизации убытков
                    strategy['phase_1'].append({
                        'action': 'DAMAGE_CONTROL',
                        'plan': create_damage_control_plan(crisis['delivery']),
                        'priority': 'CRITICAL'
                    })
        
        # УРОВЕНЬ 2: Перераспределение ближайших поставок (15-60 минут)
        immediate_slots = get_immediate_slots(
            truck=assessment['broken_truck'],
            time_horizon=TimeSpan.FromHours(2),
            from_time=assessment['breakdown_time']
        )
        
        for slot in immediate_slots:
            reassignment_plan = create_slot_reassignment_plan(
                slot=slot,
                available_trucks=assessment['available_resources'],
                strategy='NEAREST_FIT'
            )
            
            strategy['phase_2'].append({
                'action': 'SLOT_REASSIGNMENT',
                'slot': slot,
                'plan': reassignment_plan,
                'priority': 'HIGH' if slot.delivery.client.priority >= 8 else 'MEDIUM'
            })
        
        # УРОВЕНЬ 3: Оптимизация оставшейся смены
        remaining_slots = get_remaining_slots(
            truck=assessment['broken_truck'],
            from_time=assessment['breakdown_time'] + TimeSpan.FromHours(2)
        )
        
        optimization_strategy = create_optimization_strategy(
            slots=remaining_slots,
            available_trucks=assessment['available_resources'],
            time_constraints=assessment['window_of_opportunity']
        )
        
        strategy['phase_3'] = optimization_strategy
        
        # Резервные планы
        strategy['fallback_plans'] = create_fallback_plans(strategy)
        
        return strategy

    ФАЗА 3: КАСКАДНЫЙ ПЕРЕРАСЧЕТ С УПРАВЛЯЕМЫМ РАСПРОСТРАНЕНИЕМ

    python
    def execute_cascade_reschedule(strategy, original_schedule):
        """
        Выполнение каскадного перерасчета с контролем распространения
        """
        # Шаг 3.1: Создание "песочницы" для экспериментов
        sandbox_schedule = original_schedule.clone()
        change_log = []
        
        # Шаг 3.2: Последовательное выполнение фаз стратегии
        for phase_name in ['phase_1', 'phase_2', 'phase_3']:
            phase_actions = strategy[phase_name]
            
            for action in phase_actions:
                result = execute_action_in_sandbox(
                    action=action,
                    schedule=sandbox_schedule,
                    change_log=change_log
                )
                
                if not result['success']:
                    # Действие не удалось - активируем механизм компенсации
                    compensation = create_compensation_plan(
                        failed_action=action,
                        failure_reason=result['reason'],
                        current_state=sandbox_schedule
                    )
                    
                    # Пытаемся выполнить компенсацию
                    compensation_result = execute_compensation(
                        compensation_plan=compensation,
                        schedule=sandbox_schedule
                    )
                    
                    if not compensation_result['success']:
                        # Каскадная неудача - эскалация
                        escalate_to_human(
                            action=action,
                            compensation=compensation,
                            current_state=sandbox_schedule
                        )
        
        # Шаг 3.3: Проверка целостности нового расписания
        integrity_check = validate_schedule_integrity(sandbox_schedule)
        
        if integrity_check['valid']:
            # Шаг 3.4: Постепенное применение изменений
            final_schedule = apply_changes_gradually(
                original=original_schedule,
                new=sandbox_schedule,
                change_log=change_log
            )
            
            return {
                'success': True,
                'schedule': final_schedule,
                'changes_made': len(change_log),
                'cascade_depth': calculate_cascade_depth(change_log),
                'estimated_impact': calculate_impact_metrics(change_log)
            }
        else:
            # Не удалось построить валидное расписание
            return {
                'success': False,
                'reason': integrity_check['violations'],
                'fallback_activated': True
            }

    4. АЛГОРИТМ ПЕРЕРАСЧЕТА ПРИ ЗАДЕРЖКЕ НА РАЗГРУЗКЕ

    Специфика: Задержка вызывает цепную реакцию, но не требует смены машины.

    python
    def handle_unloading_delay(event):
        """
        Обработка задержки на разгрузке с минимизацией каскадного эффекта
        """
        # Шаг 1: Точное измерение задержки
        delay = event.delay_minutes
        affected_slot = event.related_slot
        truck = affected_slot.truck
        
        # Шаг 2: Анализ "эластичности" временных окон
        elasticity_analysis = analyze_time_elasticity(
            slot=affected_slot,
            requested_delay=delay
        )
        
        # Шаг 3: Многоуровневая стратегия поглощения задержки
        
        # Уровень 1: Поглощение за счет внутренних резервов
        if elasticity_analysis['can_absorb_without_shift']:
            # Задержку можно поглотить за счет буферов
            adjusted_schedule = absorb_delay_internally(
                schedule=current_schedule,
                slot=affected_slot,
                delay=delay
            )
            
        # Уровень 2: Минимальный сдвиг с компенсацией
        elif elasticity_analysis['can_shift_with_minimal_impact']:
            # Находим оптимальную точку сдвига
            shift_point = find_optimal_shift_point(
                truck_schedule=truck.schedule,
                delayed_slot=affected_slot,
                delay=delay
            )
            
            # Выполняем "умный" сдвиг
            adjusted_schedule = execute_smart_shift(
                schedule=current_schedule,
                shift_from=shift_point,
                shift_amount=delay,
                strategy='MINIMAL_DISTURBANCE'
            )
            
        # Уровень 3: Каскадный перерасчет с изоляцией
        else:
            # Изолируем область воздействия
            impact_zone = isolate_impact_zone(
                schedule=current_schedule,
                epicenter=affected_slot,
                max_ripple_hours=2  # Ограничиваем распространение 2 часами
            )
            
            # Перераспределяем в изолированной зоне
            adjusted_schedule = reschedule_within_zone(
                schedule=current_schedule,
                impact_zone=impact_zone,
                delay_source=affected_slot
            )
        
        # Шаг 4: Проверка и компенсация
        compensation_needed = check_for_compensation_needs(adjusted_schedule)
        
        if compensation_needed:
            adjusted_schedule = apply_compensation_measures(
                schedule=adjusted_schedule,
                compensation_plan=compensation_needed
            )
        
        return adjusted_schedule

    5. ИНТЕЛЛЕКТУАЛЬНЫЙ МЕХАНИЗМ ПРЕДОТВРАЩЕНИЯ КАСКАДНЫХ СБОЕВ

    5.1. Алгоритм "Умной изоляции":

    python
    def intelligent_impact_isolation(schedule, failure_point):
        """
        Изоляция области воздействия сбытия с минимизацией распространения
        """
        # Шаг 1: Определение "эпицентра" и "эпицентральной зоны"
        epicenter = failure_point
        epicentral_zone = calculate_epicentral_zone(
            epicenter=epicenter,
            radius_hours=1,  # Первый час - максимальное воздействие
            schedule=schedule
        )
        
        # Шаг 2: Идентификация "амортизаторов" - поставок, которые могут сдвинуться без последствий
        shock_absorbers = identify_shock_absorbers(
            schedule=schedule,
            around_zone=epicentral_zone,
            buffer_size=2  # 2 слота в каждую сторону
        )
        
        # Шаг 3: Создание "защитного периметра"
        protective_perimeter = create_protective_perimeter(
            schedule=schedule,
            inner_zone=epicentral_zone,
            outer_radius_hours=3
        )
        
        # Шаг 4: Расчет "коэффициента упругости" для каждого слота
        elasticity_map = calculate_slot_elasticity(
            schedule=schedule,
            zone=epicentral_zone + protective_perimeter
        )
        
        return {
            'epicenter': epicenter,
            'epicentral_zone': epicentral_zone,
            'shock_absorbers': shock_absorbers,
            'protective_perimeter': protective_perimeter,
            'elasticity_map': elasticity_map,
            'isolation_strategy': 'ADAPTIVE_CONTAINMENT'
        }

    5.2. Алгоритм "Приоритетного спасения":

    python
    def priority_based_rescue(schedule, affected_slots):
        """
        Спасение поставок по приоритету с учетом многомерных критериев
        """
        # Многомерная оценка критичности каждой поставки
        criticality_scores = []
        
        for slot in affected_slots:
            score = calculate_multidimensional_criticality(
                slot=slot,
                dimensions=[
                    'CLIENT_PRIORITY',      # Важность клиента
                    'CEMENT_VIABILITY',     # Риск схватывания бетона
                    'CONTRACT_PENALTY',     # Штрафы за срыв
                    'LOGISTIC_COMPLEXITY',  # Сложность замены
                    'IMPACT_ON_OTHERS'      # Влияние на другие поставки
                ]
            )
            
            criticality_scores.append({
                'slot': slot,
                'score': score,
                'rescue_window': calculate_rescue_window(slot),
                'rescue_options': find_rescue_options(slot, schedule)
            })
        
        # Сортировка по критичности и доступности вариантов спасения
        criticality_scores.sort(
            key=lambda x: (
                -x['score']['overall'],  # По убыванию критичности
                len(x['rescue_options']),  # По количеству вариантов
                x['rescue_window']['minutes_remaining']  # По оставшемуся времени
            )
        )
        
        # Последовательное спасение
        rescued_slots = []
        failed_rescues = []
        
        for item in criticality_scores:
            rescue_result = attempt_slot_rescue(
                slot=item['slot'],
                options=item['rescue_options'],
                schedule=schedule,
                time_limit=item['rescue_window']['minutes_remaining']
            )
            
            if rescue_result['success']:
                rescued_slots.append({
                    'slot': item['slot'],
                    'new_assignment': rescue_result['new_assignment']
                })
                schedule = rescue_result['updated_schedule']
            else:
                failed_rescues.append({
                    'slot': item['slot'],
                    'reason': rescue_result['failure_reason'],
                    'suggested_actions': rescue_result['suggestions']
                })
        
        return {
            'rescued_count': len(rescued_slots),
            'failed_count': len(failed_rescues),
            'rescued_slots': rescued_slots,
            'failed_rescues': failed_rescues,
            'final_schedule': schedule,
            'rescue_efficiency': len(rescued_slots) / len(affected_slots)
        }

    6. АЛГОРИТМ ОБРАБОТКИ МНОЖЕСТВЕННЫХ СБОЕВ

    Сценарий: Одновременная поломка 2 машин + задержка на 3 объектах

    python
    def handle_multiple_failures(failure_events):
        """
        Обработка множественных одновременных сбоев
        """
        # Шаг 1: Кластеризация сбоев по времени и местоположению
        failure_clusters = cluster_failures(
            events=failure_events,
            time_threshold=TimeSpan.FromMinutes(30),
            location_threshold=5  # км
        )
        
        # Шаг 2: Определение доминирующего сбоя в каждом кластере
        dominant_failures = []
        for cluster in failure_clusters:
            dominant = find_dominant_failure(cluster)
            dominant_failures.append(dominant)
        
        # Шаг 3: Последовательная обработка доминирующих сбоев
        # с учетом их взаимного влияния
        current_schedule = get_current_schedule()
        
        for i, failure in enumerate(dominant_failures):
            # Учитываем изменения, внесенные обработкой предыдущих сбоев
            impact_forecast = forecast_cross_impact(
                failure=failure,
                already_handled=dominant_failures[:i],
                current_schedule=current_schedule
            )
            
            # Адаптивная стратегия с учетом прогноза
            strategy = adapt_strategy_based_on_forecast(
                base_strategy=get_base_strategy(failure),
                impact_forecast=impact_forecast,
                remaining_failures=dominant_failures[i+1:]
            )
            
            # Выполнение с компенсацией побочных эффектов
            result = execute_with_side_effect_compensation(
                strategy=strategy,
                schedule=current_schedule,
                compensation_buffer=TimeSpan.FromMinutes(15)
            )
            
            current_schedule = result['schedule']
            
            # Если достигнут предел устойчивости - остановка
            if result['system_stability'] < 0.6:  # Порог устойчивости
                emergency_stabilization(current_schedule)
                break
        
        # Шаг 4: Глобальная оптимизация после экстренных мер
        optimized_schedule = global_post_crisis_optimization(
            schedule=current_schedule,
            optimization_criteria=['STABILITY', 'FAIRNESS', 'EFFICIENCY']
        )
        
        return optimized_schedule

    7. МЕХАНИЗМЫ КОМПЕНСАЦИИ И ВОССТАНОВЛЕНИЯ

    7.1. Алгоритм распределения компенсационных буферов:

    python
    def redistribute_compensation_buffers(schedule, stress_points):
        """
        Перераспределение временных буферов для компенсации сбоев
        """
        # Шаг 1: Идентификация "доноров" буферов
        buffer_donors = find_buffer_donors(
            schedule=schedule,
            criteria=[
                'HAS_EXCESS_BUFFER',      # Имеет избыточный буфер
                'LOW_RISK_PROFILE',       # Низкий риск сбоев
                'FLEXIBLE_TIME_WINDOW',   # Гибкое временное окно
                'NON_CRITICAL_CLIENT'     # Не критичный клиент
            ]
        )
        
        # Шаг 2: Идентификация "реципиентов" буферов
        buffer_recipients = find_buffer_recipients(
            schedule=schedule,
            stress_points=stress_points,
            criteria=[
                'HIGH_RISK_PROFILE',      # Высокий риск
                'TIGHT_TIME_WINDOW',      # Жесткое окно
                'CRITICAL_CLIENT',        # Важный клиент
                'MINIMAL_BUFFER'          # Мало буфера
            ]
        )
        
        # Шаг 3: Оптимальное перераспределение
        redistribution_plan = optimize_buffer_redistribution(
            donors=buffer_donors,
            recipients=buffer_recipients,
            schedule=schedule,
            objective='MAXIMIZE_SYSTEM_STABILITY'
        )
        
        # Шаг 4: Поэтапное применение
        new_schedule = apply_buffer_redistribution(
            schedule=schedule,
            plan=redistribution_plan,
            phase_duration=TimeSpan.FromMinutes(5)  # Постепенно, по 5 минут
        )
        
        return new_schedule

    7.2. Алгоритм восстановления после каскадного сбоя:

    python
    def post_cascade_recovery(schedule, cascade_event):
        """
        Восстановление системы после серьезного каскадного сбоя
        """
        recovery_phases = [
            {
                'name': 'СТАБИЛИЗАЦИЯ',
                'duration': TimeSpan.FromMinutes(30),
                'objectives': ['STOP_CASCADE', 'PROTECT_CRITICAL', 'ISOLATE_DAMAGE']
            },
            {
                'name': 'ВОССТАНОВЛЕНИЕ',
                'duration': TimeSpan.FromHours(2),
                'objectives': ['RESTORE_CAPACITY', 'REASSIGN_URGENT', 'OPTIMIZE_LOCALLY']
            },
            {
                'name': 'ОПТИМИЗАЦИЯ',
                'duration': TimeSpan.FromHours(4),
                'objectives': ['GLOBAL_OPTIMIZATION', 'LOAD_BALANCING', 'BUFFER_RESTORATION']
            }
        ]
        
        recovered_schedule = schedule
        
        for phase in recovery_phases:
            phase_strategy = create_recovery_strategy(
                phase=phase,
                current_state=recovered_schedule,
                cascade_history=cascade_event.history
            )
            
            phase_result = execute_recovery_phase(
                strategy=phase_strategy,
                schedule=recovered_schedule,
                time_limit=phase['duration']
            )
            
            recovered_schedule = phase_result['schedule']
            
            # Проверка готовности к следующей фазе
            if not phase_result['phase_successful']:
                # Расширяем текущую фазу
                phase['duration'] += TimeSpan.FromMinutes(15)
                continue
            
            # Мониторинг прогресса восстановления
            recovery_metrics = calculate_recovery_metrics(
                original=schedule,
                current=recovered_schedule,
                phase=phase['name']
            )
            
            if recovery_metrics['stability_index'] >= 0.8:
                # Достигнута достаточная стабильность - переход к следующей фазе
                continue
            else:
                # Требуется дополнительное время
                phase['duration'] += TimeSpan.FromMinutes(30)
        
        # Финальная проверка
        final_validation = validate_recovery_completion(
            original_schedule=schedule,
            recovered_schedule=recovered_schedule,
            cascade_event=cascade_event
        )
        
        if final_validation['recovery_successful']:
            return {
                'status': 'FULL_RECOVERY',
                'schedule': recovered_schedule,
                'recovery_time': final_validation['total_recovery_time'],
                'efficiency_loss': final_validation['efficiency_loss_percentage']
            }
        else:
            return {
                'status': 'PARTIAL_RECOVERY',
                'schedule': recovered_schedule,
                'unresolved_issues': final_validation['remaining_issues'],
                'human_intervention_required': True
            }

    8. ИНТЕЛЛЕКТУАЛЬНАЯ СИСТЕМА ПРИНЯТИЯ РЕШЕНИЙ

    8.1. Нейросетевая оценка вариантов перерасчета:

    text
    Архитектура нейросети для оценки вариантов:
    
    Входной слой (12 параметров):
    1. Количество затронутых поставок
    2. Средний приоритет клиентов
    3. Общий объем риска (м³×приоритет)
    4. Временной горизонт воздействия
    5. Количество доступных машин
    6. Среднее расстояние до объектов
    7. Коэффициент заполнения машин
    8. Запас по времени жизни бетона
    9. Сложность логистики (1-10)
    10. Погодные условия (индекс)
    11. Дорожная ситуация (индекс)
    12. Историческая надежность варианта
    
    Скрытые слои (3 слоя по 8 нейронов):
    - Анализ взаимосвязей параметров
    - Выявление неочевидных паттернов
    - Оценка рисков и возможностей
    
    Выходной слой (4 оценки):
    1. Вероятность успеха (0-1)
    2. Ожидаемое время восстановления (минуты)
    3. Прогнозируемые потери (денежные)
    4. Качество расписания после (0-100)

    8.2. Алгоритм принятия решений на основе ML:

    python
    def ml_enhanced_decision_making(options, historical_data):
        """
        Принятие решений с использованием машинного обучения
        """
        # Шаг 1: Извлечение признаков для каждого варианта
        feature_vectors = []
        for option in options:
            features = extract_features(option)
            feature_vectors.append(features)
        
        # Шаг 2: Прогнозирование результатов с помощью обученной модели
        predictions = prediction_model.predict(feature_vectors)
        
        # Шаг 3: Многокритериальная оптимизация
        optimal_option = multi_criteria_optimization(
            options=options,
            predictions=predictions,
            criteria_weights={
                'success_probability': 0.35,
                'recovery_time': 0.25,
                'financial_impact': 0.20,
                'schedule_quality': 0.20
            }
        )
        
        # Шаг 4: Проверка на аномалии
        anomaly_check = detect_anomalies(
            selected_option=optimal_option,
            historical_patterns=historical_data
        )
        
        if anomaly_check['is_anomaly']:
            # Выбор второго лучшего варианта
            optimal_option = get_second_best(
                options=options,
                predictions=predictions
            )
        
        return optimal_option

    9. СИСТЕМА МОНИТОРИНГА И ПРОГНОЗИРОВАНИЯ КАСКАДОВ

    9.1. Алгоритм раннего предупреждения:

    python
    def early_cascade_warning_system(schedule):
        """
        Система раннего предупреждения о потенциальных каскадных сбоях
        """
        # Шаг 1: Поиск "точек напряжения" в расписании
        stress_points = find_schedule_stress_points(
            schedule=schedule,
            indicators=[
                'BACK_TO_BACK_SLOTS',      # Слоты без буфера
                'TIGHT_TIME_WINDOWS',      # Жесткие окна клиентов
                'HIGH_RISK_COMBINATIONS',  # Рискованные комбинации поставок
                'OVERLOADED_TRUCKS',       # Перегруженные машины
                'GEOGRAPHIC_CONFLICTS'     # Географические конфликты
            ]
        )
        
        # Шаг 2: Расчет "индекса каскадного риска" для каждой точки
        cascade_risk_index = {}
        for point in stress_points:
            risk_score = calculate_cascade_risk(
                stress_point=point,
                schedule=schedule,
                historical_failures=load_failure_history()
            )
            
            cascade_risk_index[point] = risk_score
        
        # Шаг 3: Прогнозирование потенциальных каскадов
        potential_cascades = forecast_potential_cascades(
            stress_points=stress_points,
            risk_index=cascade_risk_index,
            simulation_count=1000  # Монте-Карло симуляция
        )
        
        # Шаг 4: Генерация превентивных рекомендаций
        preventive_recommendations = generate_preventive_recommendations(
            potential_cascades=potential_cascades,
            schedule=schedule,
            intervention_cost_threshold=10000  # Максимальная стоимость вмешательства
        )
        
        return {
            'high_risk_points': cascade_risk_index,
            'potential_cascades': potential_cascades,
            'preventive_actions': preventive_recommendations,
            'overall_risk_level': calculate_overall_risk_level(cascade_risk_index)
        }

    10. ПРАКТИЧЕСКАЯ РЕАЛИЗАЦИЯ: ОГРАНИЧЕНИЯ И ОПТИМИЗАЦИИ

    10.1. Ограничения реального времени:

    python
    def real_time_constrained_rescheduling(event, time_budget):
        """
        Перерасчет с жестким ограничением по времени
        """
        # Шаг 1: Быстрая оценка сложности
        complexity = estimate_rescheduling_complexity(event)
        
        # Шаг 2: Выбор стратегии в зависимости от доступного времени
        if time_budget < TimeSpan.FromMinutes(2):
            strategy = 'ULTRA_FAST_HEURISTIC'
            max_iterations = 100
            search_depth = 1
        elif time_budget < TimeSpan.FromMinutes(5):
            strategy = 'FAST_LOCAL_SEARCH'
            max_iterations = 1000
            search_depth = 2
        elif time_budget < TimeSpan.FromMinutes(10):
            strategy = 'BALANCED_OPTIMIZATION'
            max_iterations = 5000
            search_depth = 3
        else:
            strategy = 'COMPREHENSIVE_SEARCH'
            max_iterations = 20000
            search_depth = 4
        
        # Шаг 3: Адаптивный алгоритм с контролем времени
        start_time = datetime.now()
        best_solution = None
        
        while (datetime.now() - start_time) < time_budget:
            candidate = generate_candidate_solution(
                event=event,
                strategy=strategy,
                current_best=best_solution
            )
            
            candidate_score = evaluate_solution(candidate)
            
            if best_solution is None or candidate_score > best_solution['score']:
                best_solution = {
                    'schedule': candidate,
                    'score': candidate_score,
                    'time_spent': datetime.now() - start_time
                }
            
            # Проверка прогресса
            if should_terminate_early(
                current_best=best_solution,
                time_remaining=time_budget - (datetime.now() - start_time),
                progress_rate=calculate_progress_rate()
            ):
                break
        
        return best_solution['schedule']

    10.2. Балансировка качества и скорости:

    Режим работы Время на перерасчет Качество решения Используется когда
    Экстренный 30-60 секунд 60-70% от оптимального Поломка в час пик, риск схватывания бетона
    Оперативный 2-5 минут 75-85% от оптимального Стандартные сбои в рабочее время
    Тщательный 5-10 минут 85-95% от оптимального Плановые изменения, оптимизация
    Аналитический 10-20 минут 95-99% от оптимального Анализ после смены, обучение системы

    11. АЛГОРИТМ ОБРАБОТКИ ОТКЛОНЕНИЯ ЗАКАЗА ВОДИТЕЛЕМ

    1. Детекция: Система получает событие от мобильного приложения: {водитель_id, заказ_id, причина, timestamp}.

    2. Изоляция: Заказ переводится в статус Отклонён. Его слот у конкретного ТС освобождается.

    3. Анализ воздействия: Определяются все последующие слоты этого же ТС. Они переводятся в статус На перепланировании.

    4. Генерация вариантов (без автоматического применения):

    5. Уведомление диспетчера: Система подсвечивает на диаграмме Ганта отклонённый заказ и затронутые слоты. Диспетчеру предлагаются сгенерированные варианты.

    6. Утверждение: Диспетчер выбирает вариант, вносит при необходимости ручные корректировки и явно подтверждает новое расписание.

    12. АЛГОРИТМ ОБРАБОТКИ ИЗМЕНЕНИЯ СТАТУСА ТС "НА ЛИНИИ"

    1. Инициация: Диспетчер вручную изменяет значение поля На линии на Нет для конкретного ТС в справочнике.

    2. Валидация: Система проверяет, есть ли у данного ТС назначенные, но ещё не начатые заказы (слоты) на будущее время.

    3. Изоляция и эскалация: Если такие заказы есть:

    ЗАКЛЮЧЕНИЕ: ФИЛОСОФИЯ УПРАВЛЕНИЯ ХАОСОМ

    Алгоритм каскадного перерасчета — это живая, адаптивная система, которая эволюционирует с каждым обработанным сбоем. Её ключевые принципы:

    1. Принцип минимального вмешательства: Менять только то, что необходимо

    2. Принцип управляемого распространения: Контролировать границы воздействия

    3. Принцип приоритетного спасения: Сначала важное, потом остальное

    4. Принцип обучаемости: Каждый сбой делает систему умнее

    5. Принцип отказоустойчивости: Всегда иметь план Б, В и Г

    Итоговая формула успеха:

    text
    Эффективность_перерасчета = 
      (Скорость_реакции × 0.3) + 
      (Качество_решения × 0.4) + 
      (Минимизация_изменений × 0.2) + 
      (Удовлетворенность_клиентов × 0.1)

    Этот алгоритм превращает хаотичные сбои из угрозы в возможность — возможность оптимизировать, учиться и становиться устойчивее с каждым днем.