Skip to content

Commit b005b98

Browse files
authored
Merge pull request #782 from OpenTrafficCam/bug/9173-otanalytics-counts_15min-csv-does-not-contain-line-with-count-0-for-combinations-after-the-first-time-point
bug/9173-otanalytics-counts_15min-csv-does-not-contain-line-with-count-0-for-combinations-after-the-first-time-point
2 parents 829c788 + 2913e08 commit b005b98

4 files changed

Lines changed: 296 additions & 151 deletions

File tree

OTAnalytics/application/analysis/traffic_counting.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,19 +1031,22 @@ def __assignment_filter_for(
10311031
specification: CountingSpecificationDto,
10321032
) -> Callable[[RoadUserAssignment], bool]:
10331033
"""Create a filter interval using the given specifications start and end time
1034-
as lower and upper bounds respectively.
1034+
as lower and upper bounds respectively. The interval is always closed at the
1035+
start time and open at the end time: [specification-start, specification-end).
1036+
The start time is always inside the interval while the end time is always
1037+
outside the interval.
10351038
10361039
The resulting filter determines whether RoadUserAssignments fall into the
10371040
desired observation interval and should be counted.
10381041
10391042
The is_upper/lower_strict parameters of this TrafficCounting object
10401043
can be used to configure the filter.
10411044
A strict bound does not allow the assignment to overlap the respective bound.
1042-
The four combinations are as follows (open = '('; closed = '['):
1043-
- [lower, upper]: both start and end of the rua must be inside the interval
1044-
- [lower, upper): the start of the rua must be inside the interval
1045-
- (lower, upper]: the end of the rua must be inside the interval
1046-
- (lower, upper): either start or end of the rua must be in the interval,
1045+
The four combinations are as follows:
1046+
- (True, True): both start and end of the rua must be inside the interval
1047+
- (True, False): the start of the rua must be inside the interval
1048+
- (False, True): the end of the rua must be inside the interval
1049+
- (False, False): either start or end of the rua must be in the interval,
10471050
or [start, end] must enclose the interval
10481051
10491052
Args:
@@ -1061,18 +1064,18 @@ def __assignment_filter_for(
10611064
is_upper_strict = self._filter_upper_bound_strict
10621065

10631066
if is_lower_strict and is_upper_strict:
1064-
return lambda rua: lower <= start_of(rua) and end_of(rua) <= upper
1067+
return lambda rua: lower <= start_of(rua) and end_of(rua) < upper
10651068

10661069
elif is_lower_strict and not is_upper_strict:
1067-
return lambda rua: lower <= start_of(rua) <= upper
1070+
return lambda rua: lower <= start_of(rua) < upper
10681071

10691072
elif not is_lower_strict and is_upper_strict:
1070-
return lambda rua: lower <= end_of(rua) <= upper
1073+
return lambda rua: lower <= end_of(rua) < upper
10711074

10721075
else:
10731076
return (
1074-
lambda rua: lower <= start_of(rua) <= upper
1075-
or lower <= end_of(rua) <= upper
1077+
lambda rua: lower <= start_of(rua) < upper
1078+
or lower <= end_of(rua) < upper
10761079
or (start_of(rua) <= lower and upper <= end_of(rua))
10771080
)
10781081

OTAnalytics/plugin_parser/export.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,24 @@ def explode(self) -> list[Tag]:
167167
second=0, microsecond=0
168168
)
169169
)
170-
maximum = self._end - start_without_seconds
171-
duration = int(maximum.total_seconds())
172170
interval = self._specification.counting_specification.interval_in_minutes * 60
171+
172+
# Round down start time to the nearest interval boundary
173+
start_timestamp = start_without_seconds.timestamp()
174+
interval_boundary = (start_timestamp // interval) * interval
175+
start_at_boundary = start_without_seconds.fromtimestamp(
176+
interval_boundary, tz=start_without_seconds.tzinfo
177+
)
178+
179+
maximum = self._end - start_at_boundary
180+
duration = int(maximum.total_seconds())
181+
if self._end.timestamp() % interval == 0:
182+
duration += 1
173183
for flow in self._specification.flow_name_info:
174184
for mode in self._specification.counting_specification.modes:
175185
for delta in range(0, duration, interval):
176186
offset = timedelta(seconds=delta)
177-
start = start_without_seconds + offset
187+
start = start_at_boundary + offset
178188
interval_time = timedelta(seconds=interval)
179189
tag = (
180190
create_flow_tag(flow.name)

tests/OTAnalytics/application/analysis/test_traffic_counting.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1273,7 +1273,7 @@ def tests_traffic_counting_filter(
12731273
# Create specification
12741274
counting_specification = CountingSpecificationDto(
12751275
start=start,
1276-
end=end,
1276+
end=end + timedelta(milliseconds=1),
12771277
interval_in_minutes=15,
12781278
modes=modes,
12791279
output_format="csv",

0 commit comments

Comments
 (0)