API – podstawowe możliwości

Poniżej znajduje się opis komunikacji z serwerem optymalizującym (dla developerów/informatyków/programistów). Zintegrowanie tej usługi optymalizacyjnej z istniejącymi narzędziami i rozwiązaniami w Twojej firmie może zająć tylko kilka godzin! Jeśli nie chcesz mieć do czynienia z programowaniem, dostępne są wygodniejsze sposoby korzystania z OptiFacility.

1. Zlecenie optymalizacji

Serwis optymalizacyjny (Serwer OptiFacility) przyjmuje zlecenie od Klienta przez HTTP POST. Zlecenie ma format json i obecnie zawiera następujące pola:

{
  "UserID": "demo",
  "RequestID": 12345,
  "Comment": "my first testing request",
  "Quality": 2,

  "AgentSpeed": 45.0,
  "AgentStartTime": 9.0,
  "AgentWorkTime": 7.5,
  "AgentVisitTime": 0.6,
  "CostAgentWork1h": 60.0,
  "CostAgentTravel1km": 1.0,

  "Facilities": [{"Lat":52.23,  "Lon":21.012},
                 {"Lat":51.75,  "Lon":19.467},
                 {"Lat":50.061, "Lon":19.937, "Name":"KRK", "AgentVisitTime":0.85},
                 {"Lat":51.1,   "Lon":17.033},
                 {"Lat":52.407, "Lon":16.93},
                 {"Lat":53.429, "Lon":14.553, "AgentVisitTime":0.95},
                 {"Lat":53.123, "Lon":18.008, "Name":"BYD", "Importance":200},
                 {"Lat":51.25,  "Lon":22.567, "Importance":33},
                 {"Lat":51.258, "Lon":18.028}],
  "Agents": [{"Lat":52.0, "Lon":21.0, "Name":"Smith", 
              "AgentStartTime":7.0, "AgentWorkTime":7.3, "AgentSpeed":42.0,
              "CostAgentWork1h":65.0, "CostAgentTravel1km":0.98}],
  
  "Distances": [2,1,9,7,80, 2,1,0,1,90],

  "Task":"FindRoutesCommutingIncluded"
}

Opis znaczenia pól:

  • UserID – nazwa użytkownika usługi optymalizacyjnej.
  • RequestID – dowolna liczba całkowita, która zostanie zwrócona w odpowiedzi – może być przydatna jeśli Klient chce wiedzieć które odpowiedzi dotyczą których zleceń, i odpowiedzi są przetwarzane przez inne procesy Klienta niż te, które wysyłały zlecenia. Jeśli klient nie potrzebuje tego mechanizmu, może podawać np. 0.
  • Comment – opcjonalne pole z dowolnymi informacjami opisującymi wysyłane dane. To pole jest ignorowane przez OptiFacility, ale dla Ciebie może okazać się przydatne, jeśli będziesz chciał zapisać zlecenie do pliku i kiedyś w przyszłości je wykorzystać.
  • Quality – liczba 1, 2 lub 3 – wskazówka dla usługi optymalizacji, ile mocy obliczeniowej poświęcić na poszukiwanie najlepszego rozwiązania. Im większa liczba, tym dłuższe oczekiwanie na odpowiedź Serwera (jednak nigdy nie dłuższe niż 60 sekund).
  • AgentSpeed – średnia prędkość przemieszczania się kurierów między punktami (obiektami) w km/h. Serwer zakłada prostoliniowe połączenia, więc jeśli Klient chciałby wyświetlać użytkownikowi końcowemu szczegółowe i dokładne dane, może po otrzymaniu odpowiedzi z Serwera dokonać korekty odległości i czasów bazując na danych z nawigacji.
  • AgentStartTime – czas (godzina) rozpoczęcia pracy kuriera (pracownika, serwisanta). Np. 13.25 oznacza 13:15 (0.25 to 1/4 godziny czyli 15 minut).
  • AgentWorkTime – ile godzin dziennie pracuje kurier (pracownik, serwisant).
  • AgentVisitTime – ile godzin zajmuje wizyta/usługa w punkcie (obiekcie, lokalizacji).
  • CostAgentWork1h – koszt godziny pracy agenta w punkcie (obiekcie, lokalizacji).
  • CostAgentTravel1km – koszt kilometra podróży agenta.
  • Facilities – tablica struktur zawierających szerokość i długość geograficzną – współrzędne punktów (obiektów). Opcjonalnie można ustawić AgentVisitTime indywidualnie dla każdego punktu (obiektu). Drugim opcjonalnym polem jest Importance – ważność odwiedzenia tego obiektu. Domyślnie wszystkie obiekty mają ważność 100; można ustawiać mniejsze i większe wartości. Na przykład ważność 210 oznacza, że opłaca się odwiedzić taki punkt zamiast dwóch innych o ważności 100, albo czterech innych o ważności 50. Trzecim opcjonalnym polem jest Name – to pole jest ignorowane podczas optymalizacji, ale może być przydatne do identyfikowania poszczególnych lokalizacji. Nazwy są też wyświetlane w GUI.
  • Agents – tablica struktur zawierających szerokość i długość geograficzną – początkowe współrzędne pobytu kurierów (tym samym określają konkretną, stałą liczbę kurierów). OptiFacility może postarać się ich lepiej rozmieścić, ale nie zmieni ich liczby. Jak pokazuje przykładowe zapytanie powyżej, opcjonalnie można ustawić kilka parametrów indywidualnie dla każdego pracownika – tak, że zaczynają i kończą pracę o różnych godzinach, poruszają się z różnymi prędkościami, i koszt ich pracy różni się.
  • Distances – opcjonalne pole z własnymi odległościami między lokalizacjami.
  • Task – wybór zadania optymalizacji – jedno z:
    • FindRoutesCommutingIncluded optymalizuje trasy nie zmieniając lokalizacji kurierów; zwraca oszacowanie kosztu wliczając czasy dojazdu i powrotu. W tym zadaniu jest możliwe, że część bardzo odległych lokalizacji nie zostanie obsłużona – to takie lokalizacje, do których czas dojazdu i powrotu przekracza dniowy czas pracy kuriera.
    • FindRoutesCommutingExcluded postępuje jak powyżej, jednak nie dolicza kosztów i czasu dojazdu od bazowego miejsca pobytu kuriera do pierwszej lokalizacji danego dnia oraz kosztu i czasu powrotu od ostatniej obsłużonej lokalizacji danego dnia do bazowego miejsca pobytu kuriera. W związku z tym każda lokalizacja będzie zawsze obsłużona, nawet te do których dojazd zajmuje bardzo dużo czasu.
    • RecommendAgentLocationsCommutingIncluded stara się zaproponować lepsze lokalizacje kurierów, aby zrównoważyć ich obciążenie i obsłużyć wszystkie lokalizacje minimalizując czas i koszt.

Oprócz tego OptiFacility dostarcza kilku zaawansowanych funkcjonalności, o których możesz poczytać później: umiejętności wymagane przez lokalizacje i dostarczane przez pracowników, pojemności (rozwożenie i zbieranie) oraz czasowe okienka dostępności.

Facilities i Agents są numerowane od zera jako jedna sekwencja – w ten sposób każdy obiekt i każdy kurier otrzymuje unikatowy numer (identyfikator, indeks), który będzie wykorzystywany w odpowiedzi Serwera.

Po chwili Serwer odpowie również w formacie json podając trasy (listy indeksów lokalizacji/punktów) przypisane do kurierów, ewentualnie zopymalizowane lokalizacje kurierów, oraz różne dodatkowe informacje dotyczące zwróconego rozwiązania.

Dokonując optymalizacji, Serwer stara się obsłużyć jak najwięcej obiektów, minimalizować sumaryczny czas (a zatem również odległości i koszt) tej obsługi, oraz ewentualnie zbalansować obciążenie kurierów.

2. Odpowiedź Serwera

Przykładowa odpowiedź dla powyższego zlecenia wygląda tak:

{
  "Version": 224,
  "RequestID": 777,
  "StatusCode": 0,
  "StatusMessage": "OK",
  "Solution": {
    "InstanceHash": "9,1,0,0",
    "EstimatedCost": 1147.204614948091,
    "Unvisited": [ 2, 3, 4, 5, 6, 8 ],
    "Assignments": [
      {
        "AgentID": 9,
        "LatLon": [ 52.0, 21.0 ],
        "UsedHours": 11.853208505539666,
        "UsedDays": 2,
        "Route": [
  {"Location":9, "Context":1, "TimeArrival": 7.0, "TimeDeparture": 7.0, "ProductLoad":[] },
  {"Location":7, "Context":0, "TimeArrival": 8.9, "TimeDeparture": 9.8, "ProductLoad":[] },
  {"Location":9, "Context":1, "TimeArrival":11.7, "TimeDeparture":31.0, "ProductLoad":[] },
  {"Location":1, "Context":0, "TimeArrival":33.5, "TimeDeparture":34.4, "ProductLoad":[] },
  {"Location":0, "Context":0, "TimeArrival":36.6, "TimeDeparture":37.5, "ProductLoad":[] },
  {"Location":9, "Context":1, "TimeArrival":38.1, "TimeDeparture":38.1, "ProductLoad":[] }
        ]
      }
    ]
  }
}

Wyjaśnienie znaczenia pól w odpowiedzi serwera:

  • Version – wersja oprogramowania i algorytmów Serwera.
  • RequestID – powtórzony ID ze zlecenia.
  • StatusCode, StatusMessage – informacja o potencjalnych problemach, np. błędnym zapytaniu, błędnym formacie, braku pól lub błędnym typie ich wartości, itp. StatusCode=0 to brak błędu.
  • Solution – przypisanie zoptymalizowanych tras dla położenia kurierów.
    • InstanceHash – krótkie podsumowanie instancji problemu, której dotyczy to rozwiązanie.
    • EstimatedCost – oszacowanie kosztu (iloczyn przejechanych kilometrów i kosztu przejechanego kilometra + iloczyn obsłużonych lokalizacji i kosztu obsługi lokalizacji). Nie zawiera kary za nieobsłużone lokalizacje.
    • Unvisited – zbiór obiektów które nie zostały obsłużone przez żadnego kuriera, bo leżały za daleko (dojazd do nich i z powrotem zająłby więcej niż dzienny czas pracy kuriera), albo nie udało się spełnić zadanych ograniczeń (umiejętności, okienek czasowych albo pojemności). Kolejność numerów obiektów na liście nie ma znaczenia; w przykładzie powyżej jest sześć takich lokalizacji.
    • Assignments – tablica przypisań tras dla kurierów:
      • AgentID – unikatowy numer agenta (zgodnie z zasadą numerowania z zapytania).
      • LatLon – współrzędne bazowego miejsca pobytu kuriera. Dla Task równego FindRoutesCommutingIncluded lub FindRoutesCommutingExcluded to są miejsca podane w zapytaniu. Dla RecommendAgentLocationsCommutingIncluded bazowe miejsce może się różnić od tego podanego w zapytaniu – kurierzy mogą być przesunięci przez Serwer w korzystniejsze miejsca.
      • UsedHours – ile godzin zajęło w sumie kurierowi podróżowanie i obsługa lokalizacji.
      • UsedDays – ile dni (liczba całkowita) zajęło kurierowi podróżowanie i obsługa lokalizacji.
      • Route – sekwencja struktur odpowiadających kolejnym lokalizacjom odwiedzanym przez kuriera. Każda struktura zawiera:
        • Location – numer odwiedzanej lokalizacji. Jeśli numer ten jest równy AgentID, to oznacza, że kurier wraca do swojego miejsca stałego pobytu.
        • TimeArrival i TimeDeparture – czas przyjazdu do i wyjazdu z lokalizacji.
        • Context – przyczyna/cel wizyty. Obecnie może być 0 (zwykła wizyta pracownika w obiekcie), 1 (powrót do bazy na noc) albo 2 (powrót do bazy w celu załadunku/wyładunku, kiedy ograniczenia pojemnościowe zostały przekroczone).
        • ProductLoad – bieżący stan załadowania poszczególnych produktów, istotny tylko wtedy gdy używasz pojemności (rozwożenie i zbieranie).

Zobaczmy, jak takie minimalne, przykładowe zlecenie zamieszczone powyżej wygląda na mapie. Zlecenie definiowało 9 lokalizacji obiektów (numery od 0 do 8) oraz jednego kuriera (numer 9) przebywającego blisko obiektu o numerze 0.

Dla zadania FindRoutesCommutingIncluded zoptymalizowane trasy z powyższej przykładowej odpowiedzi Serwera wyglądają tak:

Ponieważ w tym przykładzie obiekty są odległe o setki kilometrów (rozpiętość mapy to 1000km) a agent podróżuje dosyć wolno, dysponując 7.5-godzinnym dniem pracy był w stanie odwiedzić przez dwa dni ("UsedDays": 2) tylko trzy punkty (7 w pierwszej wyprawie oraz 1 i 0 w drugiej, czyli pola "Location" w "Route" tworzą sekwencję 9, 7, 9, 1, 0, 9. Sześć obiektów pozostało poza zasięgiem (podróż do obiektu, obsługa, i podróż z powrotem w przypadku każdego z nich trwa więcej niż dzień pracy).

Efekt zlecenia zadania RecommendAgentLocationsCommutingIncluded wygląda tak:

Kurier 9 został przeniesiony w inne miejsce (tuż przy obiekcie 6) i dzięki temu może w ciągu jednego dnia ("UsedDays": 1) odwiedzić dwie lokalizacje – jego trasa to 9, 6, 4, 9. Zauważ że agent odwiedza teraz o jedną lokalizację mniej niż wcześniej, ale lokalizacja 6 ma ważność 200, zatem jej odwiedzenie jest warte tyle co odwiedzenie dwóch lokalizacji o domyślnej ważności (100). Zauważ też że przed przenosinami agent odwiedzał lokalizację 7, która miała niską ważność (33), zatem suma ważności nieodwiedzonych lokalizacji wynosiła 700. Po przeniesieniu agenta suma ta spadła do 633, i nie ma lokalizacji agenta która dałaby niższą wartość.

Celem powyższego minimalnego przykładu z jednym kurierem było wyjaśnienie znaczenia pól w komunikatach json, więc był on zbyt mały, by pokazać bardziej interesujące przypadki. Spójrzmy na poniższy nieco większy przykład, gdzie firma ma do dyspozycji pięciu specjalistów-serwisantów obsługujących obszar całej Polski. System zoptymalizował ich trasy oraz położenie tak, że przy zadanych ograniczeniach nie da się znaleźć nic lepszego:

  • Obciążenie kuriera 1: 8.01 dnia
  • Obciążenie kuriera 2: 10.35 dnia
  • Obciążenie kuriera 3: 9.89 dnia
  • Obciążenie kuriera 4: 9.38 dnia
  • Obciążenie kuriera 5: 5.79 dnia
  • Obiektów poza zasięgiem kurierów: 8

Więcej przykładów działania OptiFacility zobaczysz na filmie poniżej:

3. Praca z Serwerem i optymalizacja kosztów logistyki

W praktyce końcowy użytkownik jest zainteresowany minimalizacją kosztów obsługi, zatem optymalizacją położenia kurierów oraz ich optymalnymi trasami. Używając Serwera OptiFacility można realizować następujące scenariusze:

  1. Wyznaczenie zoptymalizowanych tras dla istniejących lokalizacji kurierów. Wykorzystujemy wtedy zadanie FindRoutesCommutingIncluded albo FindRoutesCommutingExcluded, dla których Serwer nie zmienia lokalizacji kurierów.
  2. Wyznaczenie minimalnej liczby kurierów obsługujących wszystkie lokalizacje, wliczając czasy dojazdu. Osiągamy to poprzez wywoływanie usługi Serwera w trybie FindRoutesCommutingIncluded (jeśli sami podajemy sensowne, bazowe lokalizacje kurierów) albo RecommendAgentLocationsCommutingIncluded (jeśli serwer ma proponować lokalizacje kurierów). Sukcesywnie podajemy coraz większą liczbę kurierów – zaczynając od 1 kuriera, potem 2, 3... dopóki lista "Route" dla "AgentID":-1 nie stanie się pusta. To oznacza, że kurierzy byli w stanie dojechać do wszystkich lokalizacji.
  3. Wyznaczenie minimalnej liczby kurierów obsługujących wszystkie lokalizacje w ciągu zadanej liczby dni osiągamy poprzez analogiczne postępowanie; ma tu sens również tryb FindRoutesCommutingExcluded. Zwiększamy liczbę kurierów tak długo, aż lista "Route" dla "AgentID":-1 nie stanie się pusta (w trybie FindRoutesCommutingExcluded jeden kurier zawsze wystarczy) oraz zwiększamy dalej, dopóki dla wszystkich kurierów ich pole UsedDays będzie mniejsze lub równe pożądanej liczbie dni (np. 5). I w tym przypadku możemy samodzielnie określać stałe lokalizacje bazowe kurierów, albo pozwalać by Serwer próbował je ulepszać.
  4. Nietypowe sytuacje i zmieniające się dynamicznie warunki: Serwera OptiFacility można używać w bardziej kreatywny sposób, na przykład:
    1. Jeśli zaplanowano globalny harmonogram obsługi dla całej grupy kurierów i z jakiegoś powodu chcemy przekazać obsługę części lokalizacji od jednego kuriera do drugiego, możemy zadać dwa osobne zapytania do Serwera, w każdym z nich uwzględniając tylko jednego z tych dwóch kurierów, i podając punkty które tylko on ma obsłużyć.
    2. Jeśli chcemy przeplanować kurierowi od nowa fragment ustalonego wcześniej planu (nawet w trakcie dnia), można wysłać zapytanie tylko z punktami dla tego pojedynczego kuriera, podając odpowiednio mniejszą liczbę godzin pracy jeśli część dnia już upłynęła. Można nawet podać jego bieżącą lokalizację z GPS.
    3. Jeśli wśród 50 pracowników chcemy zmienić plan czterem z nich, możemy zapytać Serwer o zoptymalizowany plan tylko dla nich (zapytanie zawiera wtedy tylko 4 kurierów i lokalizacje, które mają obsłużyć).
    4. Jeśli lokalizacje są z jakiegoś powodu przypisane na sztywno kurierom, zadajemy serwerowi osobne zapytania o ich trasy, w każdym zapytaniu podając jednego kuriera i przypisane mu lokalizacje. Używając trybu RecommendAgentLocationsCommutingIncluded możemy też spytać Serwera o to, czy istnieje korzystniejsza lokalizacja kuriera niż ta, którą podajemy w zapytaniu.