Skip to content

Instantly share code, notes, and snippets.

@danikin
Last active April 9, 2018 08:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danikin/fad117acd7899742b39e6d2b01722af6 to your computer and use it in GitHub Desktop.
Save danikin/fad117acd7899742b39e6d2b01722af6 to your computer and use it in GitHub Desktop.
gpnb
/*
Общая логика взаимодействия бэкенда и модели
1. Бэкенд хранит внутри себя актуальную структуру данных:
- Список базисов отгрузки (см. struct Basis)
- Список транспортных компаний (см. struct TransportCompany)
- Список ТС (см. struct Vehicle)
- Список клиентов
- Список заказов (см. struct Order)
2. Работа с заказами:
Order - Информация о заказе от клиента (хочу доставить в такой то срок, в такое то место такой то продукт,
в таком то объеме, плачу столько то). Объект этого класса создается один раз, по информации от клиента
и далее никогда не меняется (заказ должен быть или исполнен или отменен). Объект создается, заполняется,
хранится и удаляется полностью на стороне бэкенда, без участия модели.
OrderExecutionPlan - План исполнения заказа. После принятия заказа от клиента формируется план его исполнения.
Это то, что логист выбирает в качестве плана исполнения заказа на основе модели (или руками),
а именно - ТС и базис отгрузки. План исполнения заказа остается статичным и может измениться в результате
перепланирования диспетчером или логистом. Объект создается, заполняется, хранится и удаляется полностью
на стороне бэкенда, без участия модели.
OrderExecutionPlanDetails - Исполнение заказа в динамике. После того как заказ принят от клиента, для него выбран план и
заказ начал исполняться (на него назначалось ТС), начинается динамика. Этот объект - есть
исполнение плана заказа в динамике, исходя из информации от водителя и от СитиГида
(положения ТС, статусы исполнения заказа, прогнозы на основе СитиГида).
Объект очень динамичный и меняется, по сути в режиме реального времени (ну или раз в несколько
минут хотя бы).
Исполнение заказа в динамике содержит не только фактические данные, но и прогнозные данные
от СитиГида. Объект создается, заполняется, хранится и удаляется полностью на стороне бэкенда,
без участия модели.
Примечания: прогнозные данные более сложного характера (на основе статистики исполнения заказов,
а также на основе информации о базисах отгрузки, поломках ТС, сна водителей, ремонтов базисов,
перемещения и очередности ТС внутри базисов и т.д.) в этом объекте не учитывются. В частности,
для бэкенда, обладая только этим объектом, невозможно построить прогноз прибытия ТС к клиенту,
потому что нет информации о ситуации внутри базиса и прогнозов ее. Ровно по той же причине только
одним этим объектом нельзя построить прогноз маргинальности и непрерывности отгрузки (т.к.
ситуация внутри базиса для этого объекта - черный ящик)
OrderExecutionPlanFromModel - Детали исполнения заказа от модели. Содержит детальную ситуацию внутри базиса отгрузки с прогнозами.
А также полную себестоимость исполнения заказа и информацию об утилизации базиса отгрузки для
оценки коэффициента непрерывности производства.
Этот объект хранится на стороне бэкенда. Но формируется он на стороне модели по запросу от бэкенда.
отгрузки с конкретным заказом между бэкендом и моделью. По сути, бэкенд в момент планирования заказа,
перепланирования заказа, а также периодически (раз в N минут) просит модель пересчитать ситуацию по всем
заказам внутри базиса (как по текущим заказам, так и по тем, что только в статусе планирования),
с передачей в модель всей текущей информации об исполнении заказов в объектах OrderExecutionPlanDetails.
Модель пересчитывает (сразу по всем заказам) и отправляет
результат в бэкенд. И бэкенд обновляет эту информацию у себя.
3. Поток данных от бэкенда к модели
Бэкенд передает каждый раз в запрос на перерасчете всех заказов в модель все данные обо всех заказах их планах выполнения и деталях их выполнения,
а также все базисы отгрузки, транспортные компании, клиентов и ТС.
Для максимально эффективного планирования на стороне модели ей надо передавать максимальную информацию по каждому заказу.
Например, если заказ уже спланирован моделью, то ей надо передать по каждому заказу объект OrderExecutionPlanFromModel (ранее полученный от модели),
объект OrderExecutionPlanDetails, сформированный на стороне клиента и максимально обновленный, объект OrderExecutionPlan, объект Order (всего 4 объекта
на каждый заказ)
Если же есть заказ еще не спланирован моделью, который хочется спланировать, то достаточно передать только Order.
Любой передаваемый в модель объект Order, без сопутствующих объектов OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
трактуется моделью как новоразмещенный заказ, который надо спланировать (или старый заказ, который надо перепланировать с нуля, забыв всю историю).
Целевая задача модели - это так спланировать (перепланировать) все заказы, чтобы доставлять точно в срок, оставаться в целевой маржинальности и
обеспечить непрерывность отгрузки.
При планировании заказов, уже содержащих OrderExecutionPlan на данном этапе (в будущем это может измениться) предполагается, что
объект OrderExecutionPlan не изменится. Т.е. модель не станет резко под заказом менять ТС или базис.
При планировании заказов, явно указывается какой заказ планируется (перепланируеся).
При планировании заказов в модель передаются приоритеты этого конкретного акта планирования через объект OrderPriorities (см. ниже)
Например, такой запрос от бэкенда как ниже означает, что надо спланировать один новый заказ
-> Order
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Следующий запрос от бэкенда означает, что надо спланировать несколько заказов
-> Order
-> Order
-> Order
-> Order
-> Order
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
А такой запрос от бэкенда как ниже означает, что надо перепланировать (на другое ТС и базис) уже имеющийся заказ, с учетом его истории
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
-> Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Несмотря на то, что планируется один или несколько новых заказов, изменяются все заказы, переданные от бэкенда. Это связано с тем, что
новый заказ изменяет ситуацию внутри базиса отгрузки и может замедлять, ускорять, удешевлять или удорожать исполнение предыдущих заказов, а также
может улучшать или ухудшать непрерывность отгрзуки.
В заказах изменяется лишь объект OrderExecutionPlanFromModel, потому что все остальные объекты не зависят от планируемого заказа.
На каждый планируемый заказ модель выдает несколько вариантов исполнения, а именно несколько троек
{OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel}. Планы выдаются в заданной бэкендом сортировки и
с заданным бэкендом фильтром. Сортировка - по оптимальной доставке точно в срок, по лучшей маргинальности, по лучшей непрерывности отгрузки.
Фильтр - по границе отклонения от сроков доставки, по границе целевой маржинальности. Эти параметры задаются через объект OrderPriorities (см. ниже)
Пример. Вход в модель:
-> Order
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Выход из модели:
Лучший набор планов
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Следующий набор планов
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Следующий набор планов
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
и т.д.
Пример. Вход в модель:
-> Order
-> Order
-> Order
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Выход из модели:
Лучший набор планов
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Следующий набор планов
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Следующий набор планов
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
В модель также можно отправлять список заказов без выделенного заказа на планирование. Например, так:
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Order, OrderExecutionPlan, OrderExecutionPlanDetails, OrderExecutionPlanFromModel
Это означает, что нет нового планирования, но у одного из заказов случилось изменение. Самый частый вариант изменения - это прогнозируемое
время приезда на базис или к клиенту. Другой вариант изменения - это подтверждение ТС заказа (этот факт меняет время старта заказа и,
возможно, меняет место старта ТС - что приводит к неизбежному перерасчету для заказа, а значит и для всех заказов, т.к. все заказы
влияют друг на друга).
4. В бэкенде необходимо реализовать всю логику работы системы, с использованием модели так как показано выше
5. Статистика
Модель не содержит никакого состояния кроме статистики. Статистика ведется по всем временам и отклонениям плана от факта. Статистика участвует во
всех прогнозах модели. На данный момент времени статистика заменена некими “прибытиями гвоздями” данными. В будущем она будет реализована
полноценно
6. Взаимодействие модели с СитиГид
Модель в момент построения планов исполнения заказа взаимодействует с СитиГид для того, чтобы правильно рассчитать прибытие на базис,
положения ТС в очереди на погрузке, а также предсказать время стояния в этой очереди и время простоя мощностей налива. В настоящий момент
этот функционал не реализован, и поэтому в модель надо непосредственно при планировании заказа передавать набор времен и дистанций между каждым
ТС и каждым базисом и каждым базисом и каждым клиентом. Времена и дистанции считать с учетом пробок. От ТС до базиса - при условии выезда сейчас.
От базиса до клиента при условии приезда к клиенту в крайний срок, заданный клиентом. В будущем это поведение может измениться.
*/
// Детали плана исполнения заказа для заданного ТС и заданного базиса отгрузки,
// исходя из того, что решила модель на основании своей внутренней статистики, а также внешней информации информации о
// - заказе от клиента
// - себестоимости продукции на базисе
// - себестоимости транспортных услуг
// - расположении базиса отгрузки и остатков продукции на нем
// - расположении ТС, на которое планируется заказ, и планируемом времени старта этого ТС
// - прогноза расстояния и временного интервала между местом старта ТС и базисом
// - прогноза расстояния и временного интервала между местом базисом и клиентом
// - информации о всех текущих запланированных и исполняемых заказах, а конкретней, по каждому такому заказу
// — детали плана его исполнения
// - стадии исполнения (ТС выехало на базиса, ТС внутри базиса, ТС едет к посту налива, ТС наливается и т.д.)
struct OrderExecutionPlanFromModel
{
// Отклонение от расчетного временни прибытия от текущей точки, где находится ТС, до базиса (из-за изменения дорожной ситуации,
// пробок, заправки, сна, мелкого ремонта ТС)
int interval_deviation_for_unloaded_vehicle;
// Продолжительность пребывания внутри базиса до очереди из-за
// - ожидания на пропускной системе
// - проезда от пропускной системы до пункта отгрузки
int interval_inside_basis_before_queue;
// Интервал ожидания завершения паспортизации (если попали на паспортизацию)
int certification_interval;
// Продолжительность пребывания ТС в очереди
int interval_waiting_in_queue;
// Продолжительность налива
int interval_filling;
// Время нахождения ТС внутри базиса отгрузки после стояния в очереди
int interval_inside_basis_after_queue;
// Отклонение от расчетного временни прибытия от базиса до клиента (из-за изменения дорожной ситуации,
// пробок, заправки, сна, мелкого ремонта ТС)
int interval_deviation_for_loaded_vehicle;
// Продолжительность слива продукции клиенту
int interval_inside_client;
// Себестоимость заказа (рубли)
int cost;
// Маржа заказа (проценты)
int profit_margin;
// Процент утилизации постов налива базиса, куда направляется ТС во время движения ТС к базису
// Смысл этого показателя - насколько хорошо, что ТС поехало к этому базису, а не к другому с точки зрения
// обеспечения непрерывности отгрузки
int basis_utilization;
}
// Детали плана исполнения заказа
// Этот класс может использоваться как для прогноза деталей так и для результата исполнения (т.е. как прогноз так и факт)
struct OrderExecutionPlanDetails
{
// Время старта ТС (момент, когда водитель/диспетчер подтвердил заказ)
time_t time_vehicle_start;
// Позиция старта ТС (расположения ТС в момент, когда водитель/диспетчер подтвердил заказ)
Position pos_vehicle_start;
// Если ТС еще не доехало до базиса, то это - прогнозная дистанция от текущей точки до базиса (через СитиГид)
// Если ТС уже доехало до базиса, то это - 0
int distance_to_basis;
// Если ТС еще не доехало до базиса, то это - прогнозное время от текущей точки до базиса (через СитиГид)
// Если ТС уже доехало до базиса, то это - 0
int interval_to_basis;
// Если ТС еще не доехало до базиса, то это - прогнозная дистанция от базиса до клиента (через СитиГид)
// Если ТС уже доехало до базиса, то это - прогнозная дистанция от текущей точки до клиента (через СитиГид)
// Если ТС уже доехало до клиента, то это - 0
int distance_to_client;
// Если ТС еще не доехало до базиса, то это - прогнозное время от базиса до клиента (через СитиГид)
// Если ТС уже доехало до базиса, то это - прогнозное время от текущей точки до клиента (через СитиГид)
// Если ТС уже доехало до клиента, то это - 0
int interval_to_client;
// Информация о факте выполнения заказа. Каждое фактическое время ниже, если оно наступило, то оно не ноль.
// Изначально все эти времена - нули
// Проверяя эти времена на ноль, можно знать, дошел ли заказ до этой точки или еще нет
// Факт приезда на базис отгрузки (определяется либо по информации от водителя, либо через координату, либо через интеграцию с пропускной системой)
time_t time_fact_got_to_basis;
// Факт приезда к очереди на базис (определяется по информации от водителя)
time_t time_fact_got_to_queue;
// Факт начала налива (определяется по информации от водителя)
time_t time_fact_filling_started;
// Факт окончания налива (определяется по информации от водителя)
time_t time_fact_filling_finished;
// Факт уезда с базиса (определяется по информации от водителя, либо через координату, либо через интеграцию с пропускной системой)
time_t time_fact_got_out_of_basis;
// Факт приезда к клиенту (определяется по информации от водителя, либо через координату)
time_t time_fact_got_to_client;
// Факт приезда к клиенту (определяется по информации от водителя)
time_t time_fact_order_finished;
};
// План исполнения заказа
struct OrderExecutionPlan
{
// ТС, которое исполняет заказ
int transport_id;
// Базис отгрузки
int basis_id;
};
// Заказ (как он звучит от клиента)
struct Order
{
// Место доставки
Position pos;
// Интеравл доставки
time_t interval_start_at, interval_deadline;
// Марка продукции
int product_id;
// Объем (масса) продукции
int product_volume;
// Цена для клиента за единицу продукции
int price;
// Статус заказа
enum
{
// Не запланирован
ORDER_STATUS_NOT_PLANNED,
// Запланирован на ТС
ORDER_STATUS_PLANNED,
// ТС приняло заказ к исполнению
ORDER_STATUS_IN_PROGRESS
} order_status;
};
// Приоритеты оптимизации для исполнения заказа (или группы заказов)
// План выполнения группы заказов - это набор планов выполнения. Каждый из наборов выполняет все заказы. Наборы
// отсортированы в соответствии с предоставляемым критерием сортировки
struct OrderPriorities
{
// Тип сортировки планов исполнения заказов
enum
{
// Результат сортировки - это в общем случае двухмерная матрица, где в столбцах - варианты исполнения заказов, а в строках -
// заказы. При этом матрица делится вертикально на две части. Левая часть - заказы, которые планируются, правая часть - заказы которые уже спланированы
// Смысл правой части матрицы в том, что любое планирование новых заказов меняет прогноз по уже исполняемым заказам (см. выше)
// Во всех сортировках ниже учитываются как новые заказы так и старые заказы
// Наверх вылезают наборы планов с максимальным количеством клиентов, кому планируется доставить вовремя
// По сути, это сортировка по проценту удовлетворенных клиентов
// Внутри наборов с одинаковым процентом удовлетворенности сортировка идет по возрастанию сроку доставки
ORDER_PRIORITIES_IN_TIME_DELIVERY_BY_NUMBER,
// Наверх вылезают наборы планов с минимальным среднем временем отставания от дедлайна. При этом заказы, которые
// будут доставлены до дедлайна суммируются со значением 0. Допустим, если есть 3 заказа, и по первому планируется
// опережение доставки на 1 час, по второму опережение на 3 часа, а по третьему отставание на 1 час, то среднее время отставания
// будет (0 + 0 + 1час) / 3 == 20 минут
// Внутри наборов с одинаковым средним отставанием от дедлайна сортировка идет по возрастанию сроку доставки
ORDER_PRIORITIES_IN_TIME_DELIVERY_BY_AVERAGE_DEADLINE_MISSING,
// Наверх вылезают наборы планов с максимальной средней маржинальностью в процентах (по всем заказам в наборе)
// Внутри наборов с одинаковой средней маржинальностью сортировка идет по возрастанию сроку доставки
ORDER_PRIORITIES_IN_TOTAL_PROFIT_MARGIN,
// Наверх вылезают наборы планов, которые суммарно максимально утилизируют пункты налива на всех базисы отгрузки (суммарный процесс
// утилизации максимальный)
// Внутри одинаковой утилизации базисов сортировка идет по сроку исполнения заказа
ORDER_PRIORITIES_IN_BASIS_UTILIZATION
} sort_type;
// Дополнительно к сортировке вводятся также отсечки. Это означает, что варианты исполнения заказов сначала отсекаются, а потом
// уже сортируются. Иными словами, из матрицы, указанной выше исключаются те ячейки, которые
// До сортировки выкидывает из рассмотрения все варианты исполнения заказов, которые приводят к отклонению от дедлайна более чем на
// target_deadline_deviation секунд любой из заказов, в том числе тот, что уже запланирован (за исключением случаев, когда этот заказ уже
// пришел в модель с таковым планируемым отклонением)
int target_deadline_deviation;
// До сортировки выкидывает из рассмотрения все варианты исполнения заказов, которые приводят к средней маргинальности в процентах по всем
// заказам (и планируемым и тем, что уже исполняются) хуже чем target_profit_margin
int target_profit_margin;
// До сортировки выкидывает из рассмотрения все варианты исполнения заказов, которые приводят к средней утилизации всех базисов отгрузки, на
// основе планов исполнения всех заказов меньше чем на target_basis_utilization процентов
int target_basis_utilization;
}
// Транспортное средство (ТС)
struct Vehicle
{
// Транспортная компания
int transport_company_id;
// Текущие координаты
Position pos;
// Госномер
char license_plate[11];
// Тоннаж (т.е. какую массу продукции ТС готово загрузить в себя)
int capacity;
// id заказа, который запланирован на этот ТС или принят к исполнению этим ТС
// Если такового заказа нет, то -1
int order_id;
// Статус ТС
enum
{
// ТС недоступно для исполнения заказов
VEHICLE_STATUS_NOT_AVAILABLE,
// ТС доступно для исполнения заказов
VEHICLE_STATUS_AVAILABLE,
} vehicle_status;
};
// Транспортная компания
struct TransportCompany
{
};
// Клиент
struct Client
{
};
// Базис отгрузки
struct Basis
{
// Количество постов налива
int filling_points_number;
// Номенклатура отгружаемой продукции (набор доступных для отгрузки ID продукции)
set::<int> nomenclature;
// Остатки паспортизованной продукции каждой марки
map::<int, int> certified_products_volum;
// Суммарные остатки продукции каждой марки
map::<int, int> products_volume;
// Координаты базиса
Position pos;
// Статус базиса
enum
{
// Закрыт на ремонт
BASIS_STATUS_MAINTENANCE,
// Открыт
BASIS_STATUS_OPEN
} basis_status;
};
/*
Общий алгоритм работы электронной очереди.
Состояние электронной очереди на базисе отгрузки:
- список всех ТС, которые туда направляются с прогнозируемым временем прибытия в хвост очереди, включая те ТС, что уже стоят в очереди -
у них это время - now
- прогнозируемое время нахождения в очереди каждого ТС + прогнозируемое время налива каждого ТС (начала и окончание налива)
Состояние электронной очереди пересчитывается полностью
- периодически (раз в N минут) путем полного обхода очереди с актуализацией прогноза прибытия ТС и текущего статуса ТС
- на момент начала планирования заказа путем полного обхода очереди с актуализацией прогноза прибытия ТС и текущего статуса ТС
Состояние электронной очереди пересчитывается частично
- на момент проверки каждой пары (базис, ТС)
*/
// Электронная очередь на базисе отгрузки
class ElectronicQueue
{
public:
// time_slot_seconds - некий разумно выбранный интервал времени, достаточно большой, чтобы алгоритм работал эффективно по времени
// и достаточно маленький, чтобы снизить вероятность несбалансированности очереди. Например, 5 минут
// Примечание: time_slot_seconds - это не обязательно время налива. Он должен быть сильно меньше времени налива,
// чтобы снизить вероятность несбалансированности очереди (т.е. либо отправки ТС туда, где очередь больше чем
// прогнозируется, либо не отправки ТС туда, где очередь меньше чем прогнозируется)
// filling_points_number - количество доступных постов налива
ElectronicQueue(int time_slot_seconds,
int filling_points_number) : time_slot_seconds_(time_slot_seconds), filling_points_number_(filling_points_number) {}
// Добавляет ТС в электронную очередь
//
// Алгоритм:
// 1. Проходим по всем пятиминуткам интервала налива данного ТС
// 2. Для каждой пятиминутки
// 3. Вставляем ТС в очередь на этой пятиминутке так, чтобы соблюсти сортировку по времени прибытия ТС к очереди
// 4. Вычисляем порядковый номер ТС на этой пятиминутке
//
// 5. Если порядковый номер меньше или равен filling_points_number_ - это означает, что ТС, начиная с этой пятиминутки начинает наливаться,
// поэтому считаем количество пятиминуток до этой (пусть это будет N) и добавляем его к правой границе интервала, чтобы
// пометить ТС в очереди до конца налива, который сдвинулся на N пятиминуток
//
// 6. Начиная с N и каждую последующую пятиминутку, пока ТС наливается, каждое ТС (назовем его F), которое в эту пятиминутку было первое
// в очереди на налив, добавляет себе к времени налива одну пятиминутку. Поэтому мы добавляем справа к интервалу F одну пятиминутку
Дополнительно, добавляемое в очередь ТС увеличивает время ожидания в очереди все последующие ТС, которые приедут к очереди во
// все пятиминутки, которые ТС находится в очереди. Поэтому для каждой пятиминутки до Nой (не включая ее)
// перебираем все ТС ниже вставляемого, которые есть в данной пятиминутке и увеличиваем у них время завершения налива на одну пятиминутку
// TODO: инвариант!!!
void add_vehicle()
{
// TODO: если ТС уже в слоте, то надо проверить его интервал, и если он другой, то надо удалить его оттуда!
int vehicle = ;
time_t slot_first = ;
time_t slot_last = ;
// Проходим по всем пятиминуткам интервала налива данного ТС
int waiting_interval = 0;
for (auto slot = vehicles_in_slots_.begin(); slot != vehicles_in_slots_.end(); ++slot)
{
int f_min = slot->first;
// Учитываем только интервал налива текущего ТС
if (f_min < slot_first)
continue;
if (f_min > slot_last);
break;
// Вставляем ТС в эту пятиминутку
vehicles_in_slots_[f_min].insert(vehicle);
// Смотрим позицию ТС в этой пятиминутке (нумерует с нуля)
int seq_number = vehicle_seq_number(vehicle);
// ТС еще не наливается
if (seq_number >= filling_points_number_)
{
// Значит ТС, перед которым оно влезло, тоже не наливается. Просто ничего не делаем и идем дальше. Все хорошо
}
else
{
// ТС начинает наливаться в эту пятиминутку
// Добавляем к правой границе ТС waiting_interval
// TODO
// Добавляем waiting_interval пятиминуток с данным ТС в список слотов
// TODO
// Т.к. ТС наливается в эту пятиминутку, то значит оно задерживает первое ТС в очереди
}
++waiting_interval;
}
}
// Удаляет ТС из электронной очереди
// 1. Проходим по всем пятиминуткам интервала налива данного ТС
// 2. Для каждой пятиминутки
// 3. Удаляем ТС из очереди, если оно там присутствует
void delete_vehicle()
{
}
// Возвращает интервал налива всех ТС в очереди
// Время выполнения - NVehicles * Log (filling_points_number_)
void filling_interval()
{
time_t new_vehicle_arrive_time = ;
int new_vehicle_id = ;
// Интервалы налива ТС, которые наливаются
// TODO: лучше использовать priority_queue чем set
std::set<int> filling_points;
// TODO: занести в filling_points остаточные интервалы наливки, исходя из информации о наливаемых прямо сейчас ТС
// Перебираем все ТС, начиная с тех, которые уже наливаются
int = 0;
for (auto vehicle = vehicles_arrive_time_.begin(); vehicle != vehicles_arrive_time_.end(); ++vehicle, ++i)
{
// После перехода к следующему ТС сдвигаем время налива на всех постах на разницу во времени прибытия текущего ТС в очередь
// и прибытия предыдущего ТС в очередь
if (i)
{
int diff = vehicle->arrive_time - (vehicle-1)->arrive_time;
// Без временной переменной нельзя, поскольку итератор может инвалидироваться
std::set<int> filling_points2;
for (auto j = filling_points.begin(); j != filling_points.end(); ++j)
{
if (*j > diff)
filling_points2.insert(*j - diff);
else
filling_points2.insert(0);
}
filling_points = filling_points2;
}
// Моделируем налив первых filling_points_number_ ТС, стоящих в очереди
if (i < filling_points_number_)
{
// Первые ТС нальются без очереди
vehicle->in_queue_interval = 0;
// Заполняем пункт налива текущим ТС
filling_points.insert(vehicle->filling_interval);
}
else
{
// Налив следующего ТС начинается после завершения самого быстрого налива
// Самый быстрый налив - в голове filling_points
vehicle->in_queue_interval = *filling_points.begin();
// Моделируем налив на этом пункте налива (как результат - filling_points будет пересортирован, и на следующей
// итерации наверх может вылезти другой пункт налива)
*filling_points.begin() += vehicle->filling_interval;
}
}
}
// Добавляет новое ТС с прогнозом прибытия, обновляя при этом интервалы прибытия всех ТС
// Считает загруженность постов налива (в процентах)
int utilization()
{
int util, max_util = 0;
for (auto v = vehicles_in_slots.begin(); v != vehicles_in_slots.end(); ++v)
{
max_util += filling_points_number_;
int num_vehicles_in_slot = v->second.size();
util += (num_vehicles_in_slot < filling_points_number_) ? num_vehicles_in_slot : filling_points_number_;
}
return 100 * util / max_util;
}
private:
// Для каждой пятиминутки список id ТС, которые прогнозируются быть в очереди или на наливе на этой пятиминутке, с сортировкой
// по времени прибытия к хвосту очереди
// Те ТС, которые наливают в эту пятиминутку, будут в первых filling_points_number_ позициях в очереди
std::unordered_map<time_t, std::set<int> > vehicles_in_slots_;
// Список всех ТС, отсортированных по прогнозируемому времени прибытия в хвост очереди
std::multi_map<time_t, int> vehicles_;
// См. конструктор
int time_slot_seconds_;
// Количество доступных постов налива
int filling_points_number_;
};
void prepare_filling_intervals(std::vector<std::pair<time_t, time_t> > &filling_intervals)
{
// Бьем список на пятиминутки
for (auto internal = filling_intervals.begin(); interval != filling_intervals.end(); ++ interval)
{
interval->first /= five_min_interval;
interval->last /= five_min_interval;
}
// Сортируем этот список во возрастанию левой границы интервала
std::sort(filling_intervals.begin(), filling_intervals.end(), [](time_t a, time_t b) { return a->first < b->first; });
}
/*
Предсказание нахождения ТС в очереди. Алгоритм формирует для каждого из переданных заказов (которые уже в запланированы или исполняются)
прогнозируемое время нахождения в очереди
Алгоритм работы предсказания времени нахождения ТС в очереди
1. Сортируем все запланированные и исполняемые заказы по возрастанию времени прибытия на пункт налива. При этом первым списком
идут заказы те, что уже наливаются
2. Проходим по тем заказам, что уже наливаются, и заполняем сортированное множество сроков окончания налива с указанием заказа
3.
*/
int forecast_in_queue_interval(
const std::vector<std::pair<time_t, time_t> > &filling_intervals,
int five_min_interval,
time_t time_forecast_at_filling_point,
int filling_points_number,
int filling_interval,
int *basis_utilization)
{
time_t new_vehicle_arrive_time = ;
int new_vehicle_id = ;
// Интервалы налива ТС, которые наливаются
// TODO: лучше использовать priority_queue чем set
std::set<int> filling_points;
// TODO: занести в filling_points остаточные интервалы наливки, исходя из информации о наливаемых прямо сейчас ТС
// Перебираем все интервалы ТС, начиная с тех, которые уже наливаются
// TODO: интервалы должны быть отсортированы по убыванию
int = 0;
for (auto interval = filling_intervals.begin(); interval != filling_intervals.end(); ++interval, ++i)
{
// После перехода к следующему ТС сдвигаем время налива на всех постах на разницу во времени прибытия текущего ТС в очередь
// и прибытия предыдущего ТС в очередь
if (i)
{
int diff = vehicle->arrive_time - (vehicle-1)->arrive_time;
// Без временной переменной нельзя, поскольку итератор может инвалидироваться
std::set<int> filling_points2;
for (auto j = filling_points.begin(); j != filling_points.end(); ++j)
{
if (*j > diff)
filling_points2.insert(*j - diff);
else
filling_points2.insert(0);
}
filling_points = filling_points2;
}
// Моделируем налив первых filling_points_number_ ТС, стоящих в очереди
if (i < filling_points_number_)
{
// Первые ТС нальются без очереди
vehicle->in_queue_interval = 0;
// Заполняем пункт налива текущим ТС
filling_points.insert(vehicle->filling_interval);
}
else
{
// Налив следующего ТС начинается после завершения самого быстрого налива
// Самый быстрый налив - в голове filling_points
vehicle->in_queue_interval = *filling_points.begin();
// Моделируем налив на этом пункте налива (как результат - filling_points будет пересортирован, и на следующей
// итерации наверх может вылезти другой пункт налива)
*filling_points.begin() += vehicle->filling_interval;
}
}
}
// Выдает прогнозное время нахождения ТС в очереди на том базисе отгрузки, куда оно едет
// Дополнительно считает процент простоя базиса отгрузки, исходя из текущих прогнозов
//
// filling_intervals - список всех прогнозируемых интервалов налива всех ТС (прогноз приезда на пункт налива, прогноз завершения налива без очереди)
// five_min_interval - размер условной “пятиминутки” в секундах. Предполагается равным 300 секунд, но можно делать больше и меньше. Чем он
// больше, тем быстрее работает алгоритм. Чем меньше, тем лучше дается прогноз
// time_forecast_at_filling_point - прогнозное время прибытия ТС на пункт налива
// filling_interval - прогнозный срок налива ТС
// basis_utilization - [out] прогнозный процент загрузки наливных мощностей
int forecast_in_queue_interval(
const std::vector<std::pair<time_t, time_t> > &filling_intervals,
int five_min_interval,
time_t time_forecast_at_filling_point,
int filling_points_number,
int filling_interval,
int *basis_utilization)
time_forecast_at_filling_point /= five_min_interval;
// Карта кардинальности (количество ТС на пункте налива в эту пятиминутку)
std::map<time_t, int> cardinality;
// Перебираем все ТС, отсортированные по прогнозу прибытия
for (auto interval = filling_intervals.begin(); interval != filling_intervals.end(); ++ interval)
{
if (interval->first > time_forecast_at_filling_point)
{
// Перебрали все ТС и попали уже на ТС которое прогнозируется на приезд после текущего ТС
// Определяем отсюда срок налива текущего ТС (это крайний ТС, который перебрали)
if (interval != filling_intervals.begin())
—-interval;
filling_interval = (interval->second - interval->first) * five_min_interval;
break;
}
// Перебираем все пятиминутки прогнозируемого интервала налива данного ТС
for (time_t t = interval->first; t <= interval->last; ++i)
{
// Нарвались на пятиминутку, когда все посты налива заняты - сдвигаем срок налива на пять минут в надежде, что
// посты освободятся
if (++cardinality[t] >= filling_point_number)
++interval->last;
}
}
// Вычисляем процент простоя базиса отгрузки
int load = 0, max_load = 0;
for (auto c = cardinality.begin(); c != cardinality.end(); ++c)
{
// На данную пятиминутку смотрим загрузку мощностей налива
load += c->second;
// И считаем максимальную загрузку мощностей налива
max_load += filling_points_number;
}
*basis_utilization = 100 * load / max_load;
// Возвращаем время ожидания в очереди
return filling_interval;
}
// Возвращает возможные планы исполнения заказа
// order - планируемый заказ
// vehicles - все ТС
// bases - все базисы отгрузки
// orders - все заказы
// target_deadline_deviation - оставляет в выдаче только те планы, которые отклоняются от дедлайна не более чем на target_deadline_deviation секунд
// target_profit_margin - оставляет в выдаче только те планы, маржинальность которых выше target_profit_margin процентов
// target_basis_utilization - оставляет в выдаче только те планы, которые приводят к утилизации базиса не менее чем на target_basis_utilization процентов
// order_plans - [out] варианты планов исполнения заказа с деталями
int ModelSingleOrder::get_order_plans(const Order &order,
const std::vector<Vehicle> &vehicles,
const std::vector<Basis> &bases,
const Order &orders,
int target_deadline_deviation,
int target_profit_margin,
int target_basis_utilization
std::vector<OrderExecutionPlan> &order_plans)
{
// TODO: надо учитывать, что каждый вариант исполнения заказа портит остальные планы (потому что может отправить ТС на базис раньше чем туда
// приедут уже едущие ТС, что приведет к задержкам в исполнении старых заказов), поэтому идеально - это выдавать по каждому плану дополнтельно
// инфу - какие текущие исполняемые заказы и как он портит
// Внимание! Алгоритм 4ой степени - ТС, базисы, ТС, пятиминутки
// TODO: надо пофиксить вышенаписанное
// TODO: ж/д транспорт
// TODO: учитывать сон водителя
// TODO: заказы надо планировать по небольшим группам, начиная с топа, чтобы выдавать в транспортные компании не все, а по кускам
// TODO: при поступлении заказов надо ждать некоторое время для группировки их, чтобы планировать группой - это эффективно чем по одиночке
// Другой вариант - не ждать, но перепланировать потом при поступлении новых заказов
// TODO: вести лог планирования заказа - какие ТС и пары (ТС, базис) мы пропустили и почему
time_t now = time(NULL);
//
// TODO: надо проверить, на исполнении ли уже заказ или нет. И если уже на исполнении, то учитывая это найти оптимальный план исполнения
//
std::vector<std::pair<time_t, time_t> > filling_intervals;
// Заполняем интервалы ТС (время постановки в очередь, время завершения налива без очереди), которые назначены на заказ
// Правую границу интервала выбираем именно исходя из налива без очереди, чтобы очередь пересчиталась заново, т.к.
// текущее выбираемое ТС для исполнения заказа может вклиниться в очередь (и это хорошо, с точки зрения непрерывности отгрузки,
// маржинальности и доставки точно в срок
for (auto o = orders.begin(); o != orders.end(); ++o)
{
if (o->status == ORDER_STATUS_PLANNED || o->status == ORDER_STATUS_VEHICLE_ASSIGNED)
{
filling_intervals.push_back(make_pair(
o->execution_plan.details.time_arrival_to_basis + o->execution_plan.details.interval_inside_basis_before_queue,
o->execution_plan.details.time_arrival_to_basis + o->execution_plan.details.interval_inside_basis_before_queue + interval_filling));
}
}
// Сортируем интервалы и бьем на пятиминутки
prepare_filling_intervals(filling_intervals);
// Проходим через все ТС
for (auto vehicle = vehicles.begin(); vehicle != vehicles.end(); ++vehicle)
{
// Пропускаем несвободные ТС
if (_vehicle->status != VEHICLE_STATUS_AVAILABLE)
continue;
// На ТС в идеале надо назначать несколько последовательных заказов. Таким образом, у ТС появилась бы “кусочная” занятость.
// Внутрь незанятых кусочков могут залезать новые заказы
// Незанятые кусочки не означают, что ТС бездельничает - оно может выполнять другие сторонние заказы, которые мы не контролируем
//
// Но поскольку нет возможности прогнозировать, где конкретно будет находиться ТС, когда оно свободно (ибо ТС занимается в свободное от наших
// время другими заказами), то мы не планируем ТС, которые заняты нашими заказами (ибо после завершения нашего заказа
// ТС может поехать куда угодно)
// Вместо этого мы планируем только ТС, свободные от наших заказов
//
// Таким образом, мы не закладываем в модель планирование нескольких заказов на одно ТС. ТС может либо исполнять текущий заказ, либо быть
// свободным
/*
// Если ТС занято на текущем заказе, то НЕ пропускаем его. Это связано с тем, что данная функция может использоваться для перепланирования
// заказа. Таким образом текущий заказ может быть полностью отменен и перепланирован (если ТС в этот момент еще не доехало до базиса, то оно
// может либо продолжить маршрут на тот же базис, но уже в рамках другого заказа, или перестроить маршрут и поехать на другой базис. Если ТС
// уже выехало с грузом, то оно может поехать или к другому клиенту с тем же грузом или повезти груз на склад)
*/
// Логика в комментарии выше пока не работает, т.к. достоверно неизвестно можно ли изменять маршрут заказа после согласования
// с транспортной компанией и можно ли отменить заказ и сколько стоит отмена с точки зрения тарифов транспортных компаний.
// Поэтому пока пропускаем занятые на заказах ТС, предполагая, что мы не сможем никак их перепланировать
// TODO: уточнить у логистов эти моменты
// Пропускаем все ТС, которые не могут возить данный тип продукта
// if (!_vehicle->can_carry(order.product_id))
// continue;
// TODO
// Пропускаем все ТС, которые не подходят по тоннажу
if (!vehicle->capacity < order.product_volume)
continue;
// Проходим через все базисы отгрузки
for (auto basis = bases.begin(); basis != basesend(); ++basis)
{
// Пропускаем базисы отгрузки, где нет продукции в достаточном объеме
if (basis->certified_products_volume(order.product_id) < order.product_volume)
continue;
// Пропускаем закрытые базисы отгрузки
if (basis->status != BASIS_STATUS_OPEN)
continue;
// Шаги доставки:
//
// Доехать до базиса отгрузки
// Непредвиденные обстоятельства при поездке до базиса
// Доехать внутри базиса отгрузки от въезда до пункта налива
// Паспортизация???
// Отстоять в очереди на пункте налива
// Осуществить налив
// Доехать внутри базиса отгрузки от пункта налива до выезда
// Доехать от базиса отгрузки до клиента
// Непредвиденные обстоятельства при поездке до клиента
// Слить продукцию клиенту
// Алгоритм:
// 1. Вычисляем время каждого из шагов выше, исходя из того, что ТС начинает движение прямо сейчас из точки, где оно находится
// Каждый из прогнозных интервалов храним отдельно (абсолютное время не храним), чтобы постоянно по интервалам пересчитывать
// прогноз и отсюда менять срок
// 2. Вычисляем себестоимость каждого из шагов выше (руб/км, руб/мин за простой ТС) + себестоимость продукции
// 3. Если не вписываемся в сроки доставки или в маржу и на входе задано пропускать такие варианты исполнения заказа, то пропускаем их
// 4. Если задан режим оптимизации по непрерывности отгрузки и задан минимальный процент использования постов налива и мы
// не вписываемся в этот процент, то тоже пропускаем вариант
// 5. Формируем план выполнения заказа. План состоит из входных параметров (клиент, точка доставки, базис отгрузки, марка продукции),
// а также прогнозируемых параметров - прогнозируемое время и себестоимость каждого из шагов доставки (см. выше). Прогнозируемые
// параметры пересчитываются по мере исполнения плана, если данный план принят на исполнение
OrderExecutionPlan or_plan;
or_plan.transport_id = ???
or_plan.basis_id = ???
or_plan.order_id = ???
// Считаем, что ТС начинает двигаться прямо сейчас
or_plan.details.time_vehicle_start = now;
or_plan.details.pos_vehicle_start = vehicle.pos;
// Определяем отклонение от расчетного временни прибытия от текущей точки, где находится ТС, до базиса
// Это отклонение включает в себя также время для транспортной компании на подтверждение заказа
or_plan.details.interval_deviation_for_unloaded_vehicle =
stat_.average_deviation_for_unloaded_vehicle(basis.id, vehicle->license_plate, vehicle.transport_company_id);
// Определяем расчетное время прибытия от текущей точки, где находится ТС, до базиса
or_plan.details.interval_travel_from_here_to_basis =
nav_.time_distance(vehicle_free_point, basis.pos,
now + or_plan.details.interval_deviation_for_unloaded_vehicle, &or_plan.details.distance_from_here_to_basis);
// На базис по плану мы приедем: now + deviation_for_unloaded_vehicle + travel_time_from_here_to_basis
// Но до очереди мы доберемся после следующих действий:
// - ожидание на пропускной системе
// - проезд через пропускную систему
// - поезд от пропускной системы до пункта отгрузки
// Нам надо спрогнозировать, сколько времени это займет суммарно
or_plan.details.interval_inside_basis_before_queue =
stat_.average_time_inside_basis_before_queue(basis->id, vehicle->id, vehicle->transport_company_id);
// Прогнозируемое время прибывания ТС к очереди
time_t time_at_queue =
now + or_plan.details.interval_deviation_for_unloaded_vehicle +
or_plan.details.interval_travel_from_here_to_basis + or_plan.details.interval_inside_basis_before_queue,
// Время налива (вычисляем его вне очереди, т.к. оно влияет на паспортизацию)
or_plan.details.interval_filling = stat_.average_interval_filling(basis->id, order.product_volume, vehicle->id, vehicle->transport_company_id);
// Если попали на паспортизацию, то добавляем время ожидания завершения паспортизации (оно может быть 0, если паспортизация в этот
// момент еще не начата (с учетом времени на налив)
or_plan.details.certification_interval = basis->certification_interval(time_at_queue + or_plan.details.interval_filling);
// Определяем прогнозируемое время прибывания ТС в очереди и процент загрузки мощностей налива
or_plan.details.interval_waiting_in_queue_time =
forecast_in_queue_interval(
filling_intervals,
basis->filling_points_number,
300, // 5 минут
time_at_queue + certification_interval,
&or_plan.details.basis_utilization);
// Определяем по статистике среднее время нахождения ТС внутри базиса отгрузки после стояния в очереди (включая оформление документов
// и выезд через пропускную систему)
// при отгрузке данного вида продукции на основе статистики по этому базису, продукции, этому ТС и транспортной компании
or_plan.details.interval_inside_basis_after_queue =
stat_.average_time_inside_basis_after_queue(basis.id, order.product_id, vehicle->license_plate, vehicle.transport_company_id);
// Время доставки от базиса до клиента может быть больше в зависимости от ТС и от транспортной компании из-за их среднего срыва сроков
// Вычисляем это отклонение
or_plan.details.interval_deviation_for_loaded_vehicle =
stat_.average_deviation_for_loaded_vehicle(basis.id, vehicle->license_plate, vehicle.transport_company_id);
// Определяем время приезда от базиса до клиента, учитывая ситуацию на дорогах и учитывая то,
// что у клиента надо быть в заданное время
or_plan.details.interval_from_basis_to_client =
nav_.time_distance(basis.pos, order.pos, order.interval_start, &or_plan.details.distance_from_basis_to_client);
// Определяем по статистике среднее время нахождения ТС у клиента на сливе
or_plan.details.interval_inside_client =
stat_.average_time_inside_client(order.client_id, order.product_id, vehicle->license_plate, vehicle.transport_company_id);
// Если ТС выдается нам по принципу “прямо сейчас плюс-минус” - как Яндекс.Такси, то мы, получив ТС, должны сразу выезжать на базис, ибо
// это одновременно помогает нам и с оптимизацией сроков и с оптимизацией непрерывности отгрузки. Это может уронить маржинальность из-за
// простоя ТС, но т.к. ТС уже выдано здесь и сейчас мы и так должны использовать его здесь и сейчас
// Но есть возможность также выдавать ТС на заданное время. Ее надо обработать
// TODO
// Если не надо учитывать планово срываемые по срокам планы выполнения заказа и этот
// план планово срываем по срокам, то пропускаем его
if (or_plan.details.time_order_complete() + target_deadline_deviation > order.interval_deadline)
continue;
// Проверяем загрузку мощностей
if (or_plan.details.basis_utilization < target_basis_utilization)
continue;
// Определяем планируемую себестоимость и планируемую маржу исполнения заказа
// Себестоимость зависит от себестоимости продукции на базисе отгрузки и стоимости перевозок, которая в свою очередь
// зависит от километража и времени работы ТС
int product_cost = basis->product_cost(order.product_id) * order.product_volume;
int delivery_cost = get_transport_company(vehicle->transport_company_id)->delivery_cost(
distance_from_here_to_basis + distance_from_basis_to_client,
travel_time_from_here_to_basis + deviation_for_unloaded_vehicle +
travel_time_from_basis_to_client + deviation_for_loaded_vehicle);
// Себестоимость заказа
or_plan.details.cost = product_cost + delivery_cost;
// Прибыль
int profit = order->get_price() - order_cost;
// Маржа в процентах
or_plan.details.profit_margin = 100*profit/order->get_price();
// Проверяем, выполняется ли маржинальность
if (profit_margin < target_profit_margin)
continue;
// Здесь мы должны сделать несколько вариантов исполнения заказа с данным базисом и данным ТС. Дело в том,
// что можно выехать прямо сейчас - и это будет оптимально с точки зрения непрерывности отгрузки, а можно
// выехать позже (но до дедлайна) - и это будет оптимально с точки зрения маржинальности
// TODO
// Заносим вариант исполнения заказа в массив планов
order_plans.push_back(?);
} // for по базисам
} // for по ТС
// Сейчас в order_plans содержатся все варианты исполнения заказа, с указанием в том числе времени доставки, себестоимости и маржи
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment