Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug/5243-events-of-ottrks-not-starting-at-export-interval-exported-in-wrong-intervals-(counting) #547

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions OTAnalytics/application/analysis/traffic_counting.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,11 @@ def create_mode_tag(tag: str) -> Tag:
return SingleTag(level=LEVEL_CLASSIFICATION, id=tag)


def create_timeslot_tag(start_of_time_slot: datetime, interval: timedelta) -> Tag:
def create_timeslot_tag(start: datetime, interval: timedelta) -> Tag:
interval_seconds = interval.total_seconds()
original_time = int(start.timestamp())
result = int(original_time / interval_seconds) * interval_seconds
start_of_time_slot = datetime.fromtimestamp(result, tz=timezone.utc)
end_of_time_slot = start_of_time_slot + interval
serialized_start = start_of_time_slot.strftime(r"%Y-%m-%d %H:%M:%S")
serialized_end = end_of_time_slot.strftime(r"%Y-%m-%d %H:%M:%S")
Expand Down Expand Up @@ -375,11 +379,7 @@ def __init__(self, interval: timedelta) -> None:
self._interval = interval

def create_tag(self, assignment: RoadUserAssignment) -> Tag:
original_time = int(assignment.events.start.occurrence.timestamp())
interval_seconds = self._interval.total_seconds()
result = int(original_time / interval_seconds) * interval_seconds
start_of_time_slot = datetime.fromtimestamp(result, timezone.utc)
return create_timeslot_tag(start_of_time_slot, self._interval)
return create_timeslot_tag(assignment.events.start.occurrence, self._interval)


class CountableAssignments:
Expand Down Expand Up @@ -918,7 +918,10 @@ def export(self, specification: CountingSpecificationDto) -> None:
"""
if self._event_repository.is_empty():
self._create_events()
events = self._event_repository.get_all()
events = self._event_repository.get(
start_date=specification.start,
end_date=specification.end,
)
flows = self._flow_repository.get_all()
assigned_flows = self._assigner.assign(events, flows)
tagger = self._tagger_factory.create_tagger(specification)
Expand Down
34 changes: 31 additions & 3 deletions OTAnalytics/domain/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,16 +507,22 @@ def get_previous_before(

def get(
self,
start_date: datetime | None = None,
end_date: datetime | None = None,
sections: Sequence[SectionId] | None = None,
event_types: Sequence[EventType] | None = None,
) -> Iterable[Event]:
if event_types is None:
event_types = []
if sections is None:
sections = []
filter_function = self.__create_filter(event_types)
type_filter = self.__create_type_filter(event_types)
start_filter = self.__create_start_filter(start_date)
end_filter = self.__create_end_filter(end_date)
events = self.__create_event_list(sections)
return list(filter(filter_function, events))
return list(
filter(start_filter, filter(end_filter, filter(type_filter, events)))
)

def __create_event_list(self, sections: Sequence[SectionId]) -> Iterable[Event]:
if sections:
Expand All @@ -525,7 +531,29 @@ def __create_event_list(self, sections: Sequence[SectionId]) -> Iterable[Event]:
return self.get_all()

@staticmethod
def __create_filter(event_types: Sequence[EventType]) -> Callable[[Event], bool]:
def __create_type_filter(
event_types: Sequence[EventType],
) -> Callable[[Event], bool]:
if event_types:
return lambda actual: actual.event_type in event_types
return lambda event: True

@staticmethod
def __create_start_filter(start_date: datetime | None) -> Callable[[Event], bool]:
if start_date:
return after_filter(start_date)
return lambda event: True

@staticmethod
def __create_end_filter(end_date: datetime | None) -> Callable[[Event], bool]:
if end_date:
return before_filter(end_date)
return lambda event: True


def after_filter(date: datetime) -> Callable[[Event], bool]:
return lambda actual: actual.occurrence >= date


def before_filter(date: datetime) -> Callable[[Event], bool]:
return lambda actual: actual.occurrence <= date
36 changes: 35 additions & 1 deletion tests/OTAnalytics/application/analysis/test_traffic_counting.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
TaggerFactory,
TimeslotTagger,
create_export_specification,
create_timeslot_tag,
)
from OTAnalytics.application.analysis.traffic_counting_specification import (
CountingSpecificationDto,
Expand Down Expand Up @@ -64,6 +65,39 @@ def track(track_builder: TrackBuilder) -> Track:
return track_builder.build_track()


@pytest.mark.parametrize(
"start_time,expected_start_time,expected_end_time",
[
("00:00:00", "00:00:00", "00:15:00"),
("00:03:00", "00:00:00", "00:15:00"),
],
)
def test_create_timeslot_tag(
start_time: str,
expected_start_time: str,
expected_end_time: str,
) -> None:
start_date = f"2024-01-01 {start_time}"
expected_start_date = f"2024-01-01 {expected_start_time}"
expected_end_date = f"2024-01-01 {expected_end_time}"
current = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S").replace(
tzinfo=timezone.utc
)
interval = timedelta(minutes=15)
tag = create_timeslot_tag(current, interval)

expected_tag = MultiTag(
frozenset(
[
SingleTag(level=LEVEL_START_TIME, id=expected_start_date),
SingleTag(level=LEVEL_END_TIME, id=expected_end_date),
]
)
)

assert tag == expected_tag


class TestCountByFlow:
def test_to_dict(self) -> None:
value = 2
Expand Down Expand Up @@ -758,7 +792,7 @@ def test_count_traffic(self) -> None:

use_case.export(counting_specification)

event_repository.get_all.assert_called_once()
event_repository.get.assert_called_once_with(start_date=start, end_date=end)
flow_repository.get_all.assert_called_once()
create_events.assert_called_once()
road_user_assigner.assign.assert_called_once()
Expand Down
91 changes: 84 additions & 7 deletions tests/OTAnalytics/domain/test_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,19 +574,89 @@ def test_get_previous_before(
assert actual_event == expected_event

@pytest.mark.parametrize(
"sections,event_type,expected_events",
"start_date,end_date,sections,event_type,expected_events",
[
([], [], all_events()),
([SECTION_ID_1], [], [event_1_section_1(), event_2_section_1()]),
([SECTION_ID_2], [], [event_1_section_2(), event_2_section_2()]),
([SECTION_ID_1, SECTION_ID_2], [], all_events()),
([SECTION_ID_1, SECTION_ID_2], DEFAULT_EVENT_TYPES, all_events()),
(
None,
None,
[],
[],
all_events(),
),
(
event_2_section_2().occurrence,
None,
[],
[],
[event_2_section_2()],
),
(
event_2_section_1().occurrence,
None,
[],
[],
[event_2_section_1(), event_2_section_2()],
),
(
None,
event_1_section_1().occurrence,
[],
[],
[event_1_section_1()],
),
(
None,
event_1_section_2().occurrence,
[],
[],
[event_1_section_1(), event_1_section_2()],
),
(
None,
event_2_section_1().occurrence,
[],
[],
[event_1_section_1(), event_2_section_1(), event_1_section_2()],
),
(
event_1_section_2().occurrence,
event_2_section_1().occurrence,
[],
[],
[event_2_section_1(), event_1_section_2()],
),
(
None,
None,
[SECTION_ID_1],
[],
[event_1_section_1(), event_2_section_1()],
),
(
None,
None,
[SECTION_ID_2],
[],
[event_1_section_2(), event_2_section_2()],
),
(None, None, [SECTION_ID_1, SECTION_ID_2], [], all_events()),
(
None,
None,
[SECTION_ID_1, SECTION_ID_2],
DEFAULT_EVENT_TYPES,
all_events(),
),
(
None,
None,
[SECTION_ID_1],
[EventType.SECTION_ENTER],
[event_1_section_1()],
),
(
None,
None,
[SECTION_ID_1],
[EventType.SECTION_LEAVE],
[event_2_section_1()],
Expand All @@ -595,13 +665,20 @@ def test_get_previous_before(
)
def test_get(
self,
start_date: datetime,
end_date: datetime,
sections: list[SectionId],
event_type: list[EventType],
expected_events: list[Event],
) -> None:
repository = EventRepository()
repository.add_all(all_events())

actual_events = repository.get(sections=sections, event_types=event_type)
actual_events = repository.get(
start_date=start_date,
end_date=end_date,
sections=sections,
event_types=event_type,
)

assert actual_events == expected_events