Анализ и формализация бизнес-процесса: Распределение грузовиков и каскадный перерасчёт поставок для перевозки жидкого цемента
1. Введение и контекст проблемы
1.1. Сущность задачи
Создание системы распределения грузовиков-миксеров для доставки жидкого цемента (бетона) в рамках 12-часовой смены с возможностью динамического перепланирования при сбоях. Особенности задачи обусловлены спецификой продукта — жидкий бетон имеет ограниченное время жизнеспособности (обычно 90-120 минут) и требует непрерывной заливки на строительных объектах.
1.2. Ключевые ограничения
-
Временное ограничение: смена фиксированная (12 часов), все поставки должны быть завершены в этот период
-
Время жизни бетона: от загрузки до разгрузки не более 90-120 минут в зависимости от марки и температуры
-
Непрерывность заливки: для многих объектов требуется минимальный интервал между поставками (обычно 15-30 минут)
-
Непересекаемость слотов: один грузовик не может выполнять две поставки одновременно
-
Производительность завода: ограничение по количеству одновременных погрузок
2. Расширенная формализация модели
2.1. Доменная модель с учётом специфики цементной логистики
Основные сущности:
// Ядро системы - смена (рабочий день)
public class CementDeliveryShift
{
public DateTime ShiftStart { get; } // Начало смены (07:00)
public DateTime ShiftEnd { get; } // Конец смены (19:00)
public Location PlantLocation { get; } // Местоположение бетонного завода
public double PlantHourlyCapacity { get; } // Производительность завода (машин/час)
// Коллекции
public List<CementOrder> Orders { get; } // Все заказы смены
public List<ConcreteTruck> Trucks { get; } // Парк машин
public List<DeliverySlot> AllSlots { get; } // Все слоты поставок
public Schedule Schedule { get; } // Текущее расписание
}
Заказ с расширенными атрибутами:
public class CementOrder
{
public string OrderId { get; }
public Client Client { get; } // Клиент с приоритетом и историей
public ConstructionSite Site { get; } // Объект строительства
// Технические параметры
public double TotalVolume { get; } // Общий объём (м³)
public string ConcreteGrade { get; } // Марка бетона
public double SlumpTest { get; } // Осадка конуса (подвижность)
// Временные ограничения
public TimeWindow PreferredWindow { get; } // Предпочтительное окно разгрузки
public TimeWindow HardWindow { get; } // Жёсткое окно (если есть)
public bool RequiresContinuousPouring { get; } // Требуется непрерывная заливка
public TimeSpan MaxIntervalBetweenDeliveries { get; } // Макс. интервал при непрерывной заливке
// Состояние
public OrderStatus Status { get; set; }
public List<ConcreteDelivery> Deliveries { get; } // Поставки этого заказа
}
Поставка с учётом жизненного цикла бетона:
public class ConcreteDelivery
{
public string DeliveryId { get; }
public CementOrder Order { get; }
// Параметры поставки
public double Volume { get; } // Объём этой поставки (м³)
public TimeSpan ConcreteLifetime { get; } // Время жизни бетона (90-120 мин)
public DateTime MixingStartTime { get; } // Время начала замеса на заводе
// Временные метки (план/факт)
public DeliveryTimeline Timeline { get; }
// Статус и привязка
public DeliveryStatus Status { get; set; }
public ConcreteTruck AssignedTruck { get; set; }
public DeliverySlot Slot { get; set; }
// Методы контроля качества
public bool IsConcreteStillViable(DateTime currentTime)
{
return currentTime <= MixingStartTime + ConcreteLifetime;
}
}
Грузовик с расширенными характеристиками:
public class ConcreteTruck
{
public string TruckId { get; }
public string LicensePlate { get; }
// Технические характеристики
public double MixerCapacity { get; } // Вместимость миксера (м³)
public double WaterTankCapacity { get; } // Ёмкость бака для воды
public bool HasPumpAttachment { get; } // Есть ли бетононасос
public TruckSize SizeCategory { get; } // Категория (для ограничений по въезду)
// Состояние
public TruckStatus Status { get; set; }
public Location CurrentLocation { get; set; }
public double RemainingConcrete { get; set; } // Остаток бетона (для утилизации)
public bool RequiresWashing { get; set; } // Требуется промывка
// Расписание
public List<DeliverySlot> Schedule { get; }
public DateTime AvailableFrom { get; set; } // Время, когда станет свободной
// Телематика
public TelemetryData LastTelemetry { get; set; }
public MaintenanceStatus Maintenance { get; set; }
}
Слот с детализированной временной структурой:
public class DeliverySlot
{
public string SlotId { get; }
public ConcreteDelivery Delivery { get; }
public ConcreteTruck Truck { get; }
// Детальная разбивка времени
public TimeBreakdown TimeBreakdown { get; }
// Временные границы
public DateTime SlotStart { get; } // Начало слота (выезд на завод)
public DateTime SlotEnd { get; } // Конец слота (возврат в пул)
public DateTime UnloadStart { get; } // Начало разгрузки (ключевое время)
public DateTime UnloadEnd { get; } // Конец разгрузки
// Буферы и запасы
public TimeSpan RiskBuffer { get; } // Буфер на риски
public TimeSpan FlexibilityWindow { get; } // Окно гибкости для клиента
// Методы проверки
public bool OverlapsWith(DeliverySlot other)
{
return SlotStart < other.SlotEnd && other.SlotStart < SlotEnd;
}
public bool IsConcreteViable()
{
return UnloadStart <= Delivery.MixingStartTime + Delivery.ConcreteLifetime;
}
}
public class TimeBreakdown
{
public TimeSpan TravelToPlant { get; } // Путь до завода
public TimeSpan WaitingAtPlant { get; } // Ожидание загрузки
public TimeSpan Loading { get; } // Загрузка бетона
public TimeSpan TravelToSite { get; } // Путь до объекта
public TimeSpan SetupAtSite { get; } // Подготовка к разгрузке
public TimeSpan Unloading { get; } // Разгрузка
public TimeSpan Washout { get; } // Промывка (если требуется)
public TimeSpan ReturnToBase { get; } // Возвращение
public TimeSpan Total => TravelToPlant + WaitingAtPlant + Loading +
TravelToSite + SetupAtSite + Unloading +
Washout + ReturnToBase;
}
2.2. Критерии выбора машины (приоритизированные)
Обязательные критерии:
-
Вместимость:
Truck.Capacity >= Delivery.Volume -
Доступность во времени: наличие свободного окна в расписании
-
Время жизни бетона: возможность доставить в пределах ConcreteLifetime
-
Ограничения объекта: соответствие Truck.SizeCategory ограничениям объекта
-
Техническая исправность: Truck.Status != Broken/Maintenance
Оптимизационные критерии (взвешенная оценка):
-
Расстояние до завода (вес 40%) - минимизация порожнего пробега
-
Коэффициент заполнения (вес 25%) -
Delivery.Volume / Truck.Capacity -
Совместимость бетона (вес 20%) - если у машины остатки другого бетона
-
Приоритет клиента (вес 15%) - умножение оценки на коэффициент приоритета
3. Событийная архитектура с учётом мировой практики
3.1. Полная модель событий
public enum DeliveryEventType
{
// Планирование
ShiftPlanningStarted,
OrderReceived,
OrderSplitIntoDeliveries,
// Назначения
TruckAssignedToDelivery,
DeliveryScheduled,
ScheduleOptimized,
// Исполнение
TruckDepartedToPlant,
LoadingStarted,
LoadingCompleted,
TruckDepartedToSite,
ArrivedAtSite,
UnloadingStarted,
UnloadingCompleted,
TruckReturnedToBase,
// Форс-мажоры
TruckBreakdownReported,
TrafficJamDetected,
SiteNotReady,
ConcreteRejected,
WeatherAlert,
// Перепланирование
ReschedulingTriggered,
DeliveryReassigned,
DeliveryCanceled,
ScheduleCascadeUpdate,
// Качество
ConcreteViabilityWarning,
ContinuousPouringBreach,
ClientComplaint
}
public class DeliveryEvent
{
public string EventId { get; }
public DeliveryEventType Type { get; }
public DateTime OccurredAt { get; }
public DateTime RecordedAt { get; }
// Контекст события
public string SourceSystem { get; } // Кто инициировал событие
public object TriggeringEntity { get; } // Машина/поставка/заказ
public object RelatedEntities { get; } // Связанные сущности
// Данные события
public Dictionary<string, object> EventData { get; }
public EventSeverity Severity { get; }
// Для анализа
public string CorrelationId { get; } // Для группировки связанных событий
public string CausationId { get; } // ID события-причины
}
3.2. Интерфейсы системы
// Ядро планирования
public interface ICementDeliveryPlanner
{
Schedule PlanShift(CementDeliveryShift shift);
Schedule ReplanForEvent(DeliveryEvent triggeringEvent, Schedule currentSchedule);
OptimizationResult OptimizeSchedule(Schedule schedule, OptimizationCriteria criteria);
}
// Выбор машин
public interface ITruckSelector
{
ConcreteTruck SelectTruckForDelivery(
ConcreteDelivery delivery,
List<ConcreteTruck> availableTrucks,
SelectionCriteria criteria);
List<ConcreteTruck> RankTrucksForDelivery(
ConcreteDelivery delivery,
List<ConcreteTruck> candidateTrucks);
}
// Управление временными окнами
public interface ITimeWindowManager
{
bool CanAccommodateDelivery(
ConcreteTruck truck,
ConcreteDelivery delivery,
out DateTime suggestedStartTime);
List<TimeWindow> FindAvailableWindows(
ConcreteTruck truck,
TimeSpan requiredDuration,
DateTime searchStart,
DateTime searchEnd);
}
// Обработчик событий
public interface IDeliveryEventHandler
{
void HandleEvent(DeliveryEvent deliveryEvent);
Task HandleEventAsync(DeliveryEvent deliveryEvent);
void Subscribe(DeliveryEventType eventType, IEventSubscriber subscriber);
void Unsubscribe(DeliveryEventType eventType, IEventSubscriber subscriber);
}
// Визуализация
public interface IScheduleVisualizer
{
GanttChart GenerateGanttChart(Schedule schedule);
ScheduleView GenerateScheduleView(Schedule schedule, VisualizationOptions options);
ConflictMap IdentifyConflicts(Schedule schedule);
}
4. Алгоритмическая модель
4.1. Алгоритм первоначального распределения (многоэтапный)
public class MultiStageDeliveryPlanner : ICementDeliveryPlanner
{
public Schedule PlanShift(CementDeliveryShift shift)
{
// Этап 1: Подготовка и валидация
ValidateShiftConstraints(shift);
// Этап 2: Разбиение заказов на поставки с учётом непрерывной заливки
var allDeliveries = SplitOrdersIntoDeliveries(shift.Orders, shift.Trucks);
// Этап 3: Группировка поставок по приоритетам и ограничениям
var deliveryGroups = GroupDeliveries(allDeliveries);
// Этап 4: Последовательное планирование по группам
var schedule = new Schedule();
foreach (var group in deliveryGroups.OrderByDescending(g => g.Priority))
{
// 4.1. Планирование критичных поставок с жёсткими окнами
ScheduleCriticalDeliveries(group, schedule, shift);
// 4.2. Планирование поставок с непрерывной заливкой
ScheduleContinuousPouringDeliveries(group, schedule, shift);
// 4.3. Планирование остальных поставок
ScheduleRemainingDeliveries(group, schedule, shift);
}
// Этап 5: Оптимизация и балансировка
schedule = OptimizeSchedule(schedule, new OptimizationCriteria
{
MinimizeEmptyMileage = true,
BalanceTruckUtilization = true,
MaximizeOnTimeDelivery = true
});
// Этап 6: Добавление защитных буферов
AddProtectiveBuffers(schedule);
return schedule;
}
private List<DeliveryGroup> GroupDeliveries(List<ConcreteDelivery> deliveries)
{
return deliveries.GroupBy(d => new
{
PriorityCategory = GetPriorityCategory(d.Order.Client.Priority),
HasHardWindow = d.Order.HardWindow != null,
RequiresContinuous = d.Order.RequiresContinuousPouring,
ConcreteGrade = d.Order.ConcreteGrade
})
.Select(g => new DeliveryGroup
{
Deliveries = g.ToList(),
Priority = CalculateGroupPriority(g.Key),
Constraints = g.Key
})
.ToList();
}
}
4.2. Алгоритм каскадного перерасчета с локализацией
public class LocalizedCascadeRescheduler : ICementDeliveryPlanner
{
public Schedule ReplanForEvent(DeliveryEvent triggeringEvent, Schedule currentSchedule)
{
// 1. Анализ воздействия события
var impactAnalysis = AnalyzeEventImpact(triggeringEvent, currentSchedule);
// 2. Определение области перепланирования
var replanningScope = DetermineReplanningScope(impactAnalysis);
// 3. Извлечение затронутых поставок из расписания
var affectedDeliveries = ExtractAffectedDeliveries(currentSchedule, replanningScope);
// 4. Создание временного пула доступных машин
var availableTrucks = CreateAvailableTruckPool(currentSchedule, replanningScope);
// 5. Многоуровневый алгоритм перераспределения
var result = MultiLevelReallocation(affectedDeliveries, availableTrucks);
// 6. Интеграция результата в общее расписание
var newSchedule = IntegrateReplanningResult(currentSchedule, result);
// 7. Валидация и корректировка
ValidateAndAdjust(newSchedule);
return newSchedule;
}
private ReplanningResult MultiLevelReallocation(
List<ConcreteDelivery> deliveries,
List<ConcreteTruck> trucks)
{
var result = new ReplanningResult();
// Уровень 1: Попытка назначить на те же временные окна
result = TryReassignToSameTimeWindows(deliveries, trucks);
if (result.UnassignedDeliveries.Any())
{
// Уровень 2: Поиск ближайших доступных окон
result = TryFindNearestWindows(result.UnassignedDeliveries, trucks);
if (result.UnassignedDeliveries.Any())
{
// Уровень 3: Каскадный сдвиг с минимальным воздействием
result = TryCascadeShift(result.UnassignedDeliveries, trucks);
if (result.UnassignedDeliveries.Any())
{
// Уровень 4: Эскалация - требование ручного вмешательства
EscalateToDispatcher(result.UnassignedDeliveries);
}
}
}
return result;
}
}
4.3. Алгоритм выбора машины с многофакторной оценкой
public class MultiFactorTruckSelector : ITruckSelector
{
public ConcreteTruck SelectTruckForDelivery(
ConcreteDelivery delivery,
List<ConcreteTruck> availableTrucks,
SelectionCriteria criteria)
{
// 1. Фильтрация по обязательным критериям
var suitableTrucks = FilterByMandatoryCriteria(availableTrucks, delivery);
if (!suitableTrucks.Any())
return null;
// 2. Расчет оценки для каждой подходящей машины
var scoredTrucks = suitableTrucks
.Select(truck => new
{
Truck = truck,
Score = CalculateCompositeScore(truck, delivery, criteria)
})
.OrderByDescending(x => x.Score)
.ToList();
// 3. Выбор лучшей машины с учётом дополнительных факторов
return SelectBestTruck(scoredTrucks, delivery, criteria);
}
private double CalculateCompositeScore(
ConcreteTruck truck,
ConcreteDelivery delivery,
SelectionCriteria criteria)
{
double score = 0;
// Фактор 1: Эффективность расстояния (40%)
var distanceScore = CalculateDistanceScore(truck, delivery);
score += distanceScore * 0.4;
// Фактор 2: Эффективность использования ёмкости (25%)
var utilizationScore = CalculateUtilizationScore(truck, delivery);
score += utilizationScore * 0.25;
// Фактор 3: Временная совместимость (20%)
var timeScore = CalculateTimeCompatibilityScore(truck, delivery);
score += timeScore * 0.2;
// Фактор 4: Надёжность и история (15%)
var reliabilityScore = CalculateReliabilityScore(truck);
score += reliabilityScore * 0.15;
// Модификатор приоритета клиента
score *= GetPriorityMultiplier(delivery.Order.Client.Priority);
return score;
}
}
5. Учёт специфики цементной логистики
5.1. Модель времени жизни бетона
public class ConcreteViabilityModel
{
// Основные параметры, влияющие на время жизни
public double BaseLifetime { get; } = 90; // минут при 20°C
public Dictionary<string, double> GradeModifiers { get; } // Модификаторы по маркам
public Dictionary<double, double> TemperatureModifiers { get; } // Модификаторы по температуре
public TimeSpan CalculateAdjustedLifetime(
string concreteGrade,
double temperature,
double slump)
{
var lifetime = TimeSpan.FromMinutes(BaseLifetime);
// Корректировка по марке
if (GradeModifiers.ContainsKey(concreteGrade))
lifetime = TimeSpan.FromMinutes(
lifetime.TotalMinutes * GradeModifiers[concreteGrade]);
// Корректировка по температуре
if (TemperatureModifiers.ContainsKey(temperature))
lifetime = TimeSpan.FromMinutes(
lifetime.TotalMinutes * TemperatureModifiers[temperature]);
// Корректировка по подвижности
if (slump < 10) lifetime *= 0.9; // Менее подвижный - быстрее схватывается
if (slump > 20) lifetime *= 1.1; // Более подвижный - дольше живет
return lifetime;
}
}
5.2. Модель непрерывной заливки
public class ContinuousPouringManager
{
public bool ValidateContinuousPouring(
List<ConcreteDelivery> deliveries,
TimeSpan maxInterval)
{
if (deliveries.Count < 2) return true;
// Сортируем по времени разгрузки
var sortedDeliveries = deliveries
.OrderBy(d => d.Timeline.PlannedUnloadStart)
.ToList();
// Проверяем интервалы между последовательными поставками
for (int i = 0; i < sortedDeliveries.Count - 1; i++)
{
var current = sortedDeliveries[i];
var next = sortedDeliveries[i + 1];
var interval = next.Timeline.PlannedUnloadStart -
current.Timeline.PlannedUnloadEnd;
if (interval > maxInterval)
return false;
}
return true;
}
public List<TimeAdjustment> CalculateRequiredAdjustments(
List<ConcreteDelivery> deliveries,
TimeSpan maxInterval)
{
var adjustments = new List<TimeAdjustment>();
for (int i = 0; i < deliveries.Count - 1; i++)
{
var current = deliveries[i];
var next = deliveries[i + 1];
var interval = next.Timeline.PlannedUnloadStart -
current.Timeline.PlannedUnloadEnd;
if (interval > maxInterval)
{
// Требуется сократить интервал
var requiredShift = interval - maxInterval;
adjustments.Add(new TimeAdjustment
{
Delivery = next,
RequiredShift = -requiredShift, // Сдвиг назад
Reason = "Continuous pouring constraint"
});
}
}
return adjustments;
}
}
6. Подводные камни и стратегии их преодоления
6.1. Основные риски и митигирующие меры
| Риск | Вероятность | Воздействие | Стратегия митигации |
|---|---|---|---|
| Превышение времени жизни бетона | Высокая | Критическое | Динамический контроль времени, резервные маршруты |
| Нарушение непрерывной заливки | Средняя | Высокое | Резервирование машин, гибкие интервалы |
| Перегрузка завода | Средняя | Высокое | Балансировка нагрузки, предварительное планирование |
| Невозможность перераспределения | Низкая | Критическое | Эскалация к диспетчеру, альтернативные сценарии |
| Накопление задержек | Высокая | Среднее | Адаптивные буферы, приоритизация |
6.2. Алгоритм управления рисками
public class RiskManagementSystem
{
public RiskAssessment AssessScheduleRisks(Schedule schedule)
{
var risks = new List<RiskItem>();
// 1. Риск превышения времени жизни бетона
risks.AddRange(AssessConcreteViabilityRisks(schedule));
// 2. Риск нарушения непрерывной заливки
risks.AddRange(AssessContinuousPouringRisks(schedule));
// 3. Риск перегрузки завода
risks.AddRange(AssessPlantOverloadRisks(schedule));
// 4. Риск накопления задержек
risks.AddRange(AssessCascadeDelayRisks(schedule));
return new RiskAssessment
{
Risks = risks,
OverallRiskLevel = CalculateOverallRisk(risks),
RecommendedActions = GenerateMitigationActions(risks)
};
}
private List<MitigationAction> GenerateMitigationActions(List<RiskItem> risks)
{
var actions = new List<MitigationAction>();
foreach (var risk in risks.Where(r => r.Severity >= RiskSeverity.Medium))
{
switch (risk.Type)
{
case RiskType.ConcreteViability:
actions.Add(new MitigationAction
{
Type = ActionType.AddBuffer,
Target = risk.AffectedDelivery,
Description = "Добавить временной буфер",
Priority = ActionPriority.High
});
break;
case RiskType.ContinuousPouringBreach:
actions.Add(new MitigationAction
{
Type = ActionType.Reschedule,
Target = risk.AffectedDelivery,
Description = "Перенести поставку для соблюдения интервала",
Priority = ActionPriority.High
});
break;
case RiskType.PlantOverload:
actions.Add(new MitigationAction
{
Type = ActionType.Redistribute,
Target = null,
Description = "Перераспределить погрузки по времени",
Priority = ActionPriority.Medium
});
break;
}
}
return actions;
}
}
7. Архитектурные рекомендации и реализация
7.1. Рекомендуемая архитектура
7.2. Ключевые компоненты реализации
// Основной координатор системы
public class CementDeliveryOrchestrator
{
private readonly ISchedulePlanner _planner;
private readonly IEventProcessor _eventProcessor;
private readonly IRiskManager _riskManager;
private readonly IVisualizationEngine _visualizer;
public async Task<DeliveryShiftResult> ExecuteShift(
CementDeliveryShift shift,
CancellationToken cancellationToken)
{
// 1. Начальное планирование
var schedule = await _planner.PlanShiftAsync(shift);
// 2. Мониторинг и обработка событий в реальном времени
var monitorTask = MonitorAndHandleEvents(schedule, cancellationToken);
// 3. Визуализация для диспетчера
await _visualizer.RenderRealTimeDashboard(schedule);
// 4. Управление рисками
await _riskManager.MonitorAndMitigateRisks(schedule, cancellationToken);
// 5. Завершение смены и отчётность
return await CompleteShift(schedule);
}
private async Task MonitorAndHandleEvents(
Schedule schedule,
CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var events = await _eventProcessor.GetNewEventsAsync();
foreach (var deliveryEvent in events)
{
// Обработка события с перепланированием при необходимости
if (RequiresRescheduling(deliveryEvent))
{
schedule = await _planner.ReplanForEventAsync(
deliveryEvent, schedule);
await _visualizer.UpdateDashboard(schedule);
}
await _eventProcessor.ProcessEventAsync(deliveryEvent);
}
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
}
}
}
8. Заключение и рекомендации по внедрению
8.1. Критические успешные факторы
-
Качество данных: Интеграция с точными системами GPS, телематики и прогноза пробок
-
Гибкость алгоритмов: Возможность настройки параметров под конкретный парк и регион
-
Человеческий фактор: Обучение диспетчеров, вовлечение водителей через мобильное приложение
-
Постепенное внедрение: Начать с пилотного проекта на части парка
8.2. Этапы внедрения
-
Фаза 1 (3 месяца): Базовое планирование без учёта форс-мажоров
-
Фаза 2 (3 месяца): Добавление каскадного перерасчёта и обработки событий
-
Фаза 3 (2 месяца): Интеграция с внешними системами (GPS, карты, погода)
-
Фаза 4 (2 месяца): Оптимизация и тонкая настройка алгоритмов
-
Фаза 5 (постоянно): Непрерывное улучшение на основе аналитики
8.3. Метрики успеха
-
Точность доставки: % поставок в пределах окна клиента
-
Использование парка: % времени машин в работе
-
Простой бетона: % поставок с угрозой схватывания
-
Эффективность перепланирования: время восстановления расписания после сбоя
-
Удовлетворённость клиентов: NPS (Net Promoter Score)
Данная модель представляет собой комплексное решение, учитывающее специфику цементной логистики, мировой опыт и современные архитектурные подходы. Она балансирует между автоматизацией и человеческим контролем, обеспечивая как эффективность, так и устойчивость к неопределённостям.
