1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
|
3
|
1 |
|
""" |
4
|
|
|
An Optimization Problem refers to a collection of addresses that need to be |
5
|
|
|
visited. |
6
|
|
|
|
7
|
|
|
The optimization problem takes into consideration all of the addresses that |
8
|
|
|
need to be visited and all the constraints associated with each address and |
9
|
|
|
depot. |
10
|
|
|
|
11
|
|
|
It is preferable to create an optimization problem with as many orders in it |
12
|
|
|
as possible, so that the optimization engine is able to consider the entire |
13
|
|
|
problem set. |
14
|
|
|
|
15
|
|
|
This is different from a :class:`~route4me.sdk.models.Route`, which |
16
|
|
|
is a sequence of addresses that need to be visited by a single vehicle and |
17
|
|
|
driver in a fixed time period. Solving an Optimization Problem results in |
18
|
|
|
a number of routes. (Possibly recurring in the future) |
19
|
|
|
|
20
|
|
|
.. seealso:: https://route4me.io/docs/#optimizations |
21
|
|
|
|
22
|
|
|
""" |
23
|
|
|
|
24
|
1 |
|
import pydash |
25
|
|
|
|
26
|
1 |
|
from ..models import Optimization |
27
|
1 |
|
from ..enums import OptimizationStateEnum |
28
|
|
|
|
29
|
1 |
|
from ..errors import Route4MeApiError |
30
|
1 |
|
from route4me.sdk.utils import PagedList |
31
|
|
|
|
32
|
1 |
|
from route4me.sdk._internals import add_limit_offset_to_query_string |
33
|
1 |
|
from route4me.sdk._internals.typeconv import bool201 |
34
|
1 |
|
from route4me.sdk._internals.net import NetworkClient |
35
|
|
|
|
36
|
|
|
|
37
|
1 |
|
class Optimizations(object): |
38
|
|
|
""" |
39
|
|
|
Optimizations endpoint |
40
|
|
|
""" |
41
|
|
|
|
42
|
1 |
|
def __init__(self, api_key=None, _network_client=None): |
43
|
1 |
|
nc = _network_client |
44
|
1 |
|
if nc is None: |
45
|
1 |
|
nc = NetworkClient(api_key) |
46
|
1 |
|
self.__nc = nc |
47
|
|
|
|
48
|
1 |
|
def create(self, optimization_data, optimized_callback_url=None): |
49
|
|
|
""" |
50
|
|
|
Create a new optimization through the Route4Me API |
51
|
|
|
|
52
|
|
|
You could pass any valid URL as :paramref:`optimized_callback_url` |
53
|
|
|
parameter. |
54
|
|
|
|
55
|
|
|
The callback URL is a URL that gets called when the optimization is |
56
|
|
|
solved, or if there is an error. The callback is called with a |
57
|
|
|
**POST** request. The example of the POST data sent: |
58
|
|
|
|
59
|
|
|
.. code-block:: javascript |
60
|
|
|
|
61
|
|
|
{ |
62
|
|
|
"timestamp": 1500111222, // seconds |
63
|
|
|
"state": 4, // ID of the optimization state |
64
|
|
|
|
65
|
|
|
// ID of Optimization Problem |
66
|
|
|
"optimization_problem_id": "1EDB78F63556D99336E06A13A34CF139" |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
The ``state`` is a value from the enumeration |
70
|
|
|
:class:`~route4me.sdk.enums.OptimizationStateEnum` |
71
|
|
|
|
72
|
|
|
.. seealso:: |
73
|
|
|
|
74
|
|
|
Route4Me API: https://route4me.io/docs/#create-an-optimization |
75
|
|
|
|
76
|
|
|
:param optimization_data: Optimization data |
77
|
|
|
:type optimization_data: ~route4me.sdk.models.Optimization or dict |
78
|
|
|
:param optimized_callback_url: Optimization done callback URL, defaults \ |
79
|
|
|
to None |
80
|
|
|
:type optimized_callback_url: str or None, optional |
81
|
|
|
:returns: New optimization |
82
|
|
|
:rtype: ~route4me.sdk.models.Optimization |
83
|
|
|
""" |
84
|
|
|
|
85
|
1 |
|
query = None |
86
|
1 |
|
if optimized_callback_url: |
87
|
1 |
|
query = { |
88
|
|
|
'optimized_callback_url': optimized_callback_url, |
89
|
|
|
} |
90
|
|
|
|
91
|
1 |
|
data = optimization_data |
92
|
|
|
|
93
|
|
|
# if isinstance(optimization_data, BaseModel): |
94
|
|
|
# data = optimization_data.raw |
95
|
|
|
|
96
|
1 |
|
res = self.__nc.post( |
97
|
|
|
'/api.v4/optimization_problem.php', |
98
|
|
|
subdomain='www', |
99
|
|
|
query=query, |
100
|
|
|
data=data, |
101
|
|
|
) |
102
|
1 |
|
return Optimization(res) |
103
|
|
|
|
104
|
1 |
|
def get(self, ID): |
105
|
|
|
""" |
106
|
|
|
GET a single optimization by ID. |
107
|
|
|
|
108
|
|
|
.. seealso:: |
109
|
|
|
|
110
|
|
|
Route4Me API: https://route4me.io/docs/#get-an-optimization |
111
|
|
|
|
112
|
|
|
:param ID: Optimization Problem ID |
113
|
|
|
:type ID: str |
114
|
|
|
:returns: Optimization data |
115
|
|
|
:rtype: ~route4me.sdk.models.Optimization |
116
|
|
|
|
117
|
|
|
:raises ~route4me.sdk.errors.Route4MeEntityNotFoundError: if \ |
118
|
|
|
optimization was not found |
119
|
|
|
""" |
120
|
|
|
|
121
|
1 |
|
res = self.__nc.get( |
122
|
|
|
'/api.v4/optimization_problem.php', |
123
|
|
|
subdomain='www', |
124
|
|
|
query={ |
125
|
|
|
'optimization_problem_id': ID, |
126
|
|
|
} |
127
|
|
|
) |
128
|
|
|
|
129
|
1 |
|
return Optimization(res) |
130
|
|
|
|
131
|
1 |
|
def list(self, states=None, limit=None, offset=None): |
132
|
|
|
""" |
133
|
|
|
GET all optimizations belonging to a user. |
134
|
|
|
|
135
|
|
|
Optionally filtered |
136
|
|
|
|
137
|
|
|
.. seealso:: |
138
|
|
|
|
139
|
|
|
Route4Me API: https://route4me.io/docs/#get-optimizations |
140
|
|
|
|
141
|
|
|
:param states: Comma separated list of states, you can pass one CSV \ |
142
|
|
|
string or any enumerable of state IDS (string and enums are \ |
143
|
|
|
supported), defaults to None |
144
|
|
|
:type states: str or list(str) or list(OptimizationStateEnum), optional |
145
|
|
|
:param limit: Search limitation, defaults to None |
146
|
|
|
:type limit: int, optional |
147
|
|
|
:param offset: Search starting position, defaults to None |
148
|
|
|
:type offset: int, optional |
149
|
|
|
""" |
150
|
1 |
|
qs = {} |
151
|
1 |
|
add_limit_offset_to_query_string(limit, offset, qs) |
152
|
|
|
|
153
|
1 |
|
if states: |
154
|
1 |
|
s = OptimizationStateEnum.parse_many(states) |
155
|
1 |
|
qs['state'] = ','.join([str(i.value) for i in s]) |
156
|
|
|
|
157
|
1 |
|
res = self.__nc.get( |
158
|
|
|
'/api.v4/optimization_problem.php', |
159
|
|
|
subdomain='www', |
160
|
|
|
query=qs |
161
|
|
|
) |
162
|
|
|
|
163
|
1 |
|
return PagedList( |
164
|
|
|
total=res['totalRecords'], |
165
|
|
|
limit=limit, |
166
|
|
|
offset=offset, |
167
|
|
|
items=[Optimization(item) for item in res['optimizations']], |
168
|
|
|
) |
169
|
|
|
|
170
|
1 |
|
def update( |
171
|
|
|
self, |
172
|
|
|
ID, |
173
|
|
|
optimization_data=None, |
174
|
|
|
reoptimize=False, |
175
|
|
|
# TODO: try to implement or remove! |
176
|
|
|
# optimized_callback_url=None, |
177
|
|
|
): |
178
|
|
|
""" |
179
|
|
|
Update existing optimization problem, by changing some parameters or |
180
|
|
|
addresses. Optionally you can re-run the optimization engine for the |
181
|
|
|
optimization problem |
182
|
|
|
|
183
|
|
|
Notice, that you can re-run optimization without modifications. |
184
|
|
|
|
185
|
|
|
.. seealso:: |
186
|
|
|
|
187
|
|
|
Route4Me API: https://route4me.io/docs/#re-optimize-an-optimization |
188
|
|
|
|
189
|
|
|
:param ID: Optimization problem ID |
190
|
|
|
:type ID: str |
191
|
|
|
:param optimization_data: Optimization data, defaults to None |
192
|
|
|
:type optimization_data: ~route4me.sdk.models.Optimization or dict, \ |
193
|
|
|
optional |
194
|
|
|
:param reoptimize: Whether to re-run optimization, defaults to \ |
195
|
|
|
:data:`False` |
196
|
|
|
:type reoptimize: bool, optional |
197
|
|
|
:returns: Updated optimization |
198
|
|
|
:rtype: ~route4me.sdk.models.Optimization |
199
|
|
|
""" |
200
|
1 |
|
query = { |
201
|
|
|
'optimization_problem_id': ID, |
202
|
|
|
'reoptimize': bool201(reoptimize), |
203
|
|
|
} |
204
|
|
|
|
205
|
1 |
|
data = optimization_data if optimization_data else {} |
206
|
|
|
|
207
|
1 |
|
res = self.__nc.put( |
208
|
|
|
'/api.v4/optimization_problem.php', |
209
|
|
|
subdomain='www', |
210
|
|
|
query=query, |
211
|
|
|
data=data, |
212
|
|
|
) |
213
|
1 |
|
return Optimization(res) |
214
|
|
|
|
215
|
1 |
|
def remove(self, ID): |
216
|
|
|
""" |
217
|
|
|
Remove an existing optimization belonging to an user. |
218
|
|
|
|
219
|
|
|
.. seealso:: |
220
|
|
|
|
221
|
|
|
Route4Me API: https://route4me.io/docs/#remove-an-optimization |
222
|
|
|
|
223
|
|
|
:param ID: Optimization Problem ID |
224
|
|
|
:type ID: str |
225
|
|
|
:returns: Always :data:`True` |
226
|
|
|
:rtype: bool |
227
|
|
|
|
228
|
|
|
:raises ~route4me.sdk.errors.Route4MeApiError: if Route4Me API \ |
229
|
|
|
returned not expected response |
230
|
|
|
:raises ~route4me.sdk.errors.Route4MeEntityNotFoundError: if \ |
231
|
|
|
optimization was not found |
232
|
|
|
""" |
233
|
|
|
|
234
|
1 |
|
res = self.__nc.delete( |
235
|
|
|
'/api.v4/optimization_problem.php', |
236
|
|
|
subdomain='www', |
237
|
|
|
query={ |
238
|
|
|
'optimization_problem_id': ID, |
239
|
|
|
} |
240
|
|
|
) |
241
|
|
|
|
242
|
1 |
|
if not pydash.get(res, 'status'): |
243
|
|
|
# TODO: this exception should contain METHOD and URL fields |
244
|
1 |
|
raise Route4MeApiError( |
245
|
|
|
'Not expected response', |
246
|
|
|
code='route4me.sdk.api_error', |
247
|
|
|
details={ |
248
|
|
|
'res': res, |
249
|
|
|
}, |
250
|
|
|
method='DELETE', |
251
|
|
|
# url='' |
252
|
|
|
) |
253
|
|
|
|
254
|
1 |
|
return True |
255
|
|
|
|
256
|
1 |
|
def reoptimize( |
257
|
|
|
self, |
258
|
|
|
ID, |
259
|
|
|
# optimization_data=None, |
260
|
|
|
# optimized_callback_url=None, |
261
|
|
|
): |
262
|
|
|
""" |
263
|
|
|
An alias for :meth:`.update`, with ``reoptimize`` set to :data:`True` |
264
|
|
|
""" |
265
|
|
|
return self.update(ID=ID, reoptimize=True) |
266
|
|
|
|