Skip to content

Commit a25f074

Browse files
authored
Merge pull request #4307 from hove-io/fix_poi_disruptions_in_journeys
[jormun]: Fix for disrutions on POI
2 parents 8a41c97 + be052f0 commit a25f074

File tree

5 files changed

+238
-51
lines changed

5 files changed

+238
-51
lines changed

source/jormungandr/jormungandr/interfaces/v1/Journeys.py

+57
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,62 @@ def wrapper(*args, **kwargs):
354354
return wrapper
355355

356356

357+
class handle_poi_disruptions(object):
358+
@staticmethod
359+
def is_absent(links, id):
360+
return next((False for link in links if link['id'] == id), True)
361+
362+
def __call__(self, f):
363+
@wraps(f)
364+
def wrapper(*args, **kwargs):
365+
objects = f(*args, **kwargs)
366+
if has_invalid_reponse_code(objects) or journeys_absent(objects):
367+
return objects
368+
369+
def get_disruption_uris(object):
370+
uris = set()
371+
for d in objects[0].get('disruptions', []):
372+
for io in d.get('impacted_objects', []):
373+
if io['pt_object']['embedded_type'] == "poi" and io['pt_object']['id'] == object['id']:
374+
uris.add(d['id'])
375+
if 'poi' not in io['pt_object']:
376+
io['pt_object']['poi'] = object
377+
378+
return uris
379+
380+
def update_for_poi(object):
381+
# Add links in poi object
382+
object_copy = deepcopy(object)
383+
object.setdefault('links', [])
384+
disruption_uris = get_disruption_uris(object_copy)
385+
for disruption_uri in disruption_uris:
386+
if self.is_absent(object['links'], disruption_uri):
387+
object['links'].append(
388+
create_internal_link(_type="disruption", rel="disruptions", id=disruption_uri)
389+
)
390+
391+
# We should only update 'from' object of the first section as well as 'to' object of the last one
392+
# since object poi can only be present in those two cases
393+
# If object is absent in first_section['from'] as well as last_section['to'] for the first journey
394+
# then no need to verify for the remaining journeys
395+
for j in objects[0].get('journeys', []):
396+
if "sections" not in j:
397+
continue
398+
399+
first_sec = j['sections'][0]
400+
last_sec = j['sections'][-1]
401+
if first_sec['from']['embedded_type'] != "poi" and last_sec['to']['embedded_type'] != "poi":
402+
break
403+
if first_sec['from']['embedded_type'] == "poi":
404+
update_for_poi(first_sec['from']['poi'])
405+
if last_sec['to']['embedded_type'] == "poi":
406+
update_for_poi(last_sec['to']['poi'])
407+
408+
return objects
409+
410+
return wrapper
411+
412+
357413
class rig_journey(object):
358414
"""
359415
decorator to rig journeys in order to put back the requested origin/destination in the journeys
@@ -693,6 +749,7 @@ def __init__(self):
693749
@add_debug_info()
694750
@add_fare_links()
695751
@add_journey_href()
752+
@handle_poi_disruptions()
696753
@rig_journey()
697754
@get_serializer(serpy=api.JourneysSerializer)
698755
@ManageError()

source/jormungandr/jormungandr/interfaces/v1/test/journey_tests.py

+64
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@
3333
from jormungandr.exceptions import RegionNotFound
3434
from jormungandr.interfaces.v1.journey_common import compute_regions, sort_regions
3535
from navitiacommon import models
36+
import pytz
37+
from flask import g
38+
import jormungandr.scenarios.tests.helpers_tests as helpers_tests
39+
from jormungandr.interfaces.v1.serializer import api
40+
from jormungandr import app
41+
from jormungandr.interfaces.v1.decorators import get_serializer
42+
from jormungandr.interfaces.v1.Journeys import handle_poi_disruptions, rig_journey
43+
from jormungandr.interfaces.v1.errors import ManageError
3644

3745

3846
class MockInstance:
@@ -210,3 +218,59 @@ def test_sorted_regions(self):
210218
assert regions[2].name == self.regions['netherlands'].name
211219
assert regions[3].name == self.regions['france'].name
212220
assert regions[4].name == self.regions['equador'].name
221+
222+
223+
@handle_poi_disruptions()
224+
@rig_journey()
225+
@get_serializer(serpy=api.JourneysSerializer)
226+
@ManageError()
227+
def get_response_with_poi_and_disruptions():
228+
return helpers_tests.get_pb_response_with_journeys_and_disruptions()
229+
230+
231+
def handle_poi_disruptions_test():
232+
"""
233+
Both departure and arrival points are POI
234+
Only one disruption exist in the response on poi_uri_a
235+
journey 1 : walking + PT + walking
236+
journey 2 : walking
237+
"""
238+
with app.app_context():
239+
with app.test_request_context():
240+
g.timezone = pytz.utc
241+
g.origin_detail = helpers_tests.get_json_entry_point(id='poi_uri_a', name='poi_name_a')
242+
g.destination_detail = helpers_tests.get_json_entry_point(id='poi_uri_b', name='poi_name_b')
243+
244+
# get response in json with two journeys and one disruption
245+
resp = get_response_with_poi_and_disruptions()
246+
assert len(resp[0].get("journeys", 0)) == 2
247+
248+
# Journey 1: a disruption exist for poi_uri_a: the poi in journey should have links and
249+
# impacted_object should have object poi
250+
origin = resp[0].get("journeys", 0)[0]['sections'][0]['from']['poi']
251+
assert origin['id'] == "poi_uri_a"
252+
assert len(origin['links']) == 1
253+
impacted_object = resp[0]['disruptions'][0]['impacted_objects'][0]['pt_object']['poi']
254+
assert impacted_object['id'] == "poi_uri_a"
255+
assert "links" not in impacted_object
256+
257+
# No disruption exist for poi_uri_b: the poi in journey doesn't have links and object poi is absent
258+
# in impacted_object
259+
destination = resp[0].get("journeys", 0)[0]['sections'][2]['to']['poi']
260+
assert destination['id'] == "poi_uri_b"
261+
assert len(destination["links"]) == 0
262+
263+
# Journey 2: a disruption exist for poi_uri_a: the poi in journey should have links and
264+
# impacted_object should have object poi
265+
origin = resp[0].get("journeys", 0)[1]['sections'][0]['from']['poi']
266+
assert origin['id'] == "poi_uri_a"
267+
assert len(origin['links']) == 1
268+
impacted_object = resp[0]['disruptions'][0]['impacted_objects'][0]['pt_object']['poi']
269+
assert impacted_object['id'] == "poi_uri_a"
270+
assert "links" not in impacted_object
271+
272+
# No disruption exist for poi_uri_b: the poi in journey doesn't have links and object poi is absent
273+
# in impacted_object
274+
destination = resp[0].get("journeys", 0)[1]['sections'][0]['to']['poi']
275+
assert destination['id'] == "poi_uri_b"
276+
assert len(destination["links"]) == 0

source/jormungandr/jormungandr/scenarios/new_default.py

+8-20
Original file line numberDiff line numberDiff line change
@@ -504,44 +504,32 @@ def update_total_co2_emission(pb_resp):
504504

505505
def update_disruptions_on_pois(instance, pb_resp):
506506
"""
507-
Maintain a set of uri from all journey.section.origin and journey.section.destination of type Poi,
507+
Maintain a set of uri from g.origin_detail and g.destination_detail of type Poi,
508508
call loki with api api_disruptions&pois[]...
509509
For each disruption on poi, add disruption id in the attribute links and add disruptions in the response
510510
"""
511511
if not pb_resp.journeys:
512512
return
513513
# Add uri of all the pois in a set
514514
poi_uris = set()
515-
poi_objets = []
516515
since_datetime = date_to_timestamp(datetime.utcnow())
517516
until_datetime = date_to_timestamp(datetime.utcnow())
518-
for j in pb_resp.journeys:
519-
for s in j.sections:
520-
if s.origin.embedded_type == type_pb2.POI:
521-
poi_uris.add(s.origin.uri)
522-
poi_objets.append(s.origin.poi)
523-
since_datetime = min(since_datetime, s.begin_date_time)
524517

525-
if s.destination.embedded_type == type_pb2.POI:
526-
poi_uris.add(s.destination.uri)
527-
poi_objets.append(s.destination.poi)
528-
until_datetime = max(until_datetime, s.end_date_time)
518+
# Here we manage origin and destination of type POI
519+
if g.origin_detail and g.origin_detail.get('embedded_type') == "poi":
520+
poi_uris.add(g.origin_detail.get('id'))
521+
522+
if g.destination_detail and g.destination_detail.get('embedded_type') == "poi":
523+
poi_uris.add(g.destination_detail.get('id'))
529524

530525
if since_datetime >= until_datetime:
531526
since_datetime = until_datetime - 1
527+
532528
# Get disruptions for poi_uris calling loki with api poi_disruptions and poi_uris in param
533529
poi_disruptions = get_disruptions_on_poi(instance, poi_uris, since_datetime, until_datetime)
534530
if poi_disruptions is None:
535531
return
536532

537-
# For each poi in pt_objects:
538-
# add impact_uris from resp_poi and
539-
# copy object poi in impact.impacted_objects
540-
for pt_object in poi_objets:
541-
impact_uris = get_impact_uris_for_poi(poi_disruptions, pt_object)
542-
for impact_uri in impact_uris:
543-
pt_object.impact_uris.append(impact_uri)
544-
545533
# Add all impacts from resp_poi to the response
546534
add_disruptions(pb_resp, poi_disruptions)
547535

source/jormungandr/jormungandr/scenarios/tests/helpers_tests.py

+76-3
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ def fill_best_boarding_position_test():
520520
assert response_pb2.BoardingPosition.BACK not in journey.sections[0].best_boarding_positions
521521

522522

523-
def get_response_with_a_disruption_on_poi():
523+
def get_response_with_a_disruption_on_poi(uri="poi_uri", name="poi_name_from_loki"):
524524
start_period = "20240712T165200"
525525
end_period = "20240812T165200"
526526
response = response_pb2.Response()
@@ -531,8 +531,8 @@ def get_response_with_a_disruption_on_poi():
531531

532532
# poi = make_pt_object(type_pb2.POI, lon=1, lat=2, uri='poi:test_uri')
533533
# impacted_object.pt_object.CopyFrom(poi)
534-
impacted_object.pt_object.name = "poi_name_from_loki"
535-
impacted_object.pt_object.uri = "poi_uri"
534+
impacted_object.pt_object.name = name
535+
impacted_object.pt_object.uri = uri
536536
impacted_object.pt_object.embedded_type = type_pb2.POI
537537
impact.updated_at = utils.str_to_time_stamp(u'20240712T205200')
538538
application_period = impact.application_periods.add()
@@ -623,6 +623,79 @@ def get_journey_with_pois():
623623
return response
624624

625625

626+
def get_pb_response_with_journeys_and_disruptions():
627+
response = response_pb2.Response()
628+
629+
# Add a journey : walking address to stop_point + PT stop_point to stop_point + walking toward address
630+
journey = response.journeys.add()
631+
section = journey.sections.add()
632+
section.type = response_pb2.STREET_NETWORK
633+
section.street_network.mode = response_pb2.Walking
634+
section.origin.uri = 'address_a'
635+
section.origin.embedded_type = type_pb2.ADDRESS
636+
section.destination.uri = 'stop_point_a'
637+
section.destination.embedded_type = type_pb2.STOP_POINT
638+
section.destination.stop_point.uri = 'stop_point_a'
639+
section.destination.stop_point.name = 'stop_point_name_a'
640+
section.destination.stop_point.coord.lon = 1.0
641+
section.destination.stop_point.coord.lat = 2.0
642+
643+
section = journey.sections.add()
644+
section.type = response_pb2.PUBLIC_TRANSPORT
645+
section.origin.uri = 'stop_point_a'
646+
section.origin.embedded_type = type_pb2.STOP_POINT
647+
section.origin.stop_point.uri = 'stop_point_a'
648+
section.origin.stop_point.name = 'stop_point_name_a'
649+
section.origin.stop_point.coord.lon = 1.0
650+
section.origin.stop_point.coord.lat = 2.0
651+
section.destination.uri = 'stop_point_b'
652+
section.destination.embedded_type = type_pb2.STOP_POINT
653+
section.destination.stop_point.uri = 'stop_point_b'
654+
section.destination.stop_point.name = 'stop_point_name_b'
655+
section.destination.stop_point.coord.lon = 3.0
656+
section.destination.stop_point.coord.lat = 4.0
657+
658+
section = journey.sections.add()
659+
section.type = response_pb2.STREET_NETWORK
660+
section.street_network.mode = response_pb2.Walking
661+
section.origin.uri = 'stop_point_b'
662+
section.origin.embedded_type = type_pb2.STOP_POINT
663+
section.origin.stop_point.uri = 'stop_point_b'
664+
section.origin.stop_point.name = 'stop_point_name_b'
665+
section.origin.stop_point.coord.lon = 3.0
666+
section.origin.stop_point.coord.lat = 4.0
667+
section.destination.uri = 'address_b'
668+
section.destination.embedded_type = type_pb2.ADDRESS
669+
670+
# Add a journey : walking address to address
671+
journey = response.journeys.add()
672+
section = journey.sections.add()
673+
section.type = response_pb2.STREET_NETWORK
674+
section.street_network.mode = response_pb2.Walking
675+
section.origin.uri = 'address_a'
676+
section.origin.embedded_type = type_pb2.ADDRESS
677+
section.destination.uri = 'address_b'
678+
section.destination.embedded_type = type_pb2.ADDRESS
679+
680+
# Add disruption on poi 'poi_uri_a':
681+
pb_disruptions = get_response_with_a_disruption_on_poi(uri="poi_uri_a", name="poi_name_a")
682+
response.impacts.extend(pb_disruptions.impacts)
683+
response.status_code = 200
684+
return response
685+
686+
687+
def get_json_entry_point(id="poi_uri", name="poi_name_from_kraken"):
688+
entry_point = {}
689+
entry_point['id'] = id
690+
entry_point['name'] = name
691+
entry_point['embedded_type'] = "poi"
692+
object = {}
693+
object['id'] = id
694+
object['name'] = name
695+
entry_point['poi'] = object
696+
return entry_point
697+
698+
626699
def verify_poi_in_impacted_objects(object, poi_empty=True):
627700
assert object.name == "poi_name_from_loki"
628701
assert object.uri == "poi_uri"

source/jormungandr/jormungandr/scenarios/tests/new_default_tests.py

+33-28
Original file line numberDiff line numberDiff line change
@@ -805,36 +805,41 @@ def __init__(self, name="fake_instance", olympics_forbidden_uris=None):
805805

806806

807807
def journey_with_disruptions_on_poi_test(mocker):
808-
instance = lambda: None
809-
# As in navitia, object poi in the response of places_nearby doesn't have any impact
810-
response_journey_with_pois = helpers_tests.get_journey_with_pois()
811-
assert len(response_journey_with_pois.impacts) == 0
812-
assert len(response_journey_with_pois.journeys) == 1
813-
journey = response_journey_with_pois.journeys[0]
814-
assert len(journey.sections) == 3
815-
816-
# Prepare disruptions on poi as response of end point poi_disruptions of loki
817-
# pt_object poi as impacted object is absent in the response of poi_disruptions
818-
disruptions_with_poi = helpers_tests.get_response_with_a_disruption_on_poi()
819-
assert len(disruptions_with_poi.impacts) == 1
820-
assert disruptions_with_poi.impacts[0].uri == "test_impact_uri"
821-
assert len(disruptions_with_poi.impacts[0].impacted_objects) == 1
822-
object = disruptions_with_poi.impacts[0].impacted_objects[0].pt_object
823-
helpers_tests.verify_poi_in_impacted_objects(object=object, poi_empty=True)
824-
825-
mock = mocker.patch(
826-
'jormungandr.scenarios.new_default.get_disruptions_on_poi', return_value=disruptions_with_poi
827-
)
828-
update_disruptions_on_pois(instance, response_journey_with_pois)
808+
with app.app_context():
809+
instance = lambda: None
810+
g.origin_detail = helpers_tests.get_json_entry_point(id='poi_uri', name='poi_name_from_kraken')
811+
g.destination_detail = helpers_tests.get_json_entry_point(id='poi_b', name='poi_n_name')
812+
# As in navitia, object poi in the response of places_nearby doesn't have any impact
813+
response_journey_with_pois = helpers_tests.get_journey_with_pois()
814+
assert len(response_journey_with_pois.impacts) == 0
815+
assert len(response_journey_with_pois.journeys) == 1
816+
journey = response_journey_with_pois.journeys[0]
817+
assert len(journey.sections) == 3
818+
819+
# Prepare disruptions on poi as response of end point poi_disruptions of loki
820+
# pt_object poi as impacted object is absent in the response of poi_disruptions
821+
disruptions_with_poi = helpers_tests.get_response_with_a_disruption_on_poi()
822+
assert len(disruptions_with_poi.impacts) == 1
823+
assert disruptions_with_poi.impacts[0].uri == "test_impact_uri"
824+
assert len(disruptions_with_poi.impacts[0].impacted_objects) == 1
825+
object = disruptions_with_poi.impacts[0].impacted_objects[0].pt_object
826+
helpers_tests.verify_poi_in_impacted_objects(object=object, poi_empty=True)
827+
828+
mock = mocker.patch(
829+
'jormungandr.scenarios.new_default.get_disruptions_on_poi', return_value=disruptions_with_poi
830+
)
831+
update_disruptions_on_pois(instance, response_journey_with_pois)
832+
833+
assert len(response_journey_with_pois.impacts) == 1
834+
impact = response_journey_with_pois.impacts[0]
835+
assert len(impact.impacted_objects) == 1
836+
object = impact.impacted_objects[0].pt_object
829837

830-
assert len(response_journey_with_pois.impacts) == 1
831-
impact = response_journey_with_pois.impacts[0]
832-
assert len(impact.impacted_objects) == 1
833-
object = impact.impacted_objects[0].pt_object
834-
helpers_tests.verify_poi_in_impacted_objects(object=object, poi_empty=False)
838+
# In this state we haven't yet managed the final response so poi object is empty
839+
helpers_tests.verify_poi_in_impacted_objects(object=object, poi_empty=True)
835840

836-
mock.assert_called_once()
837-
return
841+
mock.assert_called_once()
842+
return
838843

839844

840845
def journey_with_booking_rule_test():

0 commit comments

Comments
 (0)