Example #1
0
def handle_plain_text(q):
    """ Handle a plain text query, contained in the q parameter
        which is an instance of the query.Query class.
        Returns True if the query was handled, and in that case
        the appropriate properties on the Query instance have
        been set, such as the answer and the query type (qtype).
        If the query is not recognized, returns False. """
    ql = q.query_lower.rstrip("?")

    # Timezone being asked about
    tz = None
    # Whether user asked for the time in a particular location
    specific_desc = None

    if ql in _TIME_QUERIES:
        # Use location to determine time zone
        tz = timezone4loc(q.location, fallback="IS")
    elif ql.startswith("hvað er klukkan á ") or ql.startswith(
            "hvað er klukkan í "):
        # Query about the time in a particular location, i.e. country or city
        loc = ql[18:]  # Cut away question prefix, leaving only placename
        # Capitalize each word in country/city name
        loc = capitalize_placename(loc)
        # Look up nominative
        # This only works for single-word city/country names found
        # in BÍN and could be improved (e.g. fails for "Nýju Jórvík")
        bin_res = BIN_Db().lookup_nominative(loc)
        words = [m.stofn for m in bin_res]
        words.append(
            loc)  # In case it's not in BÍN (e.g. "New York", "San José")

        # Check if any word is a recognised country or city name
        for w in words:
            cc = isocode_for_country_name(w)
            if cc and cc in country_timezones:
                # Look up country timezone
                # Use the first timezone although some countries have more than one
                # The timezone list returned by pytz is ordered by "dominance"
                tz = country_timezones[cc][0]
            else:
                # It's not a country name, look up in city database
                info = lookup_city_info(w)
                if info:
                    top = info[0]
                    location = (top.get("lat_wgs84"), top.get("long_wgs84"))
                    tz = timezone4loc(location)
            if tz:
                # We have a timezone
                break

        # "Klukkan í Lundúnum er" - Used for voice answer
        specific_desc = "{0} er".format(ql[8:])

        # Beautify query by capitalizing the country/city name
        q.set_beautified_query("{0}{1}?".format(q.beautified_query[:18], loc))

    # We have a timezone. Return formatted answer.
    if tz:
        now = datetime.now(timezone(tz))

        desc = specific_desc or "Klukkan er"

        # Create displayable answer
        answer = "{0:02}:{1:02}".format(now.hour, now.minute)
        # A detailed response object is usually a list or a dict
        response = dict(answer=answer)
        # A voice answer is a plain string that will be
        # passed as-is to a voice synthesizer
        voice = "{0} {1}:{2:02}.".format(desc, now.hour, now.minute)

        q.set_qtype(_TIME_QTYPE)
        q.set_key(tz)  # Query key is the timezone
        q.set_answer(response, answer, voice)
        return True

    return False
Example #2
0
def QGeoSubject(node, params, result):
    n = capitalize_placename(result._text)
    bin_res = BIN_Db().lookup_nominative(n)
    res = bin_res[0].stofn if bin_res else n
    result.subject = res
Example #3
0
def test_geo():
    """ Test geography and location-related functions in geo.py """
    from geo import (
        icelandic_city_name,
        continent_for_country,
        coords_for_country,
        coords_for_street_name,
        country_name_for_isocode,
        isocode_for_country_name,
        icelandic_addr_info,
        lookup_city_info,
        parse_address_string,
        iceprep_for_street,
        iceprep_for_placename,
        iceprep_for_country,
        iceprep_for_cc,
        capitalize_placename,
        distance,
        in_iceland,
        code_for_us_state,
        coords_for_us_state_code,
        location_info,
    )

    assert icelandic_city_name("London") == "Lundúnir"
    assert icelandic_city_name("Rome") == "Róm"

    assert continent_for_country("IS") == "EU"
    assert continent_for_country("no") == "EU"
    assert continent_for_country("MX") == "NA"

    assert coords_for_country("DE") is not None
    assert coords_for_country("it") is not None

    assert coords_for_street_name("Austurstræti") is not None
    assert coords_for_street_name("Háaleitisbraut") is not None

    assert country_name_for_isocode("DE", lang="is") == "Þýskaland"
    assert country_name_for_isocode("DE") == "Þýskaland"

    assert isocode_for_country_name("Danmörk", lang="is") == "DK"
    assert isocode_for_country_name("Danmörk", lang="IS") == "DK"
    assert isocode_for_country_name("Noregur") == "NO"

    addr_info = icelandic_addr_info("Fiskislóð 31")
    assert addr_info and addr_info["stadur_tgf"] == "Reykjavík"

    # Test city info lookup
    city_info = lookup_city_info("Kænugarður")
    assert city_info and len(
        city_info) == 1 and city_info[0]["country"] == "UA"

    city_info = lookup_city_info("Kaupmannahöfn")
    assert city_info and len(
        city_info) == 1 and city_info[0]["country"] == "DK"

    city_info = lookup_city_info("Pjongjang")
    assert city_info and len(
        city_info) == 1 and city_info[0]["country"] == "KP"

    city_info = lookup_city_info("Pyongyang")
    assert city_info and len(
        city_info) == 1 and city_info[0]["country"] == "KP"

    # Test address string parsing
    assert parse_address_string("   Fiskislóð  31") == {
        "street": "Fiskislóð",
        "number": 31,
        "letter": "",
    }
    assert parse_address_string("Öldugata 19c ") == {
        "street": "Öldugata",
        "number": 19,
        "letter": "c",
    }
    assert parse_address_string("    Dúfnahólar   10   ") == {
        "street": "Dúfnahólar",
        "number": 10,
        "letter": "",
    }

    # Test prepositions for street names
    assert iceprep_for_street("Öldugata") == "á"
    assert iceprep_for_street("Fiskislóð") == "á"
    assert iceprep_for_street("Austurstræti") == "í"
    assert iceprep_for_street("Hamrahlíð") == "í"

    # Test prepositions for placenames
    assert iceprep_for_placename("Dalvík") == "á"
    assert iceprep_for_placename("Akureyri") == "á"
    assert iceprep_for_placename("Ísafjörður") == "á"
    assert iceprep_for_placename("Reykjavík") == "í"
    assert iceprep_for_placename("Hafnarfjörður") == "í"
    assert iceprep_for_placename("London") == "í"
    assert iceprep_for_placename("Dyflinni") == "í"

    # Test prepositions for countries
    assert iceprep_for_country("Ítalía") == "á"
    assert iceprep_for_country("Ísland") == "á"
    assert iceprep_for_country("Þýskaland") == "í"
    assert iceprep_for_country("Japan") == "í"
    assert iceprep_for_country("spánn") == "á"

    # Test prepositions for countries, queried by CC
    assert iceprep_for_cc("IS") == "á"
    assert iceprep_for_cc("US") == "í"
    assert iceprep_for_cc("ES") == "á"
    assert iceprep_for_cc("es") == "á"

    # Test placename capitalization
    assert capitalize_placename("ríó de janeiro") == "Ríó de Janeiro"
    assert capitalize_placename("vík í mýrdal") == "Vík í Mýrdal"
    assert capitalize_placename("Vík í mýrdal") == "Vík í Mýrdal"
    assert capitalize_placename("frankfúrt am main") == "Frankfúrt am Main"
    assert capitalize_placename("mið-afríkulýðveldið") == "Mið-Afríkulýðveldið"
    assert capitalize_placename("Norður-kórea") == "Norður-Kórea"
    assert capitalize_placename("norður-Kórea") == "Norður-Kórea"
    assert capitalize_placename(
        "bosnía og hersegóvína") == "Bosnía og Hersegóvína"
    assert capitalize_placename("Norður-Makedónía") == "Norður-Makedónía"

    # Distance
    assert int(distance((64.141439, -21.943944),
                        (65.688131, -18.102528))) == 249
    assert in_iceland((66.462205, -15.968417))
    assert not in_iceland((62.010846, -6.776709))
    assert not in_iceland((62.031342, -18.539553))

    # US States
    assert code_for_us_state("Flórída") == "FL"
    assert code_for_us_state("Norður-Karólína") == "NC"
    assert code_for_us_state("Kalifornía") == "CA"
    assert coords_for_us_state_code("CA") == [36.778261, -119.417932]

    # Generic location info lookup functions
    assert "country" in location_info("Reykjavík", "placename")
    assert "continent" in location_info("Minsk", "placename")
    assert location_info("Japan", "country")["continent"] == "AS"
    assert location_info("Danmörk", "country")["continent"] == "EU"
    assert location_info("Mexíkó", "country")["continent"] == "NA"
    assert location_info("ísafjörður", "placename")["continent"] == "EU"
    assert location_info("Meðalfellsvatn", "placename")["country"] == "IS"
    assert location_info("Georgía", "country")["country"] != "US"
    assert location_info("Virginía", "placename")["country"] == "US"
    assert location_info("Norður-Dakóta", "country")["country"] == "US"
    assert location_info("Kænugarður", "placename")["continent"] == "EU"
    assert location_info("Fiskislóð 31", "address")["country"] == "IS"
Example #4
0
def dist_answer_for_loc(matches, query: Query):
    """Generate response to distance query, e.g.
    "Hvað er ég langt frá X?" """
    locname = matches.group(1)
    loc_nf = _addr2nom(locname) or locname

    # Try to avoid answering certain queries here
    loc_lower = locname.lower()
    # TODO: Solve this by configuring qmodule priority
    if any(s in loc_lower for s in (
            "strætó",
            "stoppistöð",
            "strætisvagn",
            "biðstöð",
            "stoppustöð",
            "stræto",
            "strædo",
            "jólin",
            "jól",
            "páska",
    )):
        return None

    # Check if user is asking about distance from home address
    is_home = False
    loc: Tuple[float, float]
    if loc_lower in _HOME_LOC:
        ad = query.client_data("address")
        if not ad:
            return gen_answer(
                "Ég veit ekki hvar þú átt heima, en þú getur sagt mér það.")
        elif "lat" not in ad or "lon" not in ad:
            return gen_answer("Ég veit ekki hvar heimili þitt er.")
        else:
            is_home = True
            loc = (cast(float, ad["lat"]), cast(float, ad["lon"]))
            loc_nf = "{0} {1}".format(ad["street"], ad["number"])
    else:
        # Talk to geocode API
        res = query_geocode_api_addr(loc_nf)

        # Verify sanity of API response
        if (not res or "status" not in res or res["status"] != "OK"
                or not res.get("results")):
            return None

        # Extract location coordinates from result
        coords = res["results"][0]["geometry"]["location"]
        loc = (coords["lat"], coords["lng"])

    # Calculate distance, round it intelligently and format num string
    if query.location is None:
        km_dist = 0.0
    else:
        km_dist = distance(query.location, loc)

    # Generate answer
    answer = distance_desc(km_dist, abbr=True)
    response = dict(answer=answer, distance=km_dist)

    loc_nf = capitalize_placename(loc_nf)
    dist = distance_desc(km_dist, case="þf")
    voice = "{0} er {1} í burtu".format(numbers_to_neutral(loc_nf), dist)

    query.set_key(loc_nf)

    # Beautify by capitalizing remote loc name
    uc = locname if is_home else capitalize_placename(locname)
    bq = query.beautified_query.replace(locname, uc)

    # Hack to fix the fact that the voice recognition often misses "ég"
    prefix_fix = "Hvað er langt frá"
    if bq.startswith(prefix_fix):
        bq = bq.replace(prefix_fix, "Hvað er ég langt frá")
    query.set_beautified_query(bq)

    query.set_context(dict(subject=loc_nf))

    return response, answer, voice
Example #5
0
def _process_result(result: Result) -> Dict[str, str]:
    """
    Return formatted description of arrival/departure
    time of flights to or from an Icelandic airport,
    based on info in result dict.
    """
    airport: str  # Icelandic or foreign airport/country
    api_airport: str  # Always an Icelandic airport, as the ISAVIA API only covers them

    departing: bool = result["departure"]
    if departing:
        # Departures (from Keflavík by default)
        api_airport = result.get("from_loc", "keflavík").lower()
        # Wildcard matches any flight (if airport wasn't specified)
        airport = result.get("to_loc", "*").lower()
    else:
        # Arrivals (to Keflavík by default)
        api_airport = result.get("to_loc", "keflavík").lower()
        airport = result.get("from_loc", "*").lower()

    from_date: datetime
    to_date: datetime
    days: int = result.get("day_count", 5)  # Check 5 days into future by default
    from_date = result.get("from_date", datetime.now(timezone.utc))
    to_date = result.get("to_date", datetime.now(timezone.utc) + timedelta(days=days))

    # Normalize airport/city names
    airport = _LOCATION_ABBREV_MAP.get(airport, airport)
    airport = NounPhrase(airport).nominative or airport

    api_airport = _LOCATION_ABBREV_MAP.get(api_airport, api_airport)
    api_airport = NounPhrase(api_airport).nominative or api_airport

    # Translate Icelandic airport to its IATA code
    iata_code: str = _AIRPORT_TO_IATA_MAP.get(api_airport, api_airport)

    # TODO: Currently module only fetches one flight,
    # modifications to the grammar could allow fetching of more flights at once
    flight_count: int = result.get("flight_count", 1)

    flight_data: FlightList
    # Check first if function result in cache, else fetch data from API
    if departing in _FLIGHT_CACHE:
        flight_data = _FLIGHT_CACHE[departing]
    else:
        flight_data = _fetch_flight_data(from_date, to_date, iata_code, departing)

    flight_data = _filter_flight_data(flight_data, airport, api_airport, flight_count)

    answ: Dict[str, str] = dict()
    if len(flight_data) > 0:
        # (Format month names in Icelandic)
        with changedlocale(category="LC_TIME"):
            answ = _format_flight_answer(flight_data)
    else:
        to_airp: str
        from_airp: str
        if departing:
            to_airp, from_airp = airport, api_airport
        else:
            from_airp, to_airp = airport, api_airport

        to_airp = icelandic_city_name(capitalize_placename(to_airp))
        from_airp = icelandic_city_name(capitalize_placename(from_airp))

        from_airp = NounPhrase(from_airp).dative or from_airp
        to_airp = NounPhrase(to_airp).genitive or to_airp

        if from_airp == "*":
            answ["answer"] = f"Ekkert flug fannst til {to_airp} næstu {days} daga."
        elif to_airp == "*":
            answ["answer"] = f"Ekkert flug fannst frá {from_airp} næstu {days} daga."
        else:
            answ["answer"] = (
                f"Ekkert flug fannst "
                f"frá {from_airp} "
                f"til {to_airp} "
                f"næstu {days} daga."
            )
        answ["voice"] = answ["answer"]

    return answ
Example #6
0
def _format_flight_answer(flights: FlightList) -> Dict[str, str]:
    """
    Takes in a list of flights and returns a dict
    containing a formatted answer and text for a voice line.

    Each flight should contain the attributes:
        'No':           Flight number
        'DisplayName':  Name of airport/city
        'api_airport':  Name of Icelandic airport/city
        'flight_time':  Time of departure/arrival
        'Departure':    True if departing from api_airport, else False
        'Status':       Info on flight status (e.g. whether it's cancelled)
    """
    airport: str
    api_airport: str
    flight_dt: Optional[datetime]
    answers: List[str] = []
    voice_lines: List[str] = []

    for flight in flights:
        airport = icelandic_city_name(capitalize_placename(flight.get("DisplayName", "")))
        api_airport = icelandic_city_name(capitalize_placename(flight.get("api_airport", "")))

        flight_dt = flight.get("flight_time")
        if flight_dt is None or airport == "" or api_airport == "":
            continue
        flight_date_str = flight_dt.strftime("%-d. %B")
        flight_time_str = flight_dt.strftime("%H:%M")

        if flight.get("Departure"):
            airport = NounPhrase(airport).genitive or airport
            api_airport = NounPhrase(api_airport).dative or api_airport

            # Catch cancelled flights
            if (
                isinstance(flight.get("Status"), str)
                and "aflýst" in str(flight["Status"]).lower()
            ):
                line = f"Flugi {flight.get('No')} frá {api_airport} til {airport} er aflýst."
            else:
                line = (
                    f"Flug {flight.get('No')} til {airport} "
                    f"flýgur frá {api_airport} {flight_date_str} "
                    f"klukkan {flight_time_str} að staðartíma."
                )
        else:
            airport = NounPhrase(airport).dative or airport
            prep = iceprep_for_placename(api_airport)
            api_airport = NounPhrase(api_airport).dative or api_airport

            if (
                isinstance(flight.get("Status"), str)
                and "aflýst" in str(flight["Status"]).lower()
            ):
                line = f"Flugi {flight.get('No')} frá {airport} til {api_airport} er aflýst."
            else:
                line = (
                    f"Flug {flight.get('No')} frá {airport} "
                    f"lendir {prep} {api_airport} {flight_date_str} "
                    f"klukkan {flight_time_str} að staðartíma."
                )

        voice_line = re.sub(r" \d+\. ", " " + _DAY_INDEX_ACC[flight_dt.day] + " ", line)

        answers.append(line)
        voice_lines.append(voice_line)

    return {
        "answer": "<br/>".join(answers).strip(),
        "voice": _BREAK_SSML.join(voice_lines).strip(),
    }
Example #7
0
def QGeoSubject(node, params, result):
    n = capitalize_placename(_preprocess(result._text))
    nom = NounPhrase(n).nominative or n
    result.subject = nom
Example #8
0
def handle_plain_text(q: Query) -> bool:
    """Handle a plain text query, contained in the q parameter
    which is an instance of the query.Query class.
    Returns True if the query was handled, and in that case
    the appropriate properties on the Query instance have
    been set, such as the answer and the query type (qtype).
    If the query is not recognized, returns False."""
    ql = q.query_lower.rstrip("?")

    # Timezone being asked about
    tz = None
    # Whether user asked for the time in a particular location
    specific_desc = None

    if ql in _TIME_QUERIES:
        # Use location to determine time zone
        tz = timezone4loc(q.location, fallback="IS")
    else:
        locq = [x for x in _TIME_IN_LOC_QUERIES if ql.startswith(x.lower())]
        if not locq:
            return False  # Not matching any time queries
        # This is a query about the time in a particular location, i.e. country or city
        # Cut away question prefix, leaving only loc name
        loc = ql[len(locq[0]):].strip()
        if not loc:
            return False  # No location string
        # Intelligently capitalize country/city/location name
        loc = capitalize_placename(loc)

        # Look up nominative
        loc_nom = NounPhrase(loc).nominative or loc
        prep = "í"

        # Check if loc is a recognised country or city name
        cc = isocode_for_country_name(loc_nom)
        if cc and cc in country_timezones:
            # Look up country timezone
            # Use the first timezone although some countries have more than one
            # The timezone list returned by pytz is ordered by "dominance"
            tz = country_timezones[cc][0]
            prep = iceprep_for_cc(cc)
        else:
            # It's not a country name, look up in city database
            info = lookup_city_info(loc_nom)
            if info:
                top = info[0]
                location = (
                    cast(float, top.get("lat_wgs84")),
                    cast(float, top.get("long_wgs84")),
                )
                tz = timezone4loc(location)
                prep = iceprep_for_placename(loc_nom)

        if tz:
            # "Klukkan í Lundúnum er" - Used for voice answer
            dat = NounPhrase(loc_nom).dative or loc
            specific_desc = "Klukkan {0} {1} er".format(prep, dat)
        else:
            # Unable to find the specified location
            q.set_qtype(_TIME_QTYPE)
            q.set_key(loc)
            q.set_answer(
                *gen_answer("Ég gat ekki flett upp staðsetningunni {0}".format(
                    icequote(loc))))
            return True

    # We have a timezone. Return formatted answer.
    if tz:
        now = datetime.now(timezone(tz))

        desc = specific_desc or "Klukkan er"

        # Create displayable answer
        answer = "{0:02}:{1:02}".format(now.hour, now.minute)
        # A detailed response object is usually a list or a dict
        response = dict(answer=answer)
        # A voice answer is a plain string that will be
        # passed as-is to a voice synthesizer
        voice = "{0} {1}:{2:02}.".format(desc, now.hour, now.minute)

        q.set_qtype(_TIME_QTYPE)
        q.set_key(tz)  # Query key is the timezone
        q.set_answer(response, answer, voice)
        return True

    return False