From 5bea4ff880f30faee9dd20192915d1f4202881ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Wed, 28 Jan 2026 22:56:48 +0100 Subject: [PATCH 01/16] adding dict to work without the ojp-to-ojp step --- test_configuration.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test_configuration.py b/test_configuration.py index 6566259..7d6bd3f 100644 --- a/test_configuration.py +++ b/test_configuration.py @@ -3,6 +3,13 @@ READFILE.append("input/input_Bern_Belp.xml") + +''' +---------------------------------------------------------------- +Reservoir for tests + +# Working OJP 1.0 examples + READFILE.append("input/input_oev_shart_plus_long.xml") READFILE.append("input/input_Zuerich_Chur.xml") READFILE.append("input/input_Visp_SaaS_Fee_problem_1_preis.xml") #1. class price problematic From 7ecfb6b982e4095b832ae9ff615f74fc3b5fd248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Mon, 16 Feb 2026 17:18:10 +0100 Subject: [PATCH 02/16] testing and making r_r fit again. --- test_configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_configuration.py b/test_configuration.py index 7d6bd3f..5c9654b 100644 --- a/test_configuration.py +++ b/test_configuration.py @@ -3,13 +3,13 @@ READFILE.append("input/input_Bern_Belp.xml") - ''' ---------------------------------------------------------------- Reservoir for tests # Working OJP 1.0 examples + READFILE.append("input/input_oev_shart_plus_long.xml") READFILE.append("input/input_Zuerich_Chur.xml") READFILE.append("input/input_Visp_SaaS_Fee_problem_1_preis.xml") #1. class price problematic From ef3d46e3fecd61f620ae4a35bd4697633719a80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Tue, 17 Feb 2026 14:18:08 +0100 Subject: [PATCH 03/16] documentation improved --- support.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/support.py b/support.py index 543bb25..4b2300f 100644 --- a/support.py +++ b/support.py @@ -83,6 +83,7 @@ def sloid2didok(sloid:str)->int: #remove the right part of sloid, if it exist if ':' in sloid: tmp = sloid[:sloid.find(':')] + # if bigger than 100000 -> no add. This is used for the 11-14 prefixes that are used for sloid that are used for local public transport # outside Switzerland if int(tmp)>100000: @@ -92,6 +93,7 @@ def sloid2didok(sloid:str)->int: return tmp + # raising an error and sending it back. Does not add values from err_str class OJPError(Exception) : From caf7ab5a31aa933ed951ad66c2b3d528ccca93b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Tue, 17 Feb 2026 20:13:07 +0100 Subject: [PATCH 04/16] fixing errors in sloid2didok --- support.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/support.py b/support.py index 4b2300f..9a5f42e 100644 --- a/support.py +++ b/support.py @@ -92,8 +92,6 @@ def sloid2didok(sloid:str)->int: tmp=my_dict.get(str(tmp),str(tmp)) # replaces if it is in the table or gets the value back return tmp - - # raising an error and sending it back. Does not add values from err_str class OJPError(Exception) : From 98aeb2a94517ad87437f4adfeea227c9ee6fa86b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Thu, 26 Feb 2026 21:40:18 +0100 Subject: [PATCH 05/16] start_improving_subscriptions_and_better_parameters --- input/input_Monatsabo_test.xml | 42 +++++++++++++++ input/input_test_dornbir.xml | 38 +++++++++++++ map_ojp_to_nova.py | 98 +++++++++++++++++++++++++++------- test_configuration.py | 51 +++++++----------- 4 files changed, 178 insertions(+), 51 deletions(-) create mode 100644 input/input_Monatsabo_test.xml create mode 100644 input/input_test_dornbir.xml diff --git a/input/input_Monatsabo_test.xml b/input/input_Monatsabo_test.xml new file mode 100644 index 0000000..05d0be1 --- /dev/null +++ b/input/input_Monatsabo_test.xml @@ -0,0 +1,42 @@ + + + + + + de + + 2026-02-17T22:00:01.474Z + OJP_DemoApp_Beta_OJP2.0 + + 2026-02-17T22:00:01.474Z + + + + 7.33995 + 46.81392 + + + origin + + + + + + 8571359 + + destination + + + + + 5 + explanatory + true + false + false + true + + + + + diff --git a/input/input_test_dornbir.xml b/input/input_test_dornbir.xml new file mode 100644 index 0000000..1a8f196 --- /dev/null +++ b/input/input_test_dornbir.xml @@ -0,0 +1,38 @@ + + + + + + de + + 2026-02-25T13:01:28Z + BLS_IOS_SDK_1.3.3 + + 2026-02-25T13:01:28Z + + + 8102329 + + Dornbirn + + + + + + 8506314 + + St. Margrethen SG + + + + + 6 + false + true + true + explanatory + + + + + \ No newline at end of file diff --git a/map_ojp_to_nova.py b/map_ojp_to_nova.py index 862feb0..53e7419 100644 --- a/map_ojp_to_nova.py +++ b/map_ojp_to_nova.py @@ -85,17 +85,26 @@ def map_fare_request_to_nova_request(ojp: Ojp, age: int=30) -> Optional[PreisAus and ojp.ojprequest.service_request.ojpfare_request[0].trip_fare_request.trip and len(ojp.ojprequest.service_request.ojpfare_request[0].trip_fare_request.trip.trip_leg) > 0): return None - + #handling of abos (monthly) otherwise the product_taxonomie is set to the standard + produkt_taxonomie = "SBB Preisauskunft" try: + if "NOVA-Subscription" in ojp.ojprequest.service_request.ojpfare_request[0].params.fare_authority_filter[0] : + produkt_taxonomie="SBB Abonnemente" + except: + pass + #handling of traveller + travellers = [] + try: + #TODO im MOment behandeln wir nur den ersten Request!!! if ojp.ojprequest.service_request.ojpfare_request[0].params.traveller is None: - travellers = [] - travellers.append(FarePassengerStructure(age=25, entitlement_product=["HTA"])) + travellers.append(FarePassengerStructure(age=25, entitlement_product=["HTA"])) #fix for bad data in traveller else: - if not(ojp.ojprequest.service_request.ojpfare_request[0].params.traveller[0].age is int): - age = ojp.ojprequest.service_request.ojpfare_request[0].params.traveller[0].age - else: - age=25 - travellers = ojp.ojprequest.service_request.ojpfare_request[0].params.traveller + # we go through all travellers + for traveller in ojp.ojprequest.service_request.ojpfare_request[0].params.traveller: + #TODO we set age, but this might be wrong + if not(traveller.age is int): + traveller.age=25 + travellers.append(traveller) except: pass @@ -136,14 +145,15 @@ def map_fare_request_to_nova_request(ojp: Ojp, age: int=30) -> Optional[PreisAus if no_pricable_leg is False: verbindungen += [VerbindungPreisAuskunft(externe_verbindungs_referenz_id=externeVerbindungsReferenzId + "_" + leg_start + "_" + leg_end, segment_hin_fahrt=segments)] + + # we process now the travellers into the nova strcuture reisende =[] for traveler in travellers: - # we only do one for the time being TODO r_alter=25 if traveler.age == None: - t_alter=25 + r_alter=25 else: - t_alter=traveler.age + r_alter=traveler.age r_typ = ReisendenTypCode.PERSON if traveler.passenger_category is None: r_typ =ReisendenTypCode.PERSON @@ -151,22 +161,72 @@ def map_fare_request_to_nova_request(ojp: Ojp, age: int=30) -> Optional[PreisAus r_typ = ReisendenTypCode.HUND elif traveler.passenger_category.value == "Bicycle": r_typ = ReisendenTypCode.VELO + elif traveler.passenger_category.value == "Adult": + r_typ = ReisendenTypCode.PERSON + elif traveler.passenger_category.value == "Child": + r_typ = ReisendenTypCode.PERSON + elif traveler.passenger_category.value == "Senior": + r_typ = ReisendenTypCode.PERSON + elif traveler.passenger_category.value == "Youth": + r_typ = ReisendenTypCode.PERSON + elif traveler.passenger_category.value == "Disabled": + r_typ = ReisendenTypCode.PERSON + elif traveler.passenger_category.value == "Motorcycle": + r_typ = ReisendenTypCode.PERSON + #TODO Raise error + elif traveler.passenger_category.value == "Car": + r_typ = ReisendenTypCode.PERSON + #TODO Raise error + elif traveler.passenger_category.value == "Truck": + r_typ = ReisendenTypCode.PERSON + #TODO Raise error + elif traveler.passenger_category.value == "Group": + r_typ = ReisendenTypCode.PERSON + #TODO Raise error else: r_typ = ReisendenTypCode.PERSON + #TODO Raise error digits = "0123456789" r_referenz= ''.join(random.choice(digits) for _ in range(6)) # we only process HTA for the time being! - reisender = ReisendenInfoPreisAuskunft(alter=r_alter, - externe_reisenden_referenz_id=r_referenz, - reisenden_typ=r_typ, - ermaessigungs_karte_code=[]) + # reisender = ReisendenInfoPreisAuskunft(alter=r_alter, + # externe_reisenden_referenz_id=r_referenz, + # reisenden_typ=r_typ, + # ermaessigungs_karte_code=[]) + entitlements =[] for entitlement_product in traveler.entitlement_product: if "HTA" in entitlement_product: - reisender = ReisendenInfoPreisAuskunft(alter=r_alter, - externe_reisenden_referenz_id=r_referenz, - reisenden_typ=r_typ, - ermaessigungs_karte_code=["HTA"]) + entitlements.append("HTA") + elif "JUNIORKARTE" in entitlement_product: + entitlements.append("JUNIORKARTE") + elif "EURAIL_CH_1KL" in entitlement_product: + entitlements.append("EURAIL_CH_1KL") + elif "EURAIL_CH_2KL" in entitlement_product: + entitlements.append("EURAIL_CH_2KL") + elif "INTERRAIL_CH_1KL" in entitlement_product: + entitlements.append("INTERRAIL_CH_1KL") + elif "INTERRAIL_CH_2KL" in entitlement_product: + entitlements.append("INTERRAIL_CH_2KL") + elif "GA_2KL" in entitlement_product: + entitlements.append("GA_2KL") + elif "GA_1KL" in entitlement_product: + entitlements.append("GA_1KL") + elif "ST_PASS_2KL" in entitlement_product: + entitlements.append("ST_PASS_2KL") + elif "ST_PASS_1KL" in entitlement_product: + entitlements.append("ST_PASS_1KL") + elif "KEINE_ERMAESSIGUNGSKARTE": + #Keine Ermässigungskarte + pass + else: + #TODO error ungültige Karte + pass + + reisender = ReisendenInfoPreisAuskunft(alter=r_alter, + externe_reisenden_referenz_id=r_referenz, + reisenden_typ=r_typ, + ermaessigungs_karte_code=entitlements) reisende.append(reisender) return PreisAuskunftServicePortTypeSoapv14ErstellePreisAuskunftInput( body=PreisAuskunftServicePortTypeSoapv14ErstellePreisAuskunftInput.Body( diff --git a/test_configuration.py b/test_configuration.py index 5c9654b..ec441cf 100644 --- a/test_configuration.py +++ b/test_configuration.py @@ -3,47 +3,34 @@ READFILE.append("input/input_Bern_Belp.xml") -''' ----------------------------------------------------------------- -Reservoir for tests -# Working OJP 1.0 examples +#READFILE.append("input/input_Monatsabo_test.xml") -READFILE.append("input/input_oev_shart_plus_long.xml") -READFILE.append("input/input_Zuerich_Chur.xml") -READFILE.append("input/input_Visp_SaaS_Fee_problem_1_preis.xml") #1. class price problematic -READFILE.append("input/input_strange_price.xml") -READFILE.append("input/input_Zuerich_Bern.xml") -READFILE.append("input/input_Basel_Sargans.xml") -READFILE.append("input/input_Bern_Interlaken_Ost.xml") -READFILE.append("input/input_Bern_Interlaken_Gymnasium.xml") -READFILE.append("input/input_Bern_Guisanplatz_Interlaken_Gymnasium.xml") -READFILE.append("input/input_local.xml") -READFILE.append("input/input_oev_shart_plus_long.xml") -READFILE.append("input/input_bus_postauto.xml") -READFILE.append("input/input_sharing_intercity.xml") -READFILE.append("input/input_problematic_case_vasile.xml") -READFILE.append("input/input_Europaplatz.xml") -READFILE.append("input/input_Bodensee_1.xml") -READFILE.append("input/input_test_europaplatz.xml") - - -# Working OJP 2.0 example -#READFILE.append("input/input_ojp_1_test.xml") -READFILE.append("input/input_ojp_2_test.xml") -READFILE.append("input/input_problematic_Europaplatz_ojp1.xml") -READFILE.append("input/input_problematic_Europaplatz_ojp2.xml") -READFILE.append("input/input_Bodensee_2.xml") -READFILE.append("input/input_problem_footpath.xml") -READFILE.append("input/input_problematic_Europaplatz_4.xml") - ''' +[ + { + "file": "input/input_Bern_Belp.xml", + "travellers": [ + { + "age":2, + "entitlements":"HTA GA_1KL", + typ: "PERSON", + "tkid": "1231231" + } + ], + "subscriptions": true, + "relationship": "KEINE_REISENDENBEZIEHUNG" + + } +] + ---------------------------------------------------------------- Reservoir for tests # Working OJP 1.0 examples READFILE.append("input/input_Bern_Belp.xml") +READFILE.append("input/input_test_dornbir.xml") READFILE.append("input/input_oev_shart_plus_long.xml") READFILE.append("input/input_Zuerich_Chur.xml") From 2f42315d06cd894420390dda7b9a7ba513d1db82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Thu, 26 Feb 2026 22:00:17 +0100 Subject: [PATCH 06/16] Update map_ojp_to_nova.py --- map_ojp_to_nova.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/map_ojp_to_nova.py b/map_ojp_to_nova.py index 53e7419..8993c2d 100644 --- a/map_ojp_to_nova.py +++ b/map_ojp_to_nova.py @@ -95,13 +95,14 @@ def map_fare_request_to_nova_request(ojp: Ojp, age: int=30) -> Optional[PreisAus #handling of traveller travellers = [] try: - #TODO im MOment behandeln wir nur den ersten Request!!! + if ojp.ojprequest.service_request.ojpfare_request[0].params.traveller is None: + #if we have no traveller, we invent one (with HTA) TODO perhaps we should instead throw an error travellers.append(FarePassengerStructure(age=25, entitlement_product=["HTA"])) #fix for bad data in traveller else: # we go through all travellers for traveller in ojp.ojprequest.service_request.ojpfare_request[0].params.traveller: - #TODO we set age, but this might be wrong + #TODO we set age, but this might be wrongs if not(traveller.age is int): traveller.age=25 travellers.append(traveller) @@ -240,7 +241,7 @@ def map_fare_request_to_nova_request(ojp: Ojp, age: int=30) -> Optional[PreisAus correlation_id=str(uuid.uuid1()), geschaefts_prozess_id="1781786f-57ba-4e9a-bc29-287e2aa97f9a"), angebots_filter=[TaxonomieFilter( - produkt_taxonomie="SBB Preisauskunft", + produkt_taxonomie=produkt_taxonomie, taxonomie_klasse_pfad=[TaxonomieKlassePfad(EmptyType())])], reisender=reisende, verbindung=verbindungen From b2877797a23ceab9334ec1343a3b0069b1acd3d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Thu, 26 Feb 2026 22:02:01 +0100 Subject: [PATCH 07/16] Update map_ojp_to_nova.py --- map_ojp_to_nova.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/map_ojp_to_nova.py b/map_ojp_to_nova.py index 8993c2d..2027d83 100644 --- a/map_ojp_to_nova.py +++ b/map_ojp_to_nova.py @@ -189,11 +189,6 @@ def map_fare_request_to_nova_request(ojp: Ojp, age: int=30) -> Optional[PreisAus #TODO Raise error digits = "0123456789" r_referenz= ''.join(random.choice(digits) for _ in range(6)) - # we only process HTA for the time being! - # reisender = ReisendenInfoPreisAuskunft(alter=r_alter, - # externe_reisenden_referenz_id=r_referenz, - # reisenden_typ=r_typ, - # ermaessigungs_karte_code=[]) entitlements =[] for entitlement_product in traveler.entitlement_product: From ecc1339a95b4cc57e8df23e5b1eb30e0764077c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Thu, 26 Feb 2026 23:30:34 +0100 Subject: [PATCH 08/16] starting switch to new testing and fixing stuff on the go. --- map_ojp2_to_ojp2.py | 39 ++++--- map_ojp_to_ojp.py | 31 ++++-- test_configuration.json | 22 ++++ test_configuration.py | 15 ++- test_network_flow.py | 236 +++++++++++++++++++++++++++------------- 5 files changed, 238 insertions(+), 105 deletions(-) create mode 100644 test_configuration.json diff --git a/map_ojp2_to_ojp2.py b/map_ojp2_to_ojp2.py index 471ef4b..554b609 100644 --- a/map_ojp2_to_ojp2.py +++ b/map_ojp2_to_ojp2.py @@ -27,22 +27,31 @@ def parse_ojp2(body: str) -> Ojp: parser = XmlParser(config) return parser.from_string(body, Ojp) -def map_to_individual_ojpfarerequest(trip: TripStructure, now: XmlDateTime) -> OjpfareRequest: - travellers=[] - if USE_HTA: - entitlementproduct=EntitlementProductStructure(fare_authority_ref=FareAuthorityRef("NOVA"),entitlement_product_name="HTA", entitlement_product_ref="HTA") #TODO correct fare_authority_ref - - entitlementproducts=EntitlementProductListStructure(entitlement_product=[entitlementproduct]) - travellers.append(FarePassengerStructure(age=25, entitlement_products = entitlementproducts)) +def map_to_individual_ojpfarerequest(trip: TripStructure, now: XmlDateTime, ojp_fare_params:FareParamStructure) -> OjpfareRequest: + if ojp_fare_params is None: + travellers=[] + if USE_HTA: + entitlementproduct = EntitlementProductStructure(fare_authority_ref=FareAuthorityRef("NOVA"), + entitlement_product_name="HTA", + entitlement_product_ref="HTA") # TODO correct fare_authority_ref + + entitlementproducts = EntitlementProductListStructure(entitlement_product=[entitlementproduct]) + travellers.append(FarePassengerStructure(age=25, entitlement_products=entitlementproducts)) + else: + travellers.append( + FarePassengerStructure(passenger_category=PassengerCategoryEnumeration.ADULT, entitlement_products=[])) + + ojp_fare_params=FareParamStructure(fare_authority_filter=[FareAuthorityRef("ch:1:NOVA")], + passenger_category=[PassengerCategoryEnumeration.ADULT], + fare_class=FareClassEnumeration.SECOND_CLASS, + traveller=travellers) else: - travellers.append(FarePassengerStructure(passenger_category=PassengerCategoryEnumeration.ADULT,entitlement_products = [])) - + # the FareParamStructure is directly added + # TODO: Perhaps we should check it in more details + pass return OjpfareRequest( request_timestamp=RequestTimestamp(now), - params=FareParamStructure(fare_authority_filter=[FareAuthorityRef("ch:1:NOVA")], - passenger_category=[PassengerCategoryEnumeration.ADULT], - fare_class=FareClassEnumeration.SECOND_CLASS, - traveller=travellers), + params=ojp_fare_params, trip_fare_request=TripFareRequestStructure(trip=trip)) # def map_to_individual_ojptriprefinerequest(trip_result: TripResultStructure, now: XmlDateTime) -> OjptripRefineRequest: @@ -107,7 +116,7 @@ def preprocess_stops_to_commercial_stops(delivery: OjptripDeliveryStructure) -> return delivery -def map_ojp2_trip_result_to_ojp2_fare_request(ojp: Ojp) -> Optional[Ojp]: +def map_ojp2_trip_result_to_ojp2_fare_request(ojp: Ojp, ojp_fare_params: FareParamStructure) -> Optional[Ojp]: if len(ojp.ojpresponse.service_delivery.ojptrip_delivery) != 1: return None @@ -120,7 +129,7 @@ def map_ojp2_trip_result_to_ojp2_fare_request(ojp: Ojp) -> Optional[Ojp]: # # preprocess trip result to translate the quays to the commercial stop ojptrip_delivery=preprocess_stops_to_commercial_stops(ojptrip_delivery) for trip_result in ojptrip_delivery.trip_result: - farerequest += [map_to_individual_ojpfarerequest(trip_result.trip, now)] + farerequest += [map_to_individual_ojpfarerequest(trip_result.trip, now,ojp_fare_params)] return Ojp(ojprequest= Ojprequest(service_request= diff --git a/map_ojp_to_ojp.py b/map_ojp_to_ojp.py index 7278a5d..7d8e564 100644 --- a/map_ojp_to_ojp.py +++ b/map_ojp_to_ojp.py @@ -26,19 +26,26 @@ def parse_ojp(body: str) -> Ojp: parser = XmlParser(config) return parser.from_string(body, Ojp) -def map_to_individual_ojpfarerequest(trip: TripStructure, now: XmlDateTime) -> OjpfareRequest: - travellers=[] - if USE_HTA: - travellers.append(FarePassengerStructure(age=25,entitlement_product = ["HTA"])) - else: - travellers.append(FarePassengerStructure(passenger_category=PassengerCategoryEnumeration.ADULT,entitlement_product = [])) +def map_to_individual_ojpfarerequest(trip: TripStructure, now: XmlDateTime, fare_params:FareParamStructure) -> OjpfareRequest: + if fare_params is None: + travellers = [] + if USE_HTA: + travellers.append(FarePassengerStructure(age=25, entitlement_product=["HTA"])) + else: + travellers.append( + FarePassengerStructure(passenger_category=PassengerCategoryEnumeration.ADULT, entitlement_product=[])) + + return OjpfareRequest( + request_timestamp=now, + params=FareParamStructure(fare_authority_filter=["ch:1:NOVA"], + passenger_category=[PassengerCategoryEnumeration.ADULT], + travel_class=TypeOfFareClassEnumeration.SECOND, + traveller=travellers), + trip_fare_request=TripFareRequestStructure(trip=trip)) return OjpfareRequest( request_timestamp=now, - params=FareParamStructure(fare_authority_filter=["ch:1:NOVA"], - passenger_category=[PassengerCategoryEnumeration.ADULT], - travel_class=TypeOfFareClassEnumeration.SECOND, - traveller=travellers), + params=fare_params, trip_fare_request=TripFareRequestStructure(trip=trip)) # def map_to_individual_ojptriprefinerequest(trip_result: TripResultStructure, now: XmlDateTime) -> OjptripRefineRequest: @@ -100,7 +107,7 @@ def preprocess_stops_to_commercial_stops(delivery: OjptripDeliveryStructure) -> leg_intermediate.stop_point_ref = parent.get(leg_intermediate.stop_point_ref,leg_intermediate.stop_point_ref) return delivery -def map_ojp_trip_result_to_ojp_fare_request(ojp: Ojp) -> Optional[Ojp]: +def map_ojp_trip_result_to_ojp_fare_request(ojp: Ojp,fare_params:FareParamStructure) -> Optional[Ojp]: if ojp.ojpresponse is None or ojp.ojpresponse.service_delivery is None or ojp.ojpresponse.service_delivery.ojptrip_delivery is None or len(ojp.ojpresponse.service_delivery.ojptrip_delivery) != 1: return None @@ -116,7 +123,7 @@ def map_ojp_trip_result_to_ojp_fare_request(ojp: Ojp) -> Optional[Ojp]: ojptrip_delivery=preprocess_stops_to_commercial_stops(ojptrip_delivery) for trip_result in ojptrip_delivery.trip_result: if trip_result.trip: - farerequest += [map_to_individual_ojpfarerequest(trip_result.trip, now)] + farerequest += [map_to_individual_ojpfarerequest(trip_result.trip, now,fare_params)] return Ojp(ojprequest= Ojprequest(service_request= diff --git a/test_configuration.json b/test_configuration.json new file mode 100644 index 0000000..c16f882 --- /dev/null +++ b/test_configuration.json @@ -0,0 +1,22 @@ +[ + { + "file": "input/input_problematic_Europaplatz_ojp2.xml", + "travellers": [ + { + "age": 14, + "entitlements": "HTA GA_1KL", + "typ": "PERSON", + "tkid": "1231231", + "birthday": "1999-10-01" + }, + { + "entitlements": "", + "typ": "DOG", + "tkid": "123131231" + } + ], + "subscriptions": true, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + } +] \ No newline at end of file diff --git a/test_configuration.py b/test_configuration.py index ec441cf..a28b4ad 100644 --- a/test_configuration.py +++ b/test_configuration.py @@ -15,12 +15,19 @@ { "age":2, "entitlements":"HTA GA_1KL", - typ: "PERSON", - "tkid": "1231231" - } + "typ": "PERSON", + "tkid": "1231231", + "birthday": "1999-10-01" + }, + { + "entitlements":"", + "typ": "DOG", + "tkid": "123131231", + } ], "subscriptions": true, - "relationship": "KEINE_REISENDENBEZIEHUNG" + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true } ] diff --git a/test_network_flow.py b/test_network_flow.py index 01bbccd..6366d8d 100644 --- a/test_network_flow.py +++ b/test_network_flow.py @@ -27,8 +27,11 @@ from nova import PreisAuskunftServicePortTypeSoapv14ErstellePreisAuskunft, \ PreisAuskunftServicePortTypeSoapv14ErstellePreisAuskunftOutput -from ojp2 import Ojp as Ojp2 -from ojp import Ojp, OjpfareDelivery +from ojp2 import Ojp as Ojp2, FareParamStructure as FareParamStructure2, \ + FarePassengerStructure as FarePassengerStructure2, FareAuthorityRefStructure as FareAuthorityRefStructure2, \ + PassengerCategoryEnumeration, FareClassEnumeration, EntitlementProductStructure, EntitlementProductListStructure +from ojp import Ojp, OjpfareDelivery, FareParamStructure, FarePassengerStructure, FareAuthorityRef, \ + TypeOfFareClassEnumeration, PassengerCategoryEnumeration from xslt_transform import transform_xml, is_version_2_0 import xml_logger import logging @@ -131,88 +134,173 @@ def check_configuration() ->None: logger.error("Nova client secret not set in the configuration") exit(1) + +def split_entitlements(value: Any) -> List[str]: + """ + Convert entitlements field into a list of strings. + - If value is None or empty string -> return empty list. + - If value is already a list -> return a shallow copy. + - If value is a string -> split on whitespace. + - Otherwise -> convert to string and split. + """ + if value is None: + return [] + if isinstance(value, list): + return value.copy() + if isinstance(value, str): + # split on any whitespace and ignore extra spaces + parts = value.split() + return parts + # Fallback: convert to string + return str(value).split() + +def build_ojp2_fare_params(travellers, subscriptions, relationship) -> FareParamStructure2: + + #travellers can't be empty. we already checked + ojptravellers = [] + for t_idx, traveller in enumerate(travellers): + if not isinstance(traveller, dict): + continue + typ = traveller.get("typ") + tkid = traveller.get("tkid") # we cannot process this for the time being + raw_ent = traveller.get("entitlements") + ent_list = split_entitlements(raw_ent) + entitlements = [] + for ent in ent_list: + entitlements.append(EntitlementProductStructure(fare_authority_ref="NOVA",entitlement_product_ref=ent,entitlement_product_name=ent)) + entitlement_list=EntitlementProductListStructure(entitlement_product=entitlements) + passenger_category = traveller.get("passenger_category") + birthday = traveller.get("birthday") #we cannot process this for the time being + age = traveller.get("age") + + ojptraveller = FarePassengerStructure2(age=age,passenger_category=passenger_category,entitlement_products=entitlement_list) + ojptravellers.append(ojptraveller) + filters =[] + if subscriptions: + # we use subscriptions instead of regular tickets + filters.append(FareAuthorityRefStructure2(value="NOVA-Subscription")) + else: + filters.append(FareAuthorityRefStructure2(value="NOVA")) + if relationship: + #TODO we will have to process relationships, need to put this in extensions + pass + + + + return FareParamStructure2(fare_authority_filter=filters,traveller=ojptravellers,passenger_category=[PassengerCategoryEnumeration.ADULT],fare_class =FareClassEnumeration.SECOND_CLASS) + if __name__ == '__main__': #check configuration ojp_trip_request_xml='' check_configuration() serializer_config = SerializerConfig(ignore_default_attributes=True, pretty_print=True) serializer = XmlSerializer(config=serializer_config) - for rf in READFILE: - if (not READTRIPREQUESTFILE): - ojp_trip_request = test_create_ojp_trip_request_simple_1() - ojp_trip_request_xml = serializer.render(ojp_trip_request, ns_map=ns_map) - else: - inputfile = open(rf, 'r', encoding='utf-8') + """ + Load JSON from json_path into test_run_dict, then loop through all top-level elements + and process those with "active": true. + + Example action: print file name and number of travellers. Replace the print block + with whatever processing you need. + """ + # Read file + with open("test_configuration.json", "r", encoding="utf-8") as f: + test_run_dict = json.load(f) + + # test_run_dict is typically a list (as in your example). If it's a dict containing + # the list under some key, adapt accordingly. + if not isinstance(test_run_dict, list): + raise ValueError("Expected JSON top-level to be a list of test runs") + + # Loop through active elements + for idx, element in enumerate(test_run_dict): + # Skip non-dict entries + if not isinstance(element, dict): + continue + + active = element.get("active", False) + if active: + file_name = element.get("file") + travellers = element.get("travellers", []) + subscriptions = element.get("subscriptions") + relationship = element.get("relationship") + print( + f"\n**************************************************************************************\n") + print(f"Active element #{idx}: file={file_name}") + print(f" travellers: {len(travellers)}") + print(f" subscriptions: {subscriptions}") + print(f" relationship: {relationship}") + inputfile = open(file_name, 'r', encoding='utf-8') ojp_trip_request_xml = inputfile.read() inputfile.close() - xml_logger.log_serialized('ojp_trip_request.xml', ojp_trip_request_xml) - try: - print (f"\n********************************************\n{rf}\n********************************************\n") - if is_version_2_0(ojp_trip_request_xml): - #We process an OJP 2 request - status, r = call_ojp_20(ojp_trip_request_xml) - if status != 200: - message = f"call returned a wrong status {status}" - raise IOError(message) - ojp_trip_result = parse_ojp2(r) - ojp_trip_result_xml = serializer.render(ojp_trip_result, ns_map=ns_map) - xml_logger.log_serialized('ojp_trip_reply.xml', ojp_trip_result_xml) - ojp_fare_request = map_ojp2_trip_result_to_ojp2_fare_request(ojp_trip_result) - if ojp_fare_request is None: - raise OJPError("ERR102: No fare request could be generated from trip delivery.") + xml_logger.log_serialized('ojp_trip_request.xml', ojp_trip_request_xml) + try: + + if is_version_2_0(ojp_trip_request_xml): + # We process an OJP 2 request + status, r = call_ojp_20(ojp_trip_request_xml) + if status != 200: + message = f"call returned a wrong status {status}" + raise IOError(message) + ojp_trip_result = parse_ojp2(r) + ojp_trip_result_xml = serializer.render(ojp_trip_result, ns_map=ns_map) + xml_logger.log_serialized('ojp_trip_reply.xml', ojp_trip_result_xml) + ojp_fare_params = build_ojp2_fare_params(travellers, subscriptions, relationship) + ojp_fare_request = map_ojp2_trip_result_to_ojp2_fare_request(ojp_trip_result, ojp_fare_params) + if ojp_fare_request is None: + raise OJPError("ERR102: No fare request could be generated from trip delivery.") + else: + ojp_fare_request_xml = serializer.render(ojp_fare_request, ns_map=ns_map) + xml_logger.log_serialized('ojp_fare_request.xml', ojp_fare_request_xml) + nova_response = test_nova_request_reply_for_ojp2(ojp_fare_request) + if nova_response: + ojp_fare_result = test_nova_to_ojp2(nova_response) + if not (ojp_fare_result): + ojp_fare_result_xml = "Not a valid nova fare response received." # TODO Improve with better handling + xml_logger.log_serialized('ojp_fare_result.xml', ojp_fare_result_xml) + else: + ojp_fare_result_xml = serializer.render(ojp_fare_result, ns_map=ns_map) + for fr1 in ojp_fare_result.fare_result: + for fr in fr1.trip_fare_result: + print("Legs: " + str(fr.from_leg_id_ref) + "-" + str(fr.to_leg_id_ref)) + print(fr.fare_product) + print("\n") + xml_logger.log_serialized('ojp_fare_result.xml', ojp_fare_result_xml) else: - ojp_fare_request_xml = serializer.render(ojp_fare_request, ns_map=ns_map) - xml_logger.log_serialized('ojp_fare_request.xml', ojp_fare_request_xml) - nova_response = test_nova_request_reply_for_ojp2(ojp_fare_request) - if nova_response: - ojp_fare_result = test_nova_to_ojp2(nova_response) - if not(ojp_fare_result): - ojp_fare_result_xml="Not a valid nova fare response received." #TODO Improve with better handling - xml_logger.log_serialized('ojp_fare_result.xml', ojp_fare_result_xml) + # We work on a OJP 1.0 request + status, r = call_ojp_2000(ojp_trip_request_xml) + if status != 200: + message = f"call returned a wrong status {status}:\n{r}" + raise IOError(message) + ojp_trip_result1 = parse_ojp(r) + ojp_trip_result_xml1 = serializer.render(ojp_trip_result1, ns_map=ns_map) + xml_logger.log_serialized('ojp_trip_reply.xml', ojp_trip_result_xml1) + ojp_fare_params = build_ojp2_fare_params(travellers, subscriptions, relationship) + ojp_fare_request1 = map_ojp_trip_result_to_ojp_fare_request(ojp_trip_result1,ojp_fare_params) + ojp_fare_request_xml1 = serializer.render(ojp_fare_request1, ns_map=ns_map) + xml_logger.log_serialized('ojp_fare_request.xml', ojp_fare_request_xml1) + + if ojp_fare_request1: + nova_response1 = test_nova_request_reply(ojp_fare_request1) else: - ojp_fare_result_xml = serializer.render(ojp_fare_result, ns_map=ns_map) - for fr1 in ojp_fare_result.fare_result: - for fr in fr1.trip_fare_result: - print("Legs: " + str(fr.from_leg_id_ref) + "-" + str(fr.to_leg_id_ref)) - print(fr.fare_product) + nova_response1 = None + if nova_response1: + ojp_fare_result1: OjpfareDelivery = test_nova_to_ojp(nova_response1) + ojp_fare_result_xml1 = serializer.render(ojp_fare_result1, ns_map=ns_map) + if ojp_fare_result1 is None: + raise OJPError("ERR100: No OJP Fare result obtained.") + for fr in ojp_fare_result1.fare_result: + for fr1 in fr.trip_fare_result: + print("Legs: " + str(fr1.from_trip_leg_id_ref) + "-" + str(fr1.to_trip_leg_id_ref)) + print(fr1.fare_product) print("\n") - xml_logger.log_serialized('ojp_fare_result.xml', ojp_fare_result_xml) - - - else: - # We work on a OJP 1.0 request - status,r = call_ojp_2000(ojp_trip_request_xml) - if status != 200: - message = f"call returned a wrong status {status}:\n{r}" - raise IOError(message) - ojp_trip_result1 = parse_ojp(r) - ojp_trip_result_xml1 = serializer.render(ojp_trip_result1, ns_map=ns_map) - xml_logger.log_serialized('ojp_trip_reply.xml', ojp_trip_result_xml1) - ojp_fare_request1 = map_ojp_trip_result_to_ojp_fare_request(ojp_trip_result1) - ojp_fare_request_xml1 = serializer.render(ojp_fare_request1, ns_map=ns_map) - xml_logger.log_serialized('ojp_fare_request.xml', ojp_fare_request_xml1) - - if ojp_fare_request1 : - nova_response1 = test_nova_request_reply(ojp_fare_request1) - else: - nova_response1 = None - if nova_response1: - ojp_fare_result1 : OjpfareDelivery = test_nova_to_ojp(nova_response1) - ojp_fare_result_xml1 = serializer.render(ojp_fare_result1, ns_map=ns_map) - if ojp_fare_result1 is None: - raise OJPError("ERR100: No OJP Fare result obtained.") - for fr in ojp_fare_result1.fare_result: - for fr1 in fr.trip_fare_result: - print("Legs: " + str(fr1.from_trip_leg_id_ref) + "-" + str(fr1.to_trip_leg_id_ref)) - print(fr1.fare_product) - print("\n") - xml_logger.log_serialized('ojp_fare_result.xml', ojp_fare_result_xml1) - - - except Exception as e: - # not yet really sophisticated handling of all other errors during the work (should be regular OJPDeliveries with OtherError set - logger.exception(e) - xml_logger.log_serialized('error_file.xml', str(e)) - traceback.print_exc() + xml_logger.log_serialized('ojp_fare_result.xml', ojp_fare_result_xml1) + + + + except Exception as e: + # not yet really sophisticated handling of all other errors during the work (should be regular OJPDeliveries with OtherError set + logger.exception(e) + xml_logger.log_serialized('error_file.xml', str(e)) + traceback.print_exc() From f75679d0d3800660d852133522ed0113a61488bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Fri, 27 Feb 2026 00:09:45 +0100 Subject: [PATCH 09/16] fixing ojp2nova mapping and better tests --- map_ojp_to_nova.py | 30 ++++++++--------- test_configuration.json | 73 ++++++++++++++++++++++++++++++++++++++++- test_network_flow.py | 40 ++++++++++++++++++++-- 3 files changed, 124 insertions(+), 19 deletions(-) diff --git a/map_ojp_to_nova.py b/map_ojp_to_nova.py index 2027d83..fae997f 100644 --- a/map_ojp_to_nova.py +++ b/map_ojp_to_nova.py @@ -101,11 +101,11 @@ def map_fare_request_to_nova_request(ojp: Ojp, age: int=30) -> Optional[PreisAus travellers.append(FarePassengerStructure(age=25, entitlement_product=["HTA"])) #fix for bad data in traveller else: # we go through all travellers - for traveller in ojp.ojprequest.service_request.ojpfare_request[0].params.traveller: + for traveler in ojp.ojprequest.service_request.ojpfare_request[0].params.traveller: #TODO we set age, but this might be wrongs - if not(traveller.age is int): - traveller.age=25 - travellers.append(traveller) + if not(traveler.age is int): + traveler.age=25 + travellers.append(traveler) except: pass @@ -192,27 +192,27 @@ def map_fare_request_to_nova_request(ojp: Ojp, age: int=30) -> Optional[PreisAus entitlements =[] for entitlement_product in traveler.entitlement_product: - if "HTA" in entitlement_product: + if "HTA" in entitlement_product.entitlement_product_ref: entitlements.append("HTA") - elif "JUNIORKARTE" in entitlement_product: + elif "JUNIORKARTE" in entitlement_product.entitlement_product_ref: entitlements.append("JUNIORKARTE") - elif "EURAIL_CH_1KL" in entitlement_product: + elif "EURAIL_CH_1KL" in entitlement_product.entitlement_product_ref: entitlements.append("EURAIL_CH_1KL") - elif "EURAIL_CH_2KL" in entitlement_product: + elif "EURAIL_CH_2KL" in entitlement_product.entitlement_product_ref: entitlements.append("EURAIL_CH_2KL") - elif "INTERRAIL_CH_1KL" in entitlement_product: + elif "INTERRAIL_CH_1KL" in entitlement_product.entitlement_product_ref: entitlements.append("INTERRAIL_CH_1KL") - elif "INTERRAIL_CH_2KL" in entitlement_product: + elif "INTERRAIL_CH_2KL" in entitlement_product.entitlement_product_ref: entitlements.append("INTERRAIL_CH_2KL") - elif "GA_2KL" in entitlement_product: + elif "GA_2KL" in entitlement_product.entitlement_product_ref: entitlements.append("GA_2KL") - elif "GA_1KL" in entitlement_product: + elif "GA_1KL" in entitlement_product.entitlement_product_ref: entitlements.append("GA_1KL") - elif "ST_PASS_2KL" in entitlement_product: + elif "ST_PASS_2KL" in entitlement_product.entitlement_product_ref: entitlements.append("ST_PASS_2KL") - elif "ST_PASS_1KL" in entitlement_product: + elif "ST_PASS_1KL" in entitlement_product.entitlement_product_ref: entitlements.append("ST_PASS_1KL") - elif "KEINE_ERMAESSIGUNGSKARTE": + elif "KEINE_ERMAESSIGUNGSKARTE" in entitlement_product.entitlement_product_ref: #Keine Ermässigungskarte pass else: diff --git a/test_configuration.json b/test_configuration.json index c16f882..6efc71d 100644 --- a/test_configuration.json +++ b/test_configuration.json @@ -17,6 +17,77 @@ ], "subscriptions": true, "relationship": "KEINE_REISENDENBEZIEHUNG", - "active": true + "active": false + }, + { + "file": "input/input_Bern_Belp.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "1231231" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": false + }, + + { + "file": "input/input_Bodensee_2.xml", + "travellers": [ + { + "age": 75, + "entitlements": "", + "typ": "PERSON", + "tkid": "12331231" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": false + }, + { + "file": "input/input_Bodensee_2.xml", + "travellers": [ + { + "age": 75, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "12331231" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": false + }, + { + "file": "input/input_bus_postauto.xml", + "travellers": [ + { + "age": 13, + "entitlements": "", + "typ": "PERSON", + "tkid": "12331231" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": false + }, + { + "file": "input/input_ojp_2_test.xml", + "travellers": [ + { + "age": 25, + "entitlements": "", + "typ": "PERSON", + "tkid": "12331231" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true } ] \ No newline at end of file diff --git a/test_network_flow.py b/test_network_flow.py index 6366d8d..1df3c01 100644 --- a/test_network_flow.py +++ b/test_network_flow.py @@ -31,7 +31,7 @@ FarePassengerStructure as FarePassengerStructure2, FareAuthorityRefStructure as FareAuthorityRefStructure2, \ PassengerCategoryEnumeration, FareClassEnumeration, EntitlementProductStructure, EntitlementProductListStructure from ojp import Ojp, OjpfareDelivery, FareParamStructure, FarePassengerStructure, FareAuthorityRef, \ - TypeOfFareClassEnumeration, PassengerCategoryEnumeration + TypeOfFareClassEnumeration, PassengerCategoryEnumeration, EntitlementProductRef from xslt_transform import transform_xml, is_version_2_0 import xml_logger import logging @@ -184,10 +184,44 @@ def build_ojp2_fare_params(travellers, subscriptions, relationship) -> FareParam if relationship: #TODO we will have to process relationships, need to put this in extensions pass + return FareParamStructure2(fare_authority_filter=filters,traveller=ojptravellers,passenger_category=[PassengerCategoryEnumeration.ADULT],fare_class =FareClassEnumeration.SECOND_CLASS) - return FareParamStructure2(fare_authority_filter=filters,traveller=ojptravellers,passenger_category=[PassengerCategoryEnumeration.ADULT],fare_class =FareClassEnumeration.SECOND_CLASS) +def build_ojp_fare_params(travellers, subscriptions, relationship) -> FareParamStructure: + + #travellers can't be empty. we already checked + ojptravellers = [] + for t_idx, traveller in enumerate(travellers): + if not isinstance(traveller, dict): + continue + typ = traveller.get("typ") + tkid = traveller.get("tkid") # we cannot process this for the time being + raw_ent = traveller.get("entitlements") + ent_list = split_entitlements(raw_ent) + entitlements=[] + for ent in ent_list: + entitlements.append(EntitlementProductStructure(fare_authority_ref="NOVA",entitlement_product_ref=ent, entitlement_product_name=ent)) + passenger_category = traveller.get("passenger_category") + birthday = traveller.get("birthday") #we cannot process this for the time being + age = traveller.get("age") + + ojptraveller = FarePassengerStructure(age=age,passenger_category=passenger_category,entitlement_product=entitlements) + ojptravellers.append(ojptraveller) + filters =[] + if subscriptions: + # we use subscriptions instead of regular tickets + filters.append(FareAuthorityRef(value="NOVA-Subscription")) + else: + filters.append(FareAuthorityRef(value="NOVA")) + if relationship: + #TODO we will have to process relationships, need to put this in extensions + pass + + + + return FareParamStructure(fare_authority_filter=filters,traveller=ojptravellers,passenger_category=[PassengerCategoryEnumeration.ADULT],travel_class =FareClassEnumeration.SECOND_CLASS) + if __name__ == '__main__': #check configuration @@ -274,7 +308,7 @@ def build_ojp2_fare_params(travellers, subscriptions, relationship) -> FareParam ojp_trip_result1 = parse_ojp(r) ojp_trip_result_xml1 = serializer.render(ojp_trip_result1, ns_map=ns_map) xml_logger.log_serialized('ojp_trip_reply.xml', ojp_trip_result_xml1) - ojp_fare_params = build_ojp2_fare_params(travellers, subscriptions, relationship) + ojp_fare_params = build_ojp_fare_params(travellers, subscriptions, relationship) ojp_fare_request1 = map_ojp_trip_result_to_ojp_fare_request(ojp_trip_result1,ojp_fare_params) ojp_fare_request_xml1 = serializer.render(ojp_fare_request1, ns_map=ns_map) xml_logger.log_serialized('ojp_fare_request.xml', ojp_fare_request_xml1) From 8275ae0f25a42e6b9447f758d691af11f9fd36b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Fri, 27 Feb 2026 00:12:15 +0100 Subject: [PATCH 10/16] Update test_configuration.json --- test_configuration.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test_configuration.json b/test_configuration.json index 6efc71d..4cb3d12 100644 --- a/test_configuration.json +++ b/test_configuration.json @@ -87,6 +87,32 @@ } ], "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": false + }, + { + "file": "input/input_ojp_2_test.xml", + "travellers": [ + { + "age": 25, + "entitlements": "", + "typ": "PERSON", + "tkid": "123312312" + }, + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312313" + }, + { + "age": 25, + "entitlements": "", + "typ": "PERSON", + "tkid": "123312314" + } + ], + "subscriptions": false, "relationship": "KEINE_REISENDENBEZIEHUNG", "active": true } From e4f1fd1059e6bbee4dba851ed727524364d2f926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Tue, 3 Mar 2026 16:12:44 +0100 Subject: [PATCH 11/16] updates for subscriptions and test Mattelift --- README.md | 4 +-- input/input_Mattelift.xml | 39 ++++++++++++++++++++++++++ input/input_coordinates.xml | 8 +++--- input/input_coordinates_ojp2.xml | 47 +++++++++++++++++++++++++++++++ map_nova_to_ojp.py | 5 ++-- map_nova_to_ojp2.py | 2 +- map_ojp2_to_nova.py | 10 ++++++- map_ojp_to_nova.py | 3 +- test_configuration.json | 48 ++++++++++++++++++++++++++++++++ xslt_transform.py | 4 ++- 10 files changed, 158 insertions(+), 12 deletions(-) create mode 100644 input/input_Mattelift.xml create mode 100644 input/input_coordinates_ojp2.xml diff --git a/README.md b/README.md index 9a9320d..8ba93a6 100644 --- a/README.md +++ b/README.md @@ -160,9 +160,9 @@ They are used as "TC-". The value needs to be transferred to nova for # Usage notes - The TripResult used in the OJP fare service should not be based on short-term real-time information. So the TripRequest should usually contain a UseRealtime set to false. -- The price is only in one direction. If the full price in both directions is needed and artificial trip must be constructed, that contains all necessary legs in both direction (and works from the time view): Search A to B, some delay, search B to A, concatenate the trips into one. This IS necessary as sometimes the trip in both direction is cheaper than two single trips. +- The price is only for A to B. If the full price in both directions is needed and artificial trip must be constructed, that contains all necessary legs in both direction (and works from the time view): Search A to B, some delay, search B to A, concatenate the trips into one. This IS necessary as sometimes the trip in both direction is cheaper than two single trips. - We base on the commercial stops (as BPUIC). The calls are more and more based on the SLOID. It is important only provide the commerical stops to NOVA. In some cases the commercial stop is no longer directly based on the other one (e.g. Europaplatz). The right one must be obtained from the PlaceContext (done in `sloid2didok` function). - +- While OJP generelly supports multiple request. The current version of the software only processes the first request. # Testing In the folder `input` there are possible xml files. Some work, some are problematic diff --git a/input/input_Mattelift.xml b/input/input_Mattelift.xml new file mode 100644 index 0000000..48d2492 --- /dev/null +++ b/input/input_Mattelift.xml @@ -0,0 +1,39 @@ + + + + + + de + + 2026-03-03T12:36:55.154Z + OJP_DemoApp_Beta_OJP2.0 + + 2026-03-03T12:36:55.154Z + + + 8500258 + + n/a + + + + + + 8500249 + + n/a + + + + + 1 + explanatory + true + false + false + true + + + + + \ No newline at end of file diff --git a/input/input_coordinates.xml b/input/input_coordinates.xml index fff516b..212f7f1 100644 --- a/input/input_coordinates.xml +++ b/input/input_coordinates.xml @@ -29,10 +29,10 @@ - 5 - true - true - true + 2 + false + false + false true diff --git a/input/input_coordinates_ojp2.xml b/input/input_coordinates_ojp2.xml new file mode 100644 index 0000000..de959c1 --- /dev/null +++ b/input/input_coordinates_ojp2.xml @@ -0,0 +1,47 @@ + + + + + + de + + 2026-03-03T15:00:17.446Z + OJP_DemoApp_Beta_OJP2.0 + + 2026-03-03T15:00:17.446Z + + + + 7.449772 + 46.962961 + + + + 46.962961,7.449772 + + + + + + + 7.489687 + 46.930422 + + + + 46.930422,7.489687 + + + + + 5 + explanatory + true + true + true + true + + + + + \ No newline at end of file diff --git a/map_nova_to_ojp.py b/map_nova_to_ojp.py index 15aeff6..c96c024 100644 --- a/map_nova_to_ojp.py +++ b/map_nova_to_ojp.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import datetime +from decimal import Decimal from typing import List, Optional, Dict from xsdata.models.datatype import XmlDateTime @@ -41,10 +42,10 @@ def map_preis_auspraegung_to_trip_fare_result(preis_auspraegungen: List[PreisAus fare_authority_ref='ch:1:sboid:101704', fare_authority_text='Alliance SwissPass', price=preis_auspraegung.preis.betrag, - net_price=round(float(preis_auspraegung.preis.betrag)*(1.0-VATRATE/100),2), + net_price=round(float(preis_auspraegung.preis.betrag)*(1.0- VATRATE/100),2), currency=preis_auspraegung.preis.waehrung, required_card=required_card, - vat_rate=VATRATE, + vat_rate=Decimal(VATRATE).quantize(Decimal("0.1")), travel_class=map_klasse_to_fareclass(preis_auspraegung.produkt_einfluss_faktoren.klasse))])) return FareResultStructure(result_id=id, trip_fare_result=tripfareresults) diff --git a/map_nova_to_ojp2.py b/map_nova_to_ojp2.py index f946707..fe60eda 100644 --- a/map_nova_to_ojp2.py +++ b/map_nova_to_ojp2.py @@ -41,7 +41,7 @@ def map_preis_auspraegung_to_trip_fare_result(preis_auspraegungen: List[PreisAus net_price=round(float(preis_auspraegung.preis.betrag)*(1.0-VATRATE/100),2), currency=preis_auspraegung.preis.waehrung, required_card=required_card, - vat_rate=Decimal(VATRATE), + vat_rate=Decimal(VATRATE).quantize(Decimal("0.1")), fare_class=map_klasse_to_fareclass(preis_auspraegung.produkt_einfluss_faktoren.klasse))])) return FareResultStructure(id=id, trip_fare_result=tripfareresults) diff --git a/map_ojp2_to_nova.py b/map_ojp2_to_nova.py index e7d310c..ff8bafc 100644 --- a/map_ojp2_to_nova.py +++ b/map_ojp2_to_nova.py @@ -79,6 +79,14 @@ def map_fare_request_to_nova_request(ojp: Ojp, age: int=30) -> Optional[PreisAus and len(ojp.ojprequest.service_request.ojpfare_request[0].trip_fare_request.trip.leg) > 0): return None + #handling of abos (monthly) otherwise the product_taxonomie is set to the standard + produkt_taxonomie = "SBB Preisauskunft" + try: + val = ojp.ojprequest.service_request.ojpfare_request[0].params.fare_authority_filter[0] + if "NOVA-Subscription" in val.value: + produkt_taxonomie="SBB Abonnemente" + except: + pass try: if ojp.ojprequest.service_request.ojpfare_request[0].params.traveller is None: travellers = [] @@ -171,7 +179,7 @@ def map_fare_request_to_nova_request(ojp: Ojp, age: int=30) -> Optional[PreisAus correlation_id=str(uuid.uuid1()), geschaefts_prozess_id="1781786f-57ba-4e9a-bc29-287e2aa97f9a"), angebots_filter=[TaxonomieFilter( - produkt_taxonomie="SBB Preisauskunft", + produkt_taxonomie=produkt_taxonomie, taxonomie_klasse_pfad=[TaxonomieKlassePfad(EmptyType())])], reisender=reisende, verbindung=verbindungen diff --git a/map_ojp_to_nova.py b/map_ojp_to_nova.py index fae997f..23519c9 100644 --- a/map_ojp_to_nova.py +++ b/map_ojp_to_nova.py @@ -88,7 +88,8 @@ def map_fare_request_to_nova_request(ojp: Ojp, age: int=30) -> Optional[PreisAus #handling of abos (monthly) otherwise the product_taxonomie is set to the standard produkt_taxonomie = "SBB Preisauskunft" try: - if "NOVA-Subscription" in ojp.ojprequest.service_request.ojpfare_request[0].params.fare_authority_filter[0] : + val = ojp.ojprequest.service_request.ojpfare_request[0].params.fare_authority_filter[0] + if "NOVA-Subscription" in val.value: produkt_taxonomie="SBB Abonnemente" except: pass diff --git a/test_configuration.json b/test_configuration.json index 4cb3d12..0f5f9e9 100644 --- a/test_configuration.json +++ b/test_configuration.json @@ -113,6 +113,54 @@ } ], "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": false + }, + + { + "description":"Test für Mattelift", + "file": "input/input_Mattelift.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": false + }, + + { + "description":"Test mit Koordinaten für Arbeitsweg (Abos).", + "file": "input/input_coordinates.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": true, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": false + }, + + { + "description":"Test mit Koordinaten für Arbeitsweg (Abos).OJP 2.0", + "file": "input/input_coordinates_ojp2.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": true, "relationship": "KEINE_REISENDENBEZIEHUNG", "active": true } diff --git a/xslt_transform.py b/xslt_transform.py index 8d9db7f..e1640c3 100644 --- a/xslt_transform.py +++ b/xslt_transform.py @@ -10,7 +10,9 @@ def is_version_2_0(xml_string:str) -> bool: # Check if there are at least two lines if len(lines) < 2: return False - + #check the first line for the version (when the xml header was omitted) + if 'version="2.0"' in lines[0]: + return True # Check the second line for the version second_line = lines[1] if 'version="2.0"' in second_line: From 7d651c352912994b079da681cbc7f8893e787434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Wed, 4 Mar 2026 11:54:52 +0100 Subject: [PATCH 12/16] move is_version_2 to better place --- support.py | 17 +++++++++++++++++ test_network_flow.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/support.py b/support.py index 9a5f42e..6519b92 100644 --- a/support.py +++ b/support.py @@ -92,6 +92,23 @@ def sloid2didok(sloid:str)->int: tmp=my_dict.get(str(tmp),str(tmp)) # replaces if it is in the table or gets the value back return tmp +def is_version_2_0(xml_string:str) -> bool: + #simple test to see if the xml is OJP version 2.0 (or should be) + # Split the string into lines + lines = xml_string.splitlines() + + # Check if there are at least two lines + if len(lines) < 2: + return False + #check the first line for the version (when the xml header was omitted) + if 'version="2.0"' in lines[0]: + return True + # Check the second line for the version + second_line = lines[1] + if 'version="2.0"' in second_line: + return True + return False + # raising an error and sending it back. Does not add values from err_str class OJPError(Exception) : diff --git a/test_network_flow.py b/test_network_flow.py index 99f35a4..7e66cac 100644 --- a/test_network_flow.py +++ b/test_network_flow.py @@ -33,7 +33,7 @@ PassengerCategoryEnumeration, FareClassEnumeration, EntitlementProductStructure, EntitlementProductListStructure from ojp import Ojp, OjpfareDelivery, FareParamStructure, FarePassengerStructure, FareAuthorityRef, \ TypeOfFareClassEnumeration, PassengerCategoryEnumeration, EntitlementProductRef -from xslt_transform import transform_xml, is_version_2_0 +from support import is_version_2_0 import xml_logger import logging From a73e8dcc166d9ac97606bf146c27932785d9a20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Thu, 5 Mar 2026 14:29:49 +0100 Subject: [PATCH 13/16] fixed problem with train numbers in OJP 2.0 --- input/input_Bern_Zweisimmen_BLS_Zukunft.xml | 3 +-- map_ojp2_to_nova.py | 14 ++++++++------ map_ojp_to_nova.py | 4 ++-- test_configuration.json | 18 +++++++++++++++++- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/input/input_Bern_Zweisimmen_BLS_Zukunft.xml b/input/input_Bern_Zweisimmen_BLS_Zukunft.xml index e4ce0bc..a277a5a 100644 --- a/input/input_Bern_Zweisimmen_BLS_Zukunft.xml +++ b/input/input_Bern_Zweisimmen_BLS_Zukunft.xml @@ -13,8 +13,7 @@ Bern - 2025-11-24T15:30:40 - + 2026-03-10T15:30:40 diff --git a/map_ojp2_to_nova.py b/map_ojp2_to_nova.py index b7983ef..a8a03e9 100644 --- a/map_ojp2_to_nova.py +++ b/map_ojp2_to_nova.py @@ -21,15 +21,17 @@ def map_timed_leg_to_segment(timed_leg: TimedLegStructure) -> FahrplanVerbindung operator_ref = timed_leg.service.operator_ref # needs to be processed afterwards to get the verwaltungs_code gattungs_code = timed_leg.service.mode.short_name.text[0].value # is correct, but a bit of a hack + # in OJP 2.' the number is in the TrainNumber + verkehrs_mittel_nummer=timed_leg.service.train_number # unfortunately it is not in line_ref, but in Extension/ojp:PublishedJourneyNumber - _, verkehrs_mittel_nummer, _ = line_ref.split(':') + #_, verkehrs_mittel_nummer, _ = line_ref.split(':') # This is an other hack. - verkehrs_mittel_nummer = ''.join(filter(lambda x: x.isdigit(), verkehrs_mittel_nummer)) - try: + #verkehrs_mittel_nummer = ''.join(filter(lambda x: x.isdigit(), verkehrs_mittel_nummer)) + #try: # Set verkehrs_mittel_nummer to timed_leg.extension.publishedjourneynumber? - verkehrs_mittel_nummer = [x.children[0].text for x in timed_leg.extension.childen if x.qname == '{http://www.vdv.de/ojp}PublishedJourneyNumber'][0] - except: - pass + # verkehrs_mittel_nummer = [x.children[0].text for x in timed_leg.extension.childen if x.qname == '{http://www.vdv.de/ojp}PublishedJourneyNumber'][0] + #except: + # pass verwaltungs_code= process_operating_ref_ojp2(operator_ref) diff --git a/map_ojp_to_nova.py b/map_ojp_to_nova.py index 23519c9..d38478e 100644 --- a/map_ojp_to_nova.py +++ b/map_ojp_to_nova.py @@ -32,9 +32,8 @@ def map_timed_leg_to_segment(timed_leg: TimedLegStructure) -> FahrplanVerbindung _, verkehrs_mittel_nummer, _ = line_ref.split(':') # We try to extract the line for NOVA verkehrs_mittel_nummer = ''.join(filter(lambda x: x.isdigit(), verkehrs_mittel_nummer)) - # For trains and in OJP 1.0 it is needed to extract the train number from the extension + # For trains and in OJP 1.0 it is needed to extract the train number from the extension PublihedJourneyNumber # e.g. necessary for discounts of BLS in future travel - # TODO perhaps do only for rail try: # Set verkehrs_mittel_nummer to timed_leg.extension.publishedjourneynumber? verkehrs_mittel_nummer = [x.children[0].text for x in timed_leg.extension.children if x.qname == '{http://www.vdv.de/ojp}PublishedJourneyNumber'][0] @@ -175,6 +174,7 @@ def map_fare_request_to_nova_request(ojp: Ojp, age: int=30) -> Optional[PreisAus r_typ = ReisendenTypCode.PERSON elif traveler.passenger_category.value == "Motorcycle": r_typ = ReisendenTypCode.PERSON + #TODO Raise error elif traveler.passenger_category.value == "Car": r_typ = ReisendenTypCode.PERSON diff --git a/test_configuration.json b/test_configuration.json index 0f5f9e9..f7d9bb7 100644 --- a/test_configuration.json +++ b/test_configuration.json @@ -17,7 +17,7 @@ ], "subscriptions": true, "relationship": "KEINE_REISENDENBEZIEHUNG", - "active": false + "active": true }, { "file": "input/input_Bern_Belp.xml", @@ -161,6 +161,22 @@ } ], "subscriptions": true, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": false + }, + + { + "description":"Test Bern - Zweisimmen. Achtung: Datum muss gesetzt werden", + "file": "input/input_Bern_Zweisimmen_BLS_Zukunft.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, "relationship": "KEINE_REISENDENBEZIEHUNG", "active": true } From d0ab7865618404a8c8bb54a4d09705f083b04d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Fri, 6 Mar 2026 22:11:43 +0100 Subject: [PATCH 14/16] test_network_flow now processes the full json. - handles future - handles assertions - better documentation of each test case. - test_network_flow now has arguments. --- README.md | 29 +- input/input_Bern_Chur_SOB_Zukunft.xml | 2 +- input/input_menusio.xml | 39 ++ input/input_nachtbus.xml | 2 +- support.py | 80 ++- test_configuration.json | 896 ++++++++++++++++++++------ test_configuration.py | 36 -- test_network_flow.py | 74 ++- 8 files changed, 913 insertions(+), 245 deletions(-) create mode 100644 input/input_menusio.xml diff --git a/README.md b/README.md index 8ba93a6..a75b10b 100644 --- a/README.md +++ b/README.md @@ -166,13 +166,35 @@ They are used as "TC-". The value needs to be transferred to nova for # Testing In the folder `input` there are possible xml files. Some work, some are problematic -The selection of files to use in `test_network_flow.py` is done by `test_configuration.py` which basically contains an array -of the files to use. `test_configuration.py` contains the explanaition on what works and what not. +The selection of files to use in `test_network_flow.py` is done by `test_configuration.json`. -Be aware: For some discount tickets the trip needs to be several days in the future. Currently this needs to be set manually in the respective +Be aware: For some discount tickets the trip needs to be several days in the future. Currently, this needs to be set manually in the respective request file in `input`. We don't use the `2025-10-10T15:30:40` in many cases, as trips in the past don't have prives. If it is omitted, then `now` is used. but we keep it in the files commented out, so that you just can put in the time. +test_configuration.json contains an array of test cases: +- id: the identifier +- description: The description of the test case +- file: the file to be loaded (contains an OJPTripRequest) +- travellers: the information about the travelers + - age + - entitlements: the list of entitlement products (there is only a short list available. Most important HTA) + - typ: PERSON, DOG, xxx + - tkid: not supported yet + - birthday: not supported yet + - gender: not supported yet +- subscription: true or false. one ticket or a subscription +- relationship: a list of relevant relationships between the travelers. not supported yet +- result: pass or fail. Should there be a price. +- assert: if set, then the value provided should be found in the answer. not supported yet +- active: saying if this test is active when running the whole file +- future: if set indicates when in the future should be searched (days). The system then uses a random time between 8:00 and 12:00 to start +- start_time: HH:MM:SS, used to test a night bus. + +`test_network_flow.py` can be used with at most one parameter: +- `--all`: tests all tests in the test file +- `--id=`: tests the test with the given id +- Without parameter all tests that are set to active are tested. # Changelog @@ -182,6 +204,7 @@ If it is omitted, then `now` is used. but we keep it in the files commented out, ## 1.2 prepared for dockerization - Make sure that we can run this in our new environment. +- Fixed more bugs. ## 1.1 Bug fixes, better documentation, better testing - OJP 2.0 support (first version) diff --git a/input/input_Bern_Chur_SOB_Zukunft.xml b/input/input_Bern_Chur_SOB_Zukunft.xml index aa39a71..dd897db 100644 --- a/input/input_Bern_Chur_SOB_Zukunft.xml +++ b/input/input_Bern_Chur_SOB_Zukunft.xml @@ -13,7 +13,7 @@ Bern - 2024-12-08T16:30:40 --> + diff --git a/input/input_menusio.xml b/input/input_menusio.xml new file mode 100644 index 0000000..469b185 --- /dev/null +++ b/input/input_menusio.xml @@ -0,0 +1,39 @@ + + + + + de + + 2026-03-05T15:57:19.971Z + OJP_DemoApp_Beta_OJP2.0 + + 2026-03-05T15:57:19.971Z + + + 8505000 + + n/a + + + 2026-03-10T21:00:00.000Z + + + + 8505417 + + n/a + + + + + 5 + explanatory + true + true + true + true + + + + + \ No newline at end of file diff --git a/input/input_nachtbus.xml b/input/input_nachtbus.xml index 328cad6..8421cdc 100644 --- a/input/input_nachtbus.xml +++ b/input/input_nachtbus.xml @@ -16,7 +16,7 @@ Bern, Bahnhof (Bern) - 2025-11-01T00:00:45.343Z + diff --git a/support.py b/support.py index 6519b92..1394879 100644 --- a/support.py +++ b/support.py @@ -7,9 +7,14 @@ import logging from ojp2 import OperatorRef +from datetime import datetime, timedelta, timezone +import random +import re +from typing import Pattern logger = logging.getLogger(__name__) + # err_str = "" #global error string # define an error response immediatly and make sure the program can send it back (ignores all warnings that were before). @@ -20,20 +25,6 @@ def error_response(error_text:str) -> Ojp: producer_ref="OJP2NOVA", error_condition=ServiceDeliveryStructure.ErrorCondition(other_error=OtherError(error_text))))) -# storing warnings to be sent with the answer -#def add_error(error_text:str): -# global err_str -# err_str=err_str+error_text -# return - -# include accumulated warnings into the response. Status not affected (so should be warnings) -# def add_error_response(sd:ServiceDeliveryStructure): -# global err_str -# if err_str=="": -# return sd -# sd.ErrorCondition(other_error=OtherError(err_str)) -# return sd - def process_operating_ref_ojp2(operator_ref:OperatorRef) ->str: operator_ref_str=operator_ref.value return process_operating_ref(operator_ref_str) @@ -71,7 +62,6 @@ def sloid2didok(sloid:str)->int: "8014485": "8503463", "8014487": "8503462", } - try: # sloids are not integer, but didok are. So we first try to convert to id. If this works, we assume, it is a didok code didok=int(sloid) @@ -109,6 +99,62 @@ def is_version_2_0(xml_string:str) -> bool: return True return False + +def build_timestamp(days_in_future: int | None = None, start_time: str | None = None) -> str: + if days_in_future is None and start_time is None: + raise ValueError("Either days_in_future or start_time must be provided.") + if days_in_future is not None: + if not isinstance(days_in_future, int) or days_in_future < 0: + raise ValueError("days_in_future must be a non-negative integer.") + + if start_time is not None: + TIME_RE = re.compile(r'^(\d{2}):(\d{2}):(\d{2})$') + m = TIME_RE.match(start_time) + if not m: + raise ValueError('start_time must be in "HH:MM:SS" format.') + h, mnt, s = map(int, m.groups()) + if not (0 <= h <= 23 and 0 <= mnt <= 59 and 0 <= s <= 59): + raise ValueError("start_time components out of range.") + hours, minutes, seconds = h, mnt, s + else: + start_sec = 8 * 3600 + end_sec = 12 * 3600 + rand_sec = random.randint(start_sec, end_sec - 1) + hours = rand_sec // 3600 + minutes = (rand_sec % 3600) // 60 + seconds = rand_sec % 60 + + days = 0 if days_in_future is None else days_in_future + + now_utc = datetime.now(timezone.utc) + target_date = (now_utc + timedelta(days=days)).replace(hour=hours, minute=minutes, second=seconds, microsecond=random.randrange(0,1000)*1000, tzinfo=timezone.utc) + # isoformat with Z + iso = target_date.isoformat(timespec='milliseconds') + if iso.endswith('+00:00'): + iso = iso[:-6] + 'Z' + return iso + + +def insert_before_line_with_substring(text: str, pattern: str, insert: str) -> str: + lines = text.splitlines(keepends=True) + for i, line in enumerate(lines): + if pattern in line: + lines.insert(i, insert) + return ''.join(lines) + return text + +def inject_departure_datetime(ojp_trip_request_xml : str, daysinthefuture : int,start_time : str) -> str: + # TODO: shaky + # build the date + ts=build_timestamp(daysinthefuture,start_time) + if is_version_2_0(ojp_trip_request_xml): + #wrap the result + ts=""+ts+"" + return insert_before_line_with_substring(ojp_trip_request_xml,"",ts) + #wrap the result + ts=""+ts+"" + return insert_before_line_with_substring(ojp_trip_request_xml,"",ts) + # raising an error and sending it back. Does not add values from err_str class OJPError(Exception) : @@ -119,4 +165,6 @@ def __init__(self, value:str) -> None: # __str__ is to print() the value def __str__(self)->str: - return (repr(self.value)) \ No newline at end of file + return (repr(self.value)) + + diff --git a/test_configuration.json b/test_configuration.json index f7d9bb7..0c704ca 100644 --- a/test_configuration.json +++ b/test_configuration.json @@ -1,183 +1,717 @@ [ - { - "file": "input/input_problematic_Europaplatz_ojp2.xml", - "travellers": [ - { - "age": 14, - "entitlements": "HTA GA_1KL", - "typ": "PERSON", - "tkid": "1231231", - "birthday": "1999-10-01" - }, - { - "entitlements": "", - "typ": "DOG", - "tkid": "123131231" - } - ], - "subscriptions": true, - "relationship": "KEINE_REISENDENBEZIEHUNG", - "active": true - }, - { - "file": "input/input_Bern_Belp.xml", - "travellers": [ - { - "age": 25, - "entitlements": "HTA", - "typ": "PERSON", - "tkid": "1231231" - } - ], - "subscriptions": false, - "relationship": "KEINE_REISENDENBEZIEHUNG", - "active": false - }, - - { - "file": "input/input_Bodensee_2.xml", - "travellers": [ - { - "age": 75, - "entitlements": "", - "typ": "PERSON", - "tkid": "12331231" - } - ], - "subscriptions": false, - "relationship": "KEINE_REISENDENBEZIEHUNG", - "active": false - }, - { - "file": "input/input_Bodensee_2.xml", - "travellers": [ - { - "age": 75, - "entitlements": "HTA", - "typ": "PERSON", - "tkid": "12331231" - } - ], - "subscriptions": false, - "relationship": "KEINE_REISENDENBEZIEHUNG", - "active": false - }, - { - "file": "input/input_bus_postauto.xml", - "travellers": [ - { - "age": 13, - "entitlements": "", - "typ": "PERSON", - "tkid": "12331231" - } - ], - "subscriptions": false, - "relationship": "KEINE_REISENDENBEZIEHUNG", - "active": false - }, - { - "file": "input/input_ojp_2_test.xml", - "travellers": [ - { - "age": 25, - "entitlements": "", - "typ": "PERSON", - "tkid": "12331231" - } - ], - "subscriptions": false, - "relationship": "KEINE_REISENDENBEZIEHUNG", - "active": false - }, - { - "file": "input/input_ojp_2_test.xml", - "travellers": [ - { - "age": 25, - "entitlements": "", - "typ": "PERSON", - "tkid": "123312312" - }, - { - "age": 25, - "entitlements": "HTA", - "typ": "PERSON", - "tkid": "123312313" - }, - { - "age": 25, - "entitlements": "", - "typ": "PERSON", - "tkid": "123312314" - } - ], - "subscriptions": false, - "relationship": "KEINE_REISENDENBEZIEHUNG", - "active": false - }, - - { - "description":"Test für Mattelift", - "file": "input/input_Mattelift.xml", - "travellers": [ - { - "age": 25, - "entitlements": "HTA", - "typ": "PERSON", - "tkid": "123312312" - } - ], - "subscriptions": false, - "relationship": "KEINE_REISENDENBEZIEHUNG", - "active": false - }, - - { - "description":"Test mit Koordinaten für Arbeitsweg (Abos).", - "file": "input/input_coordinates.xml", - "travellers": [ - { - "age": 25, - "entitlements": "HTA", - "typ": "PERSON", - "tkid": "123312312" - } - ], - "subscriptions": true, - "relationship": "KEINE_REISENDENBEZIEHUNG", - "active": false - }, - - { - "description":"Test mit Koordinaten für Arbeitsweg (Abos).OJP 2.0", - "file": "input/input_coordinates_ojp2.xml", - "travellers": [ - { - "age": 25, - "entitlements": "HTA", - "typ": "PERSON", - "tkid": "123312312" - } - ], - "subscriptions": true, - "relationship": "KEINE_REISENDENBEZIEHUNG", - "active": false - }, - - { - "description":"Test Bern - Zweisimmen. Achtung: Datum muss gesetzt werden", - "file": "input/input_Bern_Zweisimmen_BLS_Zukunft.xml", - "travellers": [ - { - "age": 25, - "entitlements": "HTA", - "typ": "PERSON", - "tkid": "123312312" - } - ], - "subscriptions": false, - "relationship": "KEINE_REISENDENBEZIEHUNG", - "active": true - } + { + "id": 1, + "description": "Check prices in Europaplatz", + "file": "input/input_problematic_Europaplatz_ojp2.xml", + "travellers": [ + { + "age": 14, + "entitlements": "HTA GA_1KL", + "typ": "PERSON", + "tkid": "1231231", + "birthday": "1999-10-01" + }, + { + "entitlements": "", + "typ": "DOG", + "tkid": "123131231" + } + ], + "subscriptions": true, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "result": "pass", + "assert": "162.00", + "active": true + }, + { + "id": 2, + "description": "Bern- Belp with Europplatz", + "file": "input/input_Bern_Belp.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "1231231" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": false + }, + { + "id": 3, + "description": "Special ticket with boat", + "file": "input/input_Bodensee_2.xml", + "travellers": [ + { + "age": 75, + "entitlements": "", + "typ": "PERSON", + "tkid": "12331231" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "result": "pass", + "assert": "12:80", + "active": false + }, + { + "id": 4, + "description": "Bodensee part 2", + "file": "input/input_Bodensee_2.xml", + "travellers": [ + { + "age": 75, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "12331231" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "result": "pass", + "assert": "6.40", + "active": false + }, + { + "id": 5, + "description": "Test with Postauto", + "file": "input/input_bus_postauto.xml", + "travellers": [ + { + "age": 13, + "entitlements": "", + "typ": "PERSON", + "tkid": "12331231" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "result": "pass", + "assert": "3.30", + "active": false + }, + { + "id": 6, + "description": "Basic OJP 2 test.", + "file": "input/input_ojp_2_test.xml", + "travellers": [ + { + "age": 25, + "entitlements": "", + "typ": "PERSON", + "tkid": "12331231" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "result": "pass", + "assert": "24.40", + "active": false + }, + { + "id": 7, + "description": "OJP test with multiple travellers", + "file": "input/input_ojp_2_test.xml", + "travellers": [ + { + "age": 25, + "entitlements": "", + "typ": "PERSON", + "tkid": "123312312" + }, + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312313" + }, + { + "age": 25, + "entitlements": "", + "typ": "PERSON", + "tkid": "123312314" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "result": "pass", + "assert": "24.40", + "active": false + }, + { + "id": 8, + "description": "Test für Mattelift", + "file": "input/input_Mattelift.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "result": "fail", + "assert": "", + "active": false + }, + { + "id": 9, + "description": "Test mit Koordinaten für Arbeitsweg (Abos).", + "file": "input/input_coordinates.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": true, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "result": "pass", + "assert": "72.00", + "active": false + }, + { + "id": 10, + "description": "Test mit Koordinaten für Arbeitsweg (Abos).OJP 2.0", + "file": "input/input_coordinates_ojp2.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": true, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "result": "pass", + "assert": "82.00", + "active": false + }, + { + "id": 11, + "description": "Test Bern - Zweisimmen. Achtung: Datum muss gesetzt werden", + "file": "input/input_Bern_Zweisimmen_BLS_Zukunft.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": false + }, + { + "id": 12, + "description": "Test Luzern - Menusio, am 10. März. Achtung Datum", + "file": "input/input_menusio.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 13, + "description": "Monatsabo test", + "file": "input/input_Monatsabo_test.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": true, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 14, + "description": "", + "file": "input/input_test_dornbir.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 15, + "description": "", + "file": "input/input_oev_shart_plus_long.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 16, + "description": "", + "file": "input/input_oev_shart_plus_long.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 17, + "description": "", + "file": "input/input_Zuerich_Chur.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 18, + "description": "", + "file": "input/input_Visp_SaaS_Fee_problem_1_preis.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 19, + "description": "", + "file": "input/input_strange_price.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 20, + "description": "", + "file": "input/input_Zuerich_Bern.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 21, + "description": "", + "file": "input/input_Basel_Sargans.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 22, + "description": "", + "file": "input/input_Bern_Interlaken_Ost.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 23, + "description": "", + "file": "input/input_Bern_Interlaken_Gymnasium.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 24, + "description": "", + "file": "input/input_Bern_Guisanplatz_Interlaken_Gymnasium.xml", + "travellers": [ + { + "age": 70, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 25, + "description": "", + "file": "input/input_local.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 26, + "description": "", + "file": "input/input_sharing_intercity.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 27, + "description": "", + "file": "input/input_problematic_case_vasile.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 28, + "description": "", + "file": "input/input_Europaplatz.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 29, + "description": "", + "file": "input/input_Bodensee_1.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 30, + "description": "", + "file": "input/input_test_europaplatz.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 31, + "description": "", + "file": "input/input_problematic_Europaplatz_ojp1.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 32, + "description": "", + "file": "input/input_problem_footpath.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 33, + "description": "", + "file": "input/input_problematic_Europaplatz_4.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 34, + "description": "", + "file": "input/input_walk_only.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "status": "fail", + "active": true + }, + { + "id": 35, + "description": "", + "file": "input/input_sharing_only.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "status": "fail", + "active": true + }, + { + "id": 36, + "description": "", + "file": "input/input_odv_alone.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "status": "fail", + "active": true + }, + { + "id": 37, + "description": "time must be reset before run. Check if discounts exist on www.sbb.ch", + "file": "input/input_Bern_Chur_SOB_Zukunft.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "future": 3, + "active": true + }, + { + "id": 38, + "description": "Past is not handled", + "file": "input/input_in_the_past_not_handeled_well_in_Preisauskunft.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 39, + "description": "Ptime must be reset before run. Check if discounts exist on www.sbb.ch", + "file": "input/input_Bern_Zweisimmen_BLS_Zukunft.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "future": 6, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 40, + "description": "time must be reset before run. Check if discounts exist on www.sbb.ch", + "file": "input/input_bern_riehen.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "future": 8, + "active": true + }, + { + "id": 41, + "description": "xxx work needed", + "file": "input/input_aller_retour.xml", + "travellers": [ + { + "age": 18, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + }, + { + "id": 42, + "description": "xxx ", + "file": "input/input_Nachtbus.xml", + "travellers": [ + { + "age": 12, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "start_time": "23:55:00", + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true + } ] \ No newline at end of file diff --git a/test_configuration.py b/test_configuration.py index a28b4ad..cda25b2 100644 --- a/test_configuration.py +++ b/test_configuration.py @@ -1,42 +1,6 @@ READFILE = [] -READFILE.append("input/input_Bern_Belp.xml") - - -#READFILE.append("input/input_Monatsabo_test.xml") - - -''' -[ - { - "file": "input/input_Bern_Belp.xml", - "travellers": [ - { - "age":2, - "entitlements":"HTA GA_1KL", - "typ": "PERSON", - "tkid": "1231231", - "birthday": "1999-10-01" - }, - { - "entitlements":"", - "typ": "DOG", - "tkid": "123131231", - } - ], - "subscriptions": true, - "relationship": "KEINE_REISENDENBEZIEHUNG", - "active": true - - } -] - ----------------------------------------------------------------- -Reservoir for tests - -# Working OJP 1.0 examples -READFILE.append("input/input_Bern_Belp.xml") READFILE.append("input/input_test_dornbir.xml") READFILE.append("input/input_oev_shart_plus_long.xml") diff --git a/test_network_flow.py b/test_network_flow.py index 7e66cac..96b9bde 100644 --- a/test_network_flow.py +++ b/test_network_flow.py @@ -3,7 +3,8 @@ import json import traceback from typing import Tuple, Any, Optional - +import argparse +import sys import requests import urllib3 from xsdata.formats.dataclass.client import Client @@ -15,7 +16,7 @@ import ojp.fare_result_structure from api.errors import NoNovaResponseError from configuration import * -from support import OJPError +from support import OJPError, inject_departure_datetime from test_create_ojp_request import * from map_nova_to_ojp import test_nova_to_ojp from map_nova_to_ojp2 import test_nova_to_ojp2 @@ -225,8 +226,34 @@ def build_ojp_fare_params(travellers, subscriptions, relationship) -> FareParamS return FareParamStructure(fare_authority_filter=filters,traveller=ojptravellers,passenger_category=[PassengerCategoryEnumeration.ADULT],travel_class =FareClassEnumeration.SECOND_CLASS) +def non_negative_int(value: str) -> int: + """argparse type function: ensures the provided value is a non-negative integer.""" + try: + ivalue = int(value) + except ValueError: + raise argparse.ArgumentTypeError(f"invalid int value: {value!r}") + if ivalue < 0: + raise argparse.ArgumentTypeError(f"value must be non-negative: {value}") + return ivalue + +def parse_args(argv=None): + parser = argparse.ArgumentParser(description="Run network flow tests.") + # allow either --all or --id, or neither. If both provided prefer --id. + parser.add_argument("--all", action="store_true", help="Run all tests") + parser.add_argument("--id", type=non_negative_int, metavar="N", help="Run the test with the given id (non-negative integer)") + return parser.parse_args(argv) + +def main(argv=None) ->int: + args = parse_args(argv) + + # Priority: --id if provided, else --all, else default_action() + if args.id is not None: + processing="id"+str(args.id)+"id" + elif args.all: + processing="all" + else: + processing="active" -if __name__ == '__main__': #check configuration ojp_trip_request_xml='' check_configuration() @@ -248,37 +275,59 @@ def build_ojp_fare_params(travellers, subscriptions, relationship) -> FareParamS if not isinstance(test_run_dict, list): raise ValueError("Expected JSON top-level to be a list of test runs") - # Loop through active elements + # Loop through the elements for idx, element in enumerate(test_run_dict): # Skip non-dict entries if not isinstance(element, dict): continue active = element.get("active", False) - if active: + if "all" in processing or "id"+str(element.get("id"))+"id" in processing or (active and "active" in processing): + id= element.get("id") file_name = element.get("file") travellers = element.get("travellers", []) subscriptions = element.get("subscriptions") relationship = element.get("relationship") + start_time = element.get("start_time") + daysinthefuture = element.get("future") + expectedstatus=element.get("status") + asserttext=element.get("assert") + + print( f"\n**************************************************************************************\n") print(f"Active element #{idx}: file={file_name}") - print(f" travellers: {len(travellers)}") - print(f" subscriptions: {subscriptions}") + print(f" id: {id}") print(f" relationship: {relationship}") + print(f" subscriptions: {subscriptions}") + print(f" travellers: {travellers}") + if start_time: + print(f" start_time: {start_time}") + if daysinthefuture: + print(f" number of days in the future: {daysinthefuture}") + print(f" expected status: {expectedstatus}") + if asserttext: + print(f" assertion that the following string is in the response (usually a price): {asserttext}") + + inputfile = open(file_name, 'r', encoding='utf-8') ojp_trip_request_xml = inputfile.read() inputfile.close() + #inject date if necessary + if daysinthefuture is not None or start_time is not None: + ojp_trip_request_xml=inject_departure_datetime(ojp_trip_request_xml,daysinthefuture,start_time) xml_logger.log_serialized('ojp_trip_request.xml', ojp_trip_request_xml) try: if is_version_2_0(ojp_trip_request_xml): + print(f" OJP version: 2.0") # We process an OJP 2 request status, r = call_ojp_20(ojp_trip_request_xml) if status != 200: message = f"call returned a wrong status {status}" raise IOError(message) ojp_trip_result = parse_ojp2(r) + ojp_trip_result_xml = serializer.render(ojp_trip_result, ns_map=ns_map) xml_logger.log_serialized('ojp_trip_reply.xml', ojp_trip_result_xml) ojp_fare_params = build_ojp2_fare_params(travellers, subscriptions, relationship) @@ -302,8 +351,12 @@ def build_ojp_fare_params(travellers, subscriptions, relationship) -> FareParamS print(fr.fare_product) print("\n") xml_logger.log_serialized('ojp_fare_result.xml', ojp_fare_result_xml) + if asserttext: + if not (asserttext in ojp_fare_result_xml): + print(f"Assertion from test case failed! {asserttext} not found.") else: # We work on a OJP 1.0 request + print(f" OJP version: 1.0") status, r = call_ojp_2000(ojp_trip_request_xml) if status != 200: message = f"call returned a wrong status {status}:\n{r}" @@ -331,6 +384,10 @@ def build_ojp_fare_params(travellers, subscriptions, relationship) -> FareParamS print(fr1.fare_product) print("\n") xml_logger.log_serialized('ojp_fare_result.xml', ojp_fare_result_xml1) + if asserttext: + if not (asserttext in ojp_fare_result_xml1): + print(f"Assertion from test casefailed! {asserttext} not found.") + @@ -341,3 +398,6 @@ def build_ojp_fare_params(travellers, subscriptions, relationship) -> FareParamS traceback.print_exc() +if __name__ == '__main__': + exit_code = main() + sys.exit(exit_code) \ No newline at end of file From 69c239f1bee1d535c6a8a11571c4aed9b62c2a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Mon, 9 Mar 2026 15:15:07 +0100 Subject: [PATCH 15/16] Delete test_configuration.py --- test_configuration.py | 61 ------------------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 test_configuration.py diff --git a/test_configuration.py b/test_configuration.py deleted file mode 100644 index cda25b2..0000000 --- a/test_configuration.py +++ /dev/null @@ -1,61 +0,0 @@ -READFILE = [] - - -READFILE.append("input/input_test_dornbir.xml") - -READFILE.append("input/input_oev_shart_plus_long.xml") -READFILE.append("input/input_Zuerich_Chur.xml") -READFILE.append("input/input_Visp_SaaS_Fee_problem_1_preis.xml") #1. class price problematic -READFILE.append("input/input_strange_price.xml") -READFILE.append("input/input_Zuerich_Bern.xml") -READFILE.append("input/input_Basel_Sargans.xml") -READFILE.append("input/input_Bern_Interlaken_Ost.xml") -READFILE.append("input/input_Bern_Interlaken_Gymnasium.xml") -READFILE.append("input/input_Bern_Guisanplatz_Interlaken_Gymnasium.xml") -READFILE.append("input/input_local.xml") -READFILE.append("input/input_oev_shart_plus_long.xml") -READFILE.append("input/input_bus_postauto.xml") -READFILE.append("input/input_sharing_intercity.xml") -READFILE.append("input/input_problematic_case_vasile.xml") -READFILE.append("input/input_Europaplatz.xml") -READFILE.append("input/input_Bodensee_1.xml") -READFILE.append("input/input_test_europaplatz.xml") - - -# Working OJP 2.0 example -#READFILE.append("input/input_ojp_1_test.xml") -READFILE.append("input/input_ojp_2_test.xml") -READFILE.append("input/input_problematic_Europaplatz_ojp1.xml") -READFILE.append("input/input_problematic_Europaplatz_ojp2.xml") -READFILE.append("input/input_Bodensee_2.xml") -READFILE.append("input/input_problem_footpath.xml") -READFILE.append("input/input_problematic_Europaplatz_4.xml") - - - -# No result, but no result is ok -READFILE.append("input/input_walk_only.xml") -READFILE.append("input/input_sharing_only.xml") -READFILE.append("input/input_odv_alone.xml") -READFILE.append("input/input_in_the_past_not_handeled_well_in_Preisauskunft.xml") -READFILE.append("input/input_Bodensee.xml") # no data available there -READFILE.append("input/input_problematic_Europaplatz_ojp2_via.xml") # no longer ok as construction finished - - -# Contains DepArr that needs to be set to something useful before testing -READFILE.append("input/input_Bern_Chur_SOB_Zukunft.xml" ) #time must be reset before run. Check if discounts exist on www.sbb.ch -READFILE.append("input/input_Bern_Zweisimmen_BLS_Zukunft.xml") #time must be reset before run. Check if discounts exist on www.sbb.ch -READFILE.append("input/input_bern_riehen.xml") #time must be reset before run. Check if discounts exist on www.sbb.ch -READFILE.append("input/input_aller_retour.xml") -READFILE.append("input/input_Nachtbus.xml") - -# need analysis -#not working on Saturdays. Handling of ODV not working -READFILE.append("input/input_demand_responsive_saturday_after_1500.xml") - -#sometimes not working -READFILE.append("input/input_sharing_intercity.xml") - -''' - - From 58b23faa7e7acc30f977d24b32f8264702059309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Tue, 26 May 2026 10:48:00 +0200 Subject: [PATCH 16/16] =?UTF-8?q?tests=20f=C3=BCr=20BLS=20und=20Anpassung?= =?UTF-8?q?=20351=20L7=5F=5F=5F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- input/input_bern_riehen.xml | 22 +++++++++----------- input/input_bern_riehen_2.xml | 39 +++++++++++++++++++++++++++++++++++ input/input_menusio.xml | 2 +- support.py | 2 ++ test_configuration.json | 17 +++++++++++++++ 5 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 input/input_bern_riehen_2.xml diff --git a/input/input_bern_riehen.xml b/input/input_bern_riehen.xml index b0d1cf3..f8ab045 100644 --- a/input/input_bern_riehen.xml +++ b/input/input_bern_riehen.xml @@ -1,38 +1,36 @@ - de - 2025-10-14T13:58:07.719Z + 2026-04-30T12:17:04.217Z OJP_DemoApp_Beta_OJP2.0 - 2025-10-14T13:58:07.719Z + 2026-04-30T12:17:04.217Z - 8507000 + 8590011 - Bern (Bern) + n/a - 8014439 + 8589451 - Riehen (Riehen) + n/a 5 - true - false - false - false - false explanatory + true + true + true + true diff --git a/input/input_bern_riehen_2.xml b/input/input_bern_riehen_2.xml new file mode 100644 index 0000000..a5a496a --- /dev/null +++ b/input/input_bern_riehen_2.xml @@ -0,0 +1,39 @@ + + + + + de + + 2026-04-30T12:17:04.217Z + OJP_DemoApp_Beta_OJP2.0 + + 2026-04-30T12:17:04.217Z + + + 8590011 + + n/a + + + 2026-04-30T12:16:00.000Z + + + + 8589451 + + n/a + + + + + 5 + explanatory + true + true + true + true + + + + + \ No newline at end of file diff --git a/input/input_menusio.xml b/input/input_menusio.xml index 469b185..8c4d2ca 100644 --- a/input/input_menusio.xml +++ b/input/input_menusio.xml @@ -15,7 +15,7 @@ n/a - 2026-03-10T21:00:00.000Z + 2026-03-24T21:00:00.000Z diff --git a/support.py b/support.py index 1394879..48067d8 100644 --- a/support.py +++ b/support.py @@ -27,6 +27,8 @@ def error_response(error_text:str) -> Ojp: def process_operating_ref_ojp2(operator_ref:OperatorRef) ->str: operator_ref_str=operator_ref.value +# if operator_ref_str == "L7____": +# operator_ref_str="351" return process_operating_ref(operator_ref_str) def process_operating_ref(operator_ref:str) ->str: diff --git a/test_configuration.json b/test_configuration.json index 0c704ca..801501c 100644 --- a/test_configuration.json +++ b/test_configuration.json @@ -713,5 +713,22 @@ "start_time": "23:55:00", "relationship": "KEINE_REISENDENBEZIEHUNG", "active": true + }, + { + "id": 43, + "description": "More experiments ", + "file": "input/input_bern_riehen_2.xml", + "travellers": [ + { + "age": 25, + "entitlements": "HTA", + "typ": "PERSON", + "tkid": "123312312" + } + ], + "subscriptions": false, + "start_time": "16:55:00", + "relationship": "KEINE_REISENDENBEZIEHUNG", + "active": true } ] \ No newline at end of file