-
Notifications
You must be signed in to change notification settings - Fork 27
/
Copy pathhelpers.py
132 lines (113 loc) · 4.84 KB
/
helpers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def get_chunks(porders, ini, end, ix=0):
if ix >= len(porders):
return [[ini, end, []]]
order = porders[ix]
ix += 1
bu = getattr(order, 'new_billed_until', order.billed_until)
if not bu or bu <= ini or order.registered_on >= end:
return get_chunks(porders, ini, end, ix=ix)
result = []
if order.registered_on < end and order.registered_on > ini:
ro = order.registered_on
result = get_chunks(porders, ini, ro, ix=ix)
ini = ro
if bu < end:
result += get_chunks(porders, bu, end, ix=ix)
end = bu
chunks = get_chunks(porders, ini, end, ix=ix)
for chunk in chunks:
chunk[2].insert(0, order)
result.append(chunk)
return result
def cmp_billed_until_or_registered_on(a, b):
"""
1) billed_until greater first
2) registered_on smaller first
"""
if a.billed_until == b.billed_until:
# Use pk which is more reliable than registered_on date
return a.id-b.id
elif a.billed_until and b.billed_until:
return (b.billed_until-a.billed_until).days
elif a.billed_until:
return (b.registered_on-a.billed_until).days
return (b.billed_until-a.registered_on).days
class Interval(object):
def __init__(self, ini, end, order=None):
self.ini = ini
self.end = end
self.order = order
def __len__(self):
return max((self.end-self.ini).days, 0)
def __sub__(self, other):
remaining = []
if self.ini < other.ini:
remaining.append(Interval(self.ini, min(self.end, other.ini), self.order))
if self.end > other.end:
remaining.append(Interval(max(self.ini,other.end), self.end, self.order))
return remaining
def __repr__(self):
return "<ini:{ini}/end:{end}>".format(
ini=self.ini.strftime('%Y-%-m-%-d'),
end=self.end.strftime('%Y-%-m-%-d')
)
def intersect(self, other, remaining_self=None, remaining_other=None):
if remaining_self is not None:
remaining_self += (self - other)
if remaining_other is not None:
remaining_other += (other - self)
result = Interval(max(self.ini, other.ini), min(self.end, other.end), self.order)
if len(result)>0:
return result
else:
return None
def intersect_set(self, others, remaining_self=None, remaining_other=None):
intersections = []
for interval in others:
intersection = self.intersect(interval, remaining_self, remaining_other)
if intersection:
intersections.append(intersection)
return intersections
def get_intersections(order_intervals, compensations):
intersections = []
for compensation in compensations:
intersection = compensation.intersect_set(order_intervals)
length = 0
for intersection_interval in intersection:
length += len(intersection_interval)
intersections.append((length, compensation))
return sorted(intersections, key=lambda i: i[0])
def intersect(compensation, order_intervals):
# Intervals should not overlap
compensated = []
not_compensated = []
unused_compensation = []
for interval in order_intervals:
compensated.append(compensation.intersect(interval, unused_compensation, not_compensated))
return (compensated, not_compensated, unused_compensation)
def apply_compensation(order, compensation):
remaining_order = []
remaining_compensation = []
applied_compensation = compensation.intersect_set(order, remaining_compensation, remaining_order)
return applied_compensation, remaining_order, remaining_compensation
def update_intersections(not_compensated, compensations):
# TODO can be optimized
compensation_intervals = []
for __, compensation in compensations:
compensation_intervals.append(compensation)
return get_intersections(not_compensated, compensation_intervals)
def compensate(order, compensations):
remaining_interval = [order]
ordered_intersections = get_intersections(remaining_interval, compensations)
applied_compensations = []
remaining_compensations = []
while ordered_intersections and ordered_intersections[len(ordered_intersections)-1][0]>0:
# Apply the first compensation:
__, compensation = ordered_intersections.pop()
(applied_compensation, remaining_interval, remaining_compensation) = apply_compensation(remaining_interval, compensation)
remaining_compensations += remaining_compensation
applied_compensations += applied_compensation
ordered_intersections = update_intersections(remaining_interval, ordered_intersections)
for __, compensation in ordered_intersections:
remaining_compensations.append(compensation)
return remaining_compensations, applied_compensations