Beispiel #1
0
def query_which_route(query, session, result):
    """ Which routes stop at a given bus stop """
    stop_name = result.stop_name  # 'Einarsnes', 'Fiskislóð'...

    if stop_name in {"þar", "þangað"}:
        # Referring to a bus stop mentioned earlier
        ctx = query.fetch_context()
        if ctx and "bus_stop" in ctx:
            stop_name = ctx["bus_stop"]
            result.qkey = stop_name
        else:
            answer = voice_answer = "Ég veit ekki við hvaða stað þú átt."
            response = dict(answer=answer)
            return response, answer, voice_answer

    bus_noun = result.bus_noun  # 'strætó', 'vagn', 'leið'...
    stops = straeto.BusStop.named(stop_name, fuzzy=True)
    if not stops:
        a = [stop_name, "þekkist ekki."]
        va = [
            "Ég", "þekki", "ekki", "biðstöðina", stop_name.capitalize(),
        ]
    else:
        routes = set()
        if query.location:
            straeto.BusStop.sort_by_proximity(stops, query.location)
        stop = stops[0]
        for route_id in stop.visits.keys():
            number = straeto.BusRoute.lookup(route_id).number
            routes.add(number)
        va = [bus_noun, "númer"]
        a = va[:]
        nroutes = len(routes)
        cnt = 0
        for rn in sorted(routes, key=lambda t: int(t)):
            if cnt:
                sep = "og" if cnt + 1 == nroutes else ","
                va.append(sep)
                a.append(sep)
            # We convert inflectable numbers to their text equivalents
            # since the speech engine can't be relied upon to get the
            # inflection of numbers right
            va.append(numbers_to_neutral(rn))
            a.append(rn)
            cnt += 1
        tail = ["stoppar á", to_dative(stop.name)]
        va.extend(tail)
        a.extend(tail)
        # Store a location coordinate and a bus stop name in the context
        query.set_context({"location": stop.location, "bus_stop": stop.name})

    voice_answer = correct_spaces(" ".join(va) + ".")
    answer = correct_spaces(" ".join(a))
    answer = answer[0].upper() + answer[1:]
    response = dict(answer=answer)
    return response, answer, voice_answer
Beispiel #2
0
def query_arrival_time(query: Query, session: Session, result: Result):
    """ Answers a query for the arrival time of a bus """

    # Examples:
    # 'Hvenær kemur strætó númer 12?'
    # 'Hvenær kemur leið sautján á Hlemm?'
    # 'Hvenær kemur næsti strætó í Einarsnes?'

    # Retrieve the client location, if available, and the name
    # of the bus stop, if given
    stop_name: Optional[str] = result.get("stop_name")
    stop: Optional[straeto.BusStop] = None
    location: Optional[Tuple[float, float]] = None

    if stop_name in {"þar", "þangað"}:
        # Referring to a bus stop mentioned earlier
        ctx = query.fetch_context()
        if ctx and "bus_stop" in ctx:
            stop_name = cast(str, ctx["bus_stop"])
        else:
            answer = voice_answer = "Ég veit ekki við hvaða stað þú átt."
            response = dict(answer=answer)
            return response, answer, voice_answer

    if not stop_name:
        location = query.location
        if location is None:
            answer = "Staðsetning óþekkt"
            response = dict(answer=answer)
            voice_answer = "Ég veit ekki hvar þú ert."
            return response, answer, voice_answer

    # Obtain today's bus schedule
    global SCHEDULE_TODAY
    with SCHEDULE_LOCK:
        if SCHEDULE_TODAY is None or not SCHEDULE_TODAY.is_valid_today:
            # We don't have today's schedule: create it
            SCHEDULE_TODAY = straeto.BusSchedule()

    # Obtain the set of stops that the user may be referring to
    stops: List[straeto.BusStop] = []
    if stop_name:
        stops = straeto.BusStop.named(stop_name, fuzzy=True)
        if query.location is not None:
            # If we know the location of the client, sort the
            # list of potential stops by proximity to the client
            straeto.BusStop.sort_by_proximity(stops, query.location)
    else:
        # Obtain the closest stops (at least within 400 meters radius)
        assert location is not None
        stops = cast(
            List[straeto.BusStop],
            straeto.BusStop.closest_to_list(location, n=2, within_radius=0.4),
        )
        if not stops:
            # This will fetch the single closest stop, regardless of distance
            stops = [
                cast(straeto.BusStop, straeto.BusStop.closest_to(location))
            ]

    # Handle the case where no bus number was specified (i.e. is 'Any')
    if result.bus_number == "Any" and stops:
        stop = stops[0]
        routes = sorted(
            (straeto.BusRoute.lookup(rid).number
             for rid in stop.visits.keys()),
            key=lambda r: int(r),
        )
        if len(routes) != 1:
            # More than one route possible: ask user to clarify
            route_seq = natlang_seq(list(map(str, routes)))
            answer = (" ".join(
                ["Leiðir", route_seq, "stoppa á",
                 to_dative(stop.name)]) + ". Spurðu um eina þeirra.")
            voice_answer = (" ".join([
                "Leiðir",
                numbers_to_neutral(route_seq),
                "stoppa á",
                to_dative(stop.name),
            ]) + ". Spurðu um eina þeirra.")
            response = dict(answer=answer)
            return response, answer, voice_answer
        # Only one route: use it as the query subject
        bus_number = routes[0]
        bus_name = "strætó númer {0}".format(bus_number)
    else:
        bus_number = result.bus_number if "bus_number" in result else 0
        bus_name = result.bus_name if "bus_name" in result else "Óþekkt"

    # Prepare results
    bus_name = cap_first(bus_name)
    va = [bus_name]
    a = []
    arrivals = []
    arrivals_dict = {}
    arrives = False
    route_number = str(bus_number)

    # First, check the closest stop
    # !!! TODO: Prepare a different area_priority parameter depending
    # !!! on the user's location; i.e. if she is in Eastern Iceland,
    # !!! route '1' would mean 'AL.1' instead of 'ST.1'.
    if stops:
        for stop in stops:
            arrivals_dict, arrives = SCHEDULE_TODAY.arrivals(
                route_number, stop)
            if arrives:
                break
        arrivals = list(arrivals_dict.items())
        a = ["Á", to_accusative(stop.name), "í átt að"]

    if arrivals:
        # Get a predicted arrival time for each direction from the
        # real-time bus location server
        prediction = SCHEDULE_TODAY.predicted_arrival(route_number, stop)
        now = datetime.utcnow()
        hms_now = (now.hour, now.minute + (now.second // 30), 0)
        first = True

        # We may get three (or more) arrivals if there are more than two
        # endpoints for the bus route in the schedule. To minimize
        # confusion, we only include the two endpoints that have the
        # earliest arrival times and skip any additional ones.
        arrivals = sorted(arrivals, key=lambda t: t[1][0])[:2]

        for direction, times in arrivals:
            if not first:
                va.append(", og")
                a.append(". Í átt að")
            va.extend(["í átt að", to_dative(direction)])
            a.append(to_dative(direction))
            deviation = []
            if prediction and direction in prediction:
                # We have a predicted arrival time
                hms_sched = times[0]
                hms_pred = prediction[direction][0]
                # Calculate the difference between the prediction and
                # now, and skip it if it is 1 minute or less
                diff = hms_diff(hms_pred, hms_now)
                if abs(diff) <= 1:
                    deviation = [", en er að fara núna"]
                else:
                    # Calculate the difference in minutes between the
                    # schedule and the prediction, with a positive number
                    # indicating a delay
                    diff = hms_diff(hms_pred, hms_sched)
                    if diff < -1:
                        # More than one minute ahead of schedule
                        if diff < -5:
                            # More than 5 minutes ahead
                            deviation = [
                                ", en kemur sennilega fyrr, eða",
                                hms_fmt(hms_pred),
                            ]
                        else:
                            # Two to five minutes ahead
                            deviation = [
                                ", en er",
                                str(-diff),
                                "mínútum á undan áætlun",
                            ]
                    elif diff >= 3:
                        # 3 minutes or more behind schedule
                        deviation = [
                            ", en kemur sennilega ekki fyrr en",
                            hms_fmt(hms_pred),
                        ]
            if first:
                assert stop is not None
                if deviation:
                    va.extend(["á að koma á", to_accusative(stop.name)])
                else:
                    va.extend(["kemur á", to_accusative(stop.name)])
            va.append("klukkan")
            a.append("klukkan")
            if len(times) == 1 or (len(times) > 1
                                   and hms_diff(times[0], hms_now) >= 10):
                # Either we have only one arrival time, or the next arrival is
                # at least 10 minutes away: only pronounce one time
                hms = times[0]
                time_text = hms_fmt(hms)
            else:
                # Return two or more times
                time_text = " og ".join(hms_fmt(hms) for hms in times)
            va.append(time_text)
            a.append(time_text)
            va.extend(deviation)
            a.extend(deviation)
            first = False

    elif arrives:
        # The given bus has already completed its scheduled halts at this stop today
        assert stops
        stop = stops[0]
        reply = ["kemur ekki aftur á", to_accusative(stop.name), "í dag"]
        va.extend(reply)
        a = [bus_name] + reply

    elif stops:
        # The given bus doesn't stop at all at either of the two closest stops
        stop = stops[0]
        va.extend(["stoppar ekki á", to_dative(stop.name)])
        a = [bus_name, "stoppar ekki á", to_dative(stop.name)]

    else:
        # The bus stop name is not recognized
        va = a = [stop_name.capitalize(), "er ekki biðstöð"]

    if stop is not None:
        # Store a location coordinate and a bus stop name in the context
        query.set_context({"location": stop.location, "bus_stop": stop.name})

    # Hack: Since we know that the query string contains no uppercase words,
    # adjust it accordingly; otherwise it may erroneously contain capitalized
    # words such as Vagn and Leið.
    bq = query.beautified_query
    for t in (
        ("Vagn ", "vagn "),
        ("Vagni ", "vagni "),
        ("Vagns ", "vagns "),
        ("Leið ", "leið "),
        ("Leiðar ", "leiðar "),
    ):
        bq = bq.replace(*t)
    query.set_beautified_query(bq)

    def assemble(x):
        """ Intelligently join answer string components. """
        return (" ".join(x) + ".").replace(" .", ".").replace(" ,", ",")

    voice_answer = assemble(va)
    answer = assemble(a)
    response = dict(answer=answer)
    return response, answer, voice_answer