Spaces:
Running on Zero
Running on Zero
| """Open-now awareness: OSM opening_hours parsing + plan-time demotion.""" | |
| from __future__ import annotations | |
| from datetime import datetime | |
| from discoverroute.routing.hours import (PARIS_TZ, apply_open_now, is_open) | |
| TUE_15H = datetime(2026, 6, 9, 15, 0, tzinfo=PARIS_TZ) # Tuesday 15:00 | |
| TUE_23H = datetime(2026, 6, 9, 23, 30, tzinfo=PARIS_TZ) # Tuesday 23:30 | |
| MON_15H = datetime(2026, 6, 8, 15, 0, tzinfo=PARIS_TZ) # Monday 15:00 | |
| WED_01H = datetime(2026, 6, 10, 1, 0, tzinfo=PARIS_TZ) # Wednesday 01:00 | |
| def test_always_open(): | |
| assert is_open("24/7", TUE_15H) is True | |
| def test_simple_weekday_range(): | |
| assert is_open("Mo-Fr 08:00-18:00", TUE_15H) is True | |
| assert is_open("Mo-Fr 08:00-18:00", TUE_23H) is False | |
| def test_multi_rule_with_off_day(): | |
| spec = "Tu-Su 10:00-18:00; Mo off" | |
| assert is_open(spec, TUE_15H) is True | |
| assert is_open(spec, MON_15H) is False | |
| def test_lunch_split_spans(): | |
| spec = "Mo-Fr 09:00-12:00,14:00-18:00" | |
| assert is_open(spec, TUE_15H) is True | |
| assert is_open(spec, datetime(2026, 6, 9, 13, 0, tzinfo=PARIS_TZ)) is False | |
| def test_daily_no_days(): | |
| assert is_open("07:00-23:30", TUE_15H) is True | |
| def test_overnight_span(): | |
| spec = "Tu 18:00-02:00" | |
| assert is_open(spec, TUE_23H) is True | |
| assert is_open(spec, WED_01H) is True # spills past midnight into Wednesday | |
| assert is_open(spec, TUE_15H) is False | |
| def test_abstains_on_complex(): | |
| assert is_open("sunrise-sunset", TUE_15H) is None | |
| assert is_open("Mar-Oct 08:00-20:00", TUE_15H) is None | |
| assert is_open(None, TUE_15H) is None | |
| assert is_open("", TUE_15H) is None | |
| class P: | |
| def __init__(self, category, hours, score=1.0): | |
| self.category, self.opening_hours, self.score = category, hours, score | |
| def test_demotion_by_posture(): | |
| closed_cafe = P("cafe", "Mo-Fr 08:00-12:00") # closed Tue 15:00 | |
| closed_monument = P("monument_historic", "Mo-Fr 08:00-12:00") | |
| open_cafe = P("cafe", "Mo-Fr 08:00-18:00") | |
| unknown = P("cafe", None) | |
| posture = {"cafe": "stop", "monument_historic": "pass"} | |
| apply_open_now([closed_cafe, closed_monument, open_cafe, unknown], | |
| posture, when=TUE_15H) | |
| assert closed_cafe.score == 0.2 # stop-at closed -> heavy demotion | |
| assert closed_monument.score == 0.7 # pass-by closed -> mild demotion | |
| assert open_cafe.score == 1.0 and open_cafe.open_state is True | |
| assert unknown.score == 1.0 and unknown.open_state is None | |
| def test_holiday_rules_dont_block_weekday_decisions(): | |
| # "PH off" must not force abstention on a decidable weekday | |
| assert is_open("Mo-Fr 08:00-18:00; PH off", TUE_15H) is True | |
| assert is_open("Mo-Fr 08:00-18:00; PH off", TUE_23H) is False | |
| # PH inside a day list extends to holidays; weekdays still decidable | |
| assert is_open("PH,Sa,Su 10:00-18:00; Mo-Fr 08:30-17:00", TUE_15H) is True | |
| def test_night_demotes_unknown_daytime_categories(): | |
| night = datetime(2026, 6, 9, 23, 30, tzinfo=PARIS_TZ) | |
| day = datetime(2026, 6, 9, 15, 0, tzinfo=PARIS_TZ) | |
| cafe_n, bar_n = P("cafe", None), P("bar_pub", None) | |
| apply_open_now([cafe_n, bar_n], {}, when=night) | |
| assert cafe_n.score == 0.5 # unknown café at 23:30 -> poor bet | |
| assert bar_n.score == 1.0 # unknown bar at night -> plausible | |
| cafe_d = P("cafe", None) | |
| apply_open_now([cafe_d], {}, when=day) | |
| assert cafe_d.score == 1.0 # daytime unknown untouched | |