From 764fd5c915b1bf7e878235117209a25035bc4772 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Mon, 3 Oct 2022 12:57:18 -0400 Subject: [PATCH 01/14] stores speaking changes, time sorting changes, new dialog files --- __init__.py | 212 ++++++------------ locale/en-us/dialog/en/all_locations.dialog | 2 +- locale/en-us/dialog/en/another_shop.dialog | 2 +- locale/en-us/dialog/en/ask_more.dialog | 2 +- locale/en-us/dialog/en/closed_now.dialog | 2 +- locale/en-us/dialog/en/closing_minutes.dialog | 2 +- locale/en-us/dialog/en/found_shop.dialog | 2 +- locale/en-us/dialog/en/more_shops_info.dialog | 1 + locale/en-us/dialog/en/more_than_one.dialog | 1 - locale/en-us/dialog/en/more_than_two.dialog | 1 + .../en-us/dialog/en/no_shop_on_level.dialog | 2 +- locale/en-us/dialog/en/open_now.dialog | 2 +- locale/en-us/dialog/en/opening_hours.dialog | 2 +- locale/en-us/dialog/en/opening_minutes.dialog | 2 +- locale/en-us/dialog/en/shop_by_floor.dialog | 2 +- locale/en-us/dialog/en/shop_not_found.dialog | 2 +- locale/en-us/dialog/en/start_parsing.dialog | 1 - request_handling.py | 22 -- test/test_skill.py | 20 +- time_calculations_handling.py | 113 ++++++++++ 20 files changed, 208 insertions(+), 187 deletions(-) create mode 100644 locale/en-us/dialog/en/more_shops_info.dialog delete mode 100644 locale/en-us/dialog/en/more_than_one.dialog create mode 100644 locale/en-us/dialog/en/more_than_two.dialog delete mode 100644 locale/en-us/dialog/en/start_parsing.dialog create mode 100644 time_calculations_handling.py diff --git a/__init__.py b/__init__.py index e6acc2f..aae21e3 100644 --- a/__init__.py +++ b/__init__.py @@ -27,13 +27,17 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +from fileinput import close from neon_utils.skills.neon_skill import NeonSkill, LOG from mycroft.skills.core import intent_file_handler -from .request_handling import RequestHandler from .request_handling import existing_lang_check, get_shop_data,\ shop_selection_by_floors,\ - location_format,\ - curent_time_extraction + location_format + + +from .time_calculations_handling import current_time_extraction,\ + time_calculation + import re @@ -132,7 +136,7 @@ def speak_shops(self, shop_info): hours = re.sub('(\d+)am.+(\d+)pm', r'from \1 A M to \2 P M', shop['hours']) self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location}) LOG.info({"name": shop['name'], "hours": hours, "location": location}) - self.gui.show_image(shop['logo'], caption=f'{hours} {location}', title=shop['name']) + # self.gui.show_image(shop['logo'], caption=f'{hours} {location}', title=shop['name']) def location_selection(self, shop_info): """ @@ -153,128 +157,49 @@ def location_selection(self, shop_info): self.speak_shops(shops) else: self.speak_dialog('no_shop_on_level') + #To do: the nearest shop search + self.speak_dialog('all_locations', {'n':len(shop_info), 'store_name':shop_info[0]['name']}) self.speak_shops(shop_info) return 3, None - def open_shops_search(self, shop_info, day_time, hour, min): - """ - Selects open shops. Collects the list of - open shops else return empty list. - Args: - shop_info (list): found shops on user's - request - Returns: - shop_info (list): open shops - """ - open_shops = [] - LOG.info(f"User's time {day_time, hour, min}") - for shop in shop_info: - parse_time = re.findall(r'(\d+)+[am|pm]', shop['hours']) - LOG.info(f'Parsed time {parse_time}') - open_time = int(parse_time[0]) - close_time = int(parse_time[1]) - if open_time <= hour < close_time: - open_shops.append(shop) - elif day_time[1] == 'am' and open_time <= hour: - open_shops.append(shop) - return open_shops - - - def time_calculation(self, shop_info, open, day_time, hour, min): - # add logic if shop opens and closes not at am-pm time period - # change to speak dialog - """ - Calculates time difference between user's current time - and shop working hours. - If 'open' argument is True: - If user one hour or less before closing: speaks how - many minutes left. Speaks shop info. - Else speaks corresponging dialog. - Speaks shop info. - If 'open' argument is False: - Speaks corresponding dialog. - If user is one hour or less before opening hours - speaks how much time is left for waiting. - If user's time is 'am' and user is before opening - hours, speaks how many hours and minutes left - waiting. - If user's time is evening (pm) speaks when the shop - opens in the morning. - Speaks shop info. - Args: - shop_info (list): found shops on user's request - open (boolean): True - if shop is open - day_time (str): user's current day time (am|pm) - hour (int): user's current hour - min (int): user's current minute - Returns: - 3, None (to ask for another shop info) - Examples: - work time 9am-10pm - user's time 8am - Prompt: 'Shop is closed now. Opens in 1 hour' - """ - for shop in shop_info: - work_time = shop['hours'] - normalized_time = re.findall(r'(\d+)[am|pm]', work_time) - open_time = int(normalized_time[0]) - close_time = int(normalized_time[1]) - LOG.info(f'work_time {work_time}') - shop_name = shop['name'] - parse_time = work_time.split('-') - LOG.info(f'parse_time {parse_time}') - # time left - wait_h = open_time - hour - 1 - wait_min = 60 - min - if open: - if day_time[1] == 'pm' and 0 < (close_time - hour) <= 1: - LOG.info(f'{shop_name} closes in {wait_min} minutes.') - self.speak_dialog('closing_minutes', {"shop_name": shop_name, "wait_min": wait_min}) - else: - LOG.info(f'{shop_name} is open.') - self.speak_dialog('open_now', {'shop_name': shop_name}) - LOG.info([shop]) - self.speak_shops([shop]) - else: - if day_time[1] == 'am' and hour < open_time: - if wait_h == 0: - LOG.info(f'{shop_name} is closed now. Opens in {wait_min} minutes') - self.speak_dialog('opening_minutes', {"shop_name": shop_name, "wait_min": wait_min}) - else: - LOG.info(f'{shop_name} is closed now. Opens in {wait_h} hour and {wait_min} minutes') - self.speak_dialog('opening_hours', {"shop_name": shop_name, "wait_h": wait_h, "wait_min": wait_min}) - elif hour >= close_time: - LOG.info(f'{shop_name} is closed now. Shop opens at {open_time}') - self.speak_dialog('closed_now', {'shop_name': shop_name, 'open_time': open_time}) - LOG.info([shop]) - self.speak_shops([shop]) - return 3, None + def speak_in_time_order(self, shop, open_info): + if open_info: + if shop[0]: + self.speak_dialog('closing_minutes', {'closing_minutes': open[0][0]}) + self.speak_shops([shop[2]]) + else: + if shop[0] and shop[1]: + self.speak_dialog('opening_hours', {'wait_h': shop[1], 'wait_min': shop[0]}) + elif shop[0]: + self.speak_dialog('opening_minutes', {'wait_min': shop[0]}) + self.speak_shops([shop[2]]) - def shops_by_time_selection(self, shop_info): - """ - If user chose to select shops by time or - use like default selection. Gets user's - current time. Selects open shops. - Args: - shop_info (list): found shops on user's - request - Returns: - time_calculation function with True - in 'open' argument. - time_calculation function with False - in 'open' argument. (if list - of open shops is 0) - - """ - LOG.info(f"Shop by time selection {shop_info}") - day_time, hour, min = curent_time_extraction() - # day_time, hour, min = ['11:15', 'pm'], 11, 15 - open_shops = self.open_shops_search(shop_info, day_time, hour, min) - if len(open_shops) >= 1: - return self.time_calculation(open_shops, True, day_time, hour, min) + def first_from_many_by_time(self, open, closed): + LOG.info(f'open: {open}, closed: {closed}') + first_shop = [] + if len(open) != 0: + first_shop = open[0] + self.speak_dialog('open_now', {'shop_name': first_shop[2]['name']}) + self.speak_in_time_order(first_shop, True) else: - return self.time_calculation(shop_info, False, day_time, hour, min) + first_shop = closed[0] + self.speak_dialog('closed_now', {'shop_name': first_shop[2]['name']}) + self.speak_in_time_order(first_shop, False) + return first_shop + def other_shops_by_time(self, open, closed, first_shop): + if first_shop in open: + for shop in open[1:]: + self.speak_in_time_order(shop, True) + if len(closed) != 0: + self.speak_dialog('closed_now', {'shop_name': open[0][2]['name']}) + for shop in closed[1:]: + self.speak_in_time_order(shop, False) + else: + for shop in close[1:]: + self.speak_in_time_order(shop, True) + return 3, None + def find_shop(self, user_request, mall_link): """ When the intent is matched, user_request @@ -309,36 +234,41 @@ def find_shop(self, user_request, mall_link): LOG.info(f'user_request {user_request}') LOG.info(f'mall_link {mall_link}') if user_request is not None: - self.speak_dialog("start_parsing") - LOG.info(f"I am parsing shops and malls for your request") file_path = self.file_system.path LOG.info(f'file_path {file_path}') shop_info = get_shop_data(mall_link, user_request, file_path) - LOG.info(f"I found {len(shop_info)} shops") LOG.info(f"shop list: {shop_info}") + day_time, hour, min = current_time_extraction() if len(shop_info) == 0: user_request = self.get_response('shop_not_found') return 1, user_request - elif len(shop_info) > 1: - self.speak_dialog('more_than_one') - # ask for the way of selection: time, location, nothing - sorting_selection = self.get_response('choose_selection') - if sorting_selection: - LOG.info(f'Users answer on sorting options: {sorting_selection}') - if self.voc_match(sorting_selection, "time"): - LOG.info('Time sorting selected') - return self.shops_by_time_selection(shop_info) - elif self.voc_match(sorting_selection, "location"): - LOG.info('Location sorting selected') - return self.location_selection(shop_info) - elif self.voc_match(sorting_selection, "no"): - LOG.info('No sorting selected. Sorting by time on default.') - return self.shops_by_time_selection(shop_info) - else: - LOG.info('Nothing matched. Sorting by time on default.') - return self.shops_by_time_selection(shop_info) + elif len(shop_info) > 2: + LOG.info(f"more_than_two: n = {len(shop_info)}, store {shop_info[0]['name']}") + self.speak_dialog('more_than_two', {'n': len(shop_info), 'store': shop_info[0]["name"]}) + # contains lists of open and closed shops + open, closed = time_calculation(shop_info, day_time, hour, min) + # speak first shop + first_shop = self.first_from_many_by_time(open, closed) + more_info = self.ask_yesno('more_shops_info') + if more_info == 'yes': + # ask for the way of selection: time, location, nothing + sorting_selection = self.get_response('choose_selection') + if sorting_selection: + LOG.info(f'Users answer on sorting options: {sorting_selection}') + if self.voc_match(sorting_selection, "time"): + return self.other_shops_by_time(open, closed, first_shop) + elif self.voc_match(sorting_selection, "location"): + LOG.info('Location sorting selected') + return self.location_selection(shop_info) + elif self.voc_match(sorting_selection, "no"): + LOG.info('No sorting selected. Sorting by time on default.') + return self.other_shops_by_time(open, closed, first_shop) + else: + LOG.info('Nothing matched. Sorting by time on default.') + return self.other_shops_by_time(open, closed, first_shop) else: LOG.info(f"found shop {shop_info}") + self.speak('I found') self.speak_shops(shop_info) return 3, None diff --git a/locale/en-us/dialog/en/all_locations.dialog b/locale/en-us/dialog/en/all_locations.dialog index fbffb2a..d147ddb 100644 --- a/locale/en-us/dialog/en/all_locations.dialog +++ b/locale/en-us/dialog/en/all_locations.dialog @@ -1 +1 @@ -Here are all possible shop locations. \ No newline at end of file +There are {{n}} {{store_name}} locations. The nearest {{store_name}} is: \ No newline at end of file diff --git a/locale/en-us/dialog/en/another_shop.dialog b/locale/en-us/dialog/en/another_shop.dialog index be4fbfd..0967824 100644 --- a/locale/en-us/dialog/en/another_shop.dialog +++ b/locale/en-us/dialog/en/another_shop.dialog @@ -1 +1 @@ -What shop you are looking for? \ No newline at end of file +What store you are looking for? \ No newline at end of file diff --git a/locale/en-us/dialog/en/ask_more.dialog b/locale/en-us/dialog/en/ask_more.dialog index b2b8db3..ba35fb1 100644 --- a/locale/en-us/dialog/en/ask_more.dialog +++ b/locale/en-us/dialog/en/ask_more.dialog @@ -1 +1 @@ -Do you want to get another shop info? \ No newline at end of file +Do you want to get another store info? \ No newline at end of file diff --git a/locale/en-us/dialog/en/closed_now.dialog b/locale/en-us/dialog/en/closed_now.dialog index d095784..852974e 100644 --- a/locale/en-us/dialog/en/closed_now.dialog +++ b/locale/en-us/dialog/en/closed_now.dialog @@ -1 +1 @@ -{{shop_name}} is closed now. Opens at {{open_time}}. \ No newline at end of file +{{shop_name}} is closed now. \ No newline at end of file diff --git a/locale/en-us/dialog/en/closing_minutes.dialog b/locale/en-us/dialog/en/closing_minutes.dialog index 46d9f8b..38e8d9f 100644 --- a/locale/en-us/dialog/en/closing_minutes.dialog +++ b/locale/en-us/dialog/en/closing_minutes.dialog @@ -1 +1 @@ -{{shop_name}} is open now. Closes in {{wait_min}} minutes. \ No newline at end of file +Closes in {{wait_min}} minutes. \ No newline at end of file diff --git a/locale/en-us/dialog/en/found_shop.dialog b/locale/en-us/dialog/en/found_shop.dialog index d667791..bab6a9f 100644 --- a/locale/en-us/dialog/en/found_shop.dialog +++ b/locale/en-us/dialog/en/found_shop.dialog @@ -1 +1 @@ -I found {{name}} with hours {{hours}}. You can find this store on {{location}}. \ No newline at end of file +{{name}}, work hours are {{hours}}. You can find this store on {{location}}. \ No newline at end of file diff --git a/locale/en-us/dialog/en/more_shops_info.dialog b/locale/en-us/dialog/en/more_shops_info.dialog new file mode 100644 index 0000000..d4227bb --- /dev/null +++ b/locale/en-us/dialog/en/more_shops_info.dialog @@ -0,0 +1 @@ +Do you want to get more stores info? \ No newline at end of file diff --git a/locale/en-us/dialog/en/more_than_one.dialog b/locale/en-us/dialog/en/more_than_one.dialog deleted file mode 100644 index 70532f4..0000000 --- a/locale/en-us/dialog/en/more_than_one.dialog +++ /dev/null @@ -1 +0,0 @@ -I found more than one shop in your request. \ No newline at end of file diff --git a/locale/en-us/dialog/en/more_than_two.dialog b/locale/en-us/dialog/en/more_than_two.dialog new file mode 100644 index 0000000..735b6e6 --- /dev/null +++ b/locale/en-us/dialog/en/more_than_two.dialog @@ -0,0 +1 @@ +There are {{n}} {{store}} stores. \ No newline at end of file diff --git a/locale/en-us/dialog/en/no_shop_on_level.dialog b/locale/en-us/dialog/en/no_shop_on_level.dialog index 69b5d6d..a6d1bb7 100644 --- a/locale/en-us/dialog/en/no_shop_on_level.dialog +++ b/locale/en-us/dialog/en/no_shop_on_level.dialog @@ -1 +1 @@ -There is no requested shop on your level. Here are all possible locations. \ No newline at end of file +There is no requested store on your level. Here are all possible locations. \ No newline at end of file diff --git a/locale/en-us/dialog/en/open_now.dialog b/locale/en-us/dialog/en/open_now.dialog index 3421d4c..f79ce72 100644 --- a/locale/en-us/dialog/en/open_now.dialog +++ b/locale/en-us/dialog/en/open_now.dialog @@ -1 +1 @@ -{{shop_name}} is open. \ No newline at end of file +{{shop_name}} is open now. \ No newline at end of file diff --git a/locale/en-us/dialog/en/opening_hours.dialog b/locale/en-us/dialog/en/opening_hours.dialog index 3bcb8ce..10b12ce 100644 --- a/locale/en-us/dialog/en/opening_hours.dialog +++ b/locale/en-us/dialog/en/opening_hours.dialog @@ -1 +1 @@ -{{shop_name}} is closed now. Opens in {{wait_h}} hour and {{wait_min}} minutes' \ No newline at end of file +Opens in {{wait_h}} hour and {{wait_min}} minutes. \ No newline at end of file diff --git a/locale/en-us/dialog/en/opening_minutes.dialog b/locale/en-us/dialog/en/opening_minutes.dialog index ce55fcc..af56a46 100644 --- a/locale/en-us/dialog/en/opening_minutes.dialog +++ b/locale/en-us/dialog/en/opening_minutes.dialog @@ -1 +1 @@ -{{shop_name}} is closed now. Opens in {{wait_min}} minutes. \ No newline at end of file +Opens in {{wait_min}} minutes. \ No newline at end of file diff --git a/locale/en-us/dialog/en/shop_by_floor.dialog b/locale/en-us/dialog/en/shop_by_floor.dialog index 97c1ac0..09bcf05 100644 --- a/locale/en-us/dialog/en/shop_by_floor.dialog +++ b/locale/en-us/dialog/en/shop_by_floor.dialog @@ -1 +1 @@ -Do you want to get shop info by floor? \ No newline at end of file +Do you want to get store info by floor? \ No newline at end of file diff --git a/locale/en-us/dialog/en/shop_not_found.dialog b/locale/en-us/dialog/en/shop_not_found.dialog index 859f286..e395799 100644 --- a/locale/en-us/dialog/en/shop_not_found.dialog +++ b/locale/en-us/dialog/en/shop_not_found.dialog @@ -1,2 +1,2 @@ Sorry, this store doesn't exist in this mall. I am not sure I've understood what you said. Repeat, please. -Sorry, I can not find this shop. I am not sure I've understood what you said. Repeat, please. \ No newline at end of file +Sorry, I can not find this store. I am not sure I've understood what you said. Repeat, please. \ No newline at end of file diff --git a/locale/en-us/dialog/en/start_parsing.dialog b/locale/en-us/dialog/en/start_parsing.dialog deleted file mode 100644 index 4071f9b..0000000 --- a/locale/en-us/dialog/en/start_parsing.dialog +++ /dev/null @@ -1 +0,0 @@ -I am parsing shops and malls for your request. \ No newline at end of file diff --git a/request_handling.py b/request_handling.py index 049e56a..e38c95f 100644 --- a/request_handling.py +++ b/request_handling.py @@ -40,8 +40,6 @@ import os import json -from datetime import datetime - class RequestHandler(): @@ -141,26 +139,6 @@ def existing_lang_check(user_lang: str, url): LOG.info('This language is not supported') return False, link -def curent_time_extraction(): - """ - Defines current time in utc timezone - Format: hour:minutes part of day (1:23 pm) - - Returns: - day_time (list): contains splited time - numerals and part of the day - day_time -> ['07:19', 'am'] - hour (int): current hour - min (int): current minute - """ - now = datetime.now().time().strftime("%I:%M %p") - # now = datetime.today().strftime("%H:%M %p") - LOG.info(f'now {now}') - day_time = now.lower().split(' ') - exact_time = day_time[0].split(':') - hour, min = int(exact_time[0]), int(exact_time[1]) - return day_time, hour, min - def location_format(location): """ Finds all digits in store's location and diff --git a/test/test_skill.py b/test/test_skill.py index 411ad8f..743ef50 100644 --- a/test/test_skill.py +++ b/test/test_skill.py @@ -92,16 +92,16 @@ def test_en_skill_init(self): {'context_key': 'MallParsing'}) self.skill.user_request_handling(message) - def test_en_time_extraction(self): - shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, - {'name': 'ABC Stores', 'hours': '10am – 8pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}] - day_time, hour, min = ['10:15', 'am'], 10, 15 - result_shops = self.skill.open_shops_search(shop_info, day_time, hour, min) - self.assertEqual(shop_info, result_shops) - - day_time, hour, min = ['9:15', 'am'], 9, 15 - result_shops = self.skill.open_shops_search(shop_info, day_time, hour, min) - self.assertEqual(shop_info[0], result_shops[0]) + # def test_en_time_extraction(self): + # shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, + # {'name': 'ABC Stores', 'hours': '10am – 8pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}] + # day_time, hour, min = ['10:15', 'am'], 10, 15 + # result_shops = self.skill.open_shops_search(shop_info, day_time, hour, min) + # self.assertEqual(shop_info, result_shops) + + # day_time, hour, min = ['9:15', 'am'], 9, 15 + # result_shops = self.skill.open_shops_search(shop_info, day_time, hour, min) + # self.assertEqual(shop_info[0], result_shops[0]) # def test_en_time_extraction(self): # shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, diff --git a/time_calculations_handling.py b/time_calculations_handling.py new file mode 100644 index 0000000..aac044b --- /dev/null +++ b/time_calculations_handling.py @@ -0,0 +1,113 @@ +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2022 Neongecko.com Inc. +# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, +# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo +# BSD-3 License +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from neon_utils.skills.neon_skill import LOG +from datetime import datetime +import re + +def current_time_extraction(): + """ + Defines current time in utc timezone + Format: hour:minutes part of day (1:23 pm) + + Returns: + day_time (list): contains splited time + numerals and part of the day + day_time -> ['07:19', 'am'] + hour (int): current hour + min (int): current minute + """ + now = datetime.now().time().strftime("%I:%M %p") + # now = datetime.today().strftime("%H:%M %p") + LOG.info(f'now {now}') + day_time = now.lower().split(' ') + exact_time = day_time[0].split(':') + hour, min = int(exact_time[0]), int(exact_time[1]) + return day_time, hour, min + + +def time_calculation(shop_info, day_time, hour, min): + """ + Calculates time difference between user's current time + and shop working hours. + If 'open' argument is True: + If user one hour or less before closing: speaks how + many minutes left. Speaks shop info. + Else speaks corresponging dialog. + Speaks shop info. + If 'open' argument is False: + Speaks corresponding dialog. + If user is one hour or less before opening hours + speaks how much time is left for waiting. + If user's time is 'am' and user is before opening + hours, speaks how many hours and minutes left + waiting. + If user's time is evening (pm) speaks when the shop + opens in the morning. + Speaks shop info. + Args: + shop_info (list): found shops on user's request + open (boolean): True - if shop is open + day_time (str): user's current day time (am|pm) + hour (int): user's current hour + min (int): user's current minute + Returns: + 3, None (to ask for another shop info) + Examples: + work time 9am-10pm + user's time 8am + Prompt: 'Shop is closed now. Opens in 1 hour' + """ + open_shops = [] + closed_shops = [] + for shop in shop_info: + work_time = shop['hours'] + normalized_time = re.findall(r'(\d+)[am|pm]', work_time) + open_time = int(normalized_time[0]) + close_time = int(normalized_time[1]) + LOG.info(f'work_time {work_time}') + parse_time = work_time.split('-') + LOG.info(f'parse_time {parse_time}') + # time left + wait_h = open_time - hour - 1 + wait_min = 60 - min + if day_time[1] == 'pm' and 0 < (close_time - hour) <= 1: + open_shops.append([wait_min, None, shop]) + elif day_time[1] == 'pm' and close_time > hour: + open_shops.append([None, None, shop]) + elif day_time[1] == 'am' and open_time <= hour: + open_shops.append([None, None, shop]) + elif day_time[1] == 'am' and hour < open_time: + if wait_h == 0: + closed_shops.append([wait_min, None, shop]) + else: + closed_shops.append([wait_min, wait_h, shop]) + elif day_time[1] == 'am' and hour >= close_time: + closed_shops.append([None, open_time, shop]) + return open_shops, closed_shops + From a2e343bd5b0a076c0ea12f06dd72657a7935aaa8 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Mon, 3 Oct 2022 16:59:41 +0000 Subject: [PATCH 02/14] Update skill.json --- skill.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skill.json b/skill.json index 1394d21..82862a5 100644 --- a/skill.json +++ b/skill.json @@ -1,6 +1,6 @@ { "title": "{Malls parser skill neon}", - "url": "https://github.com/NeonGeckoCom/skill-directory", + "url": "https://github.com/NeonGeckoCom/mall_guide_skill", "summary": "Skill for mall parsing", "short_description": "Skill for mall parsing", "description": "Skill parses mall web page and returns user name, location and work hours of requested shop, store", @@ -43,7 +43,7 @@ "NeonGeckoCom", "NeonMariia" ], - "skillname": "skill-directory", + "skillname": "mall_guide_skill", "authorname": "NeonGeckoCom", "foldername": null } \ No newline at end of file From 5d2aacb9056a9de61f984474c0929313bd6aa1c7 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Mon, 3 Oct 2022 14:36:38 -0400 Subject: [PATCH 03/14] selecting first to speak by time and location, location fixes, default location variable --- __init__.py | 50 ++++++++++++++----- locale/en-us/dialog/en/by_time_sorting.dialog | 1 + locale/en-us/dialog/en/closing_minutes.dialog | 2 +- locale/en-us/dialog/en/found_shop.dialog | 2 +- 4 files changed, 41 insertions(+), 14 deletions(-) create mode 100644 locale/en-us/dialog/en/by_time_sorting.dialog diff --git a/__init__.py b/__init__.py index aae21e3..fa3a75c 100644 --- a/__init__.py +++ b/__init__.py @@ -47,6 +47,7 @@ class DirectorySkill(NeonSkill): def __init__(self): super(DirectorySkill, self).__init__(name="DirectorySkill") self.url = "https://www.alamoanacenter.com/en/directory/" + self.user_floor = 'one' def initialize(self): @@ -134,7 +135,10 @@ def speak_shops(self, shop_info): LOG.info(shop) location = location_format(shop['location']) hours = re.sub('(\d+)am.+(\d+)pm', r'from \1 A M to \2 P M', shop['hours']) - self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location}) + if 'level' in location.lower(): + self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location, "on": 'on'}) + else: + self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location, "on": ''}) LOG.info({"name": shop['name'], "hours": hours, "location": location}) # self.gui.show_image(shop['logo'], caption=f'{hours} {location}', title=shop['name']) @@ -165,7 +169,7 @@ def location_selection(self, shop_info): def speak_in_time_order(self, shop, open_info): if open_info: if shop[0]: - self.speak_dialog('closing_minutes', {'closing_minutes': open[0][0]}) + self.speak_dialog('closing_minutes', {'closing_minutes': shop[0]}) self.speak_shops([shop[2]]) else: if shop[0] and shop[1]: @@ -173,31 +177,50 @@ def speak_in_time_order(self, shop, open_info): elif shop[0]: self.speak_dialog('opening_minutes', {'wait_min': shop[0]}) self.speak_shops([shop[2]]) + return 3, None - def first_from_many_by_time(self, open, closed): + def first_from_many_by_time(self, open, closed, shops_the_floor): LOG.info(f'open: {open}, closed: {closed}') first_shop = [] if len(open) != 0: - first_shop = open[0] - self.speak_dialog('open_now', {'shop_name': first_shop[2]['name']}) - self.speak_in_time_order(first_shop, True) + for shop in open: + if shop[2] in shops_the_floor: + first_shop = shop + self.speak_dialog('open_now', {'shop_name': first_shop[2]['name']}) + self.speak_in_time_order(first_shop, True) + return first_shop + elif first_shop == []: + first_shop = open[0] + self.speak_dialog('open_now', {'shop_name': first_shop[2]['name']}) + self.speak_in_time_order(first_shop, True) + return first_shop else: + for shop in closed: + if shop[2] in shops_the_floor: + first_shop = shop + self.speak_dialog('closed_now', {'shop_name': first_shop[2]['name']}) + self.speak_in_time_order(first_shop, False) + return first_shop first_shop = closed[0] self.speak_dialog('closed_now', {'shop_name': first_shop[2]['name']}) self.speak_in_time_order(first_shop, False) + return first_shop + return first_shop def other_shops_by_time(self, open, closed, first_shop): if first_shop in open: - for shop in open[1:]: - self.speak_in_time_order(shop, True) + for shop in open: + if shop != first_shop: + self.speak_in_time_order(shop, True) if len(closed) != 0: self.speak_dialog('closed_now', {'shop_name': open[0][2]['name']}) - for shop in closed[1:]: + for shop in closed: self.speak_in_time_order(shop, False) else: - for shop in close[1:]: - self.speak_in_time_order(shop, True) + for shop in closed: + if shop != first_shop: + self.speak_in_time_order(shop, True) return 3, None def find_shop(self, user_request, mall_link): @@ -248,7 +271,8 @@ def find_shop(self, user_request, mall_link): # contains lists of open and closed shops open, closed = time_calculation(shop_info, day_time, hour, min) # speak first shop - first_shop = self.first_from_many_by_time(open, closed) + shops_on_the_floor = shop_selection_by_floors(self.user_floor, shop_info) + first_shop = self.first_from_many_by_time(open, closed, shops_on_the_floor) more_info = self.ask_yesno('more_shops_info') if more_info == 'yes': # ask for the way of selection: time, location, nothing @@ -262,9 +286,11 @@ def find_shop(self, user_request, mall_link): return self.location_selection(shop_info) elif self.voc_match(sorting_selection, "no"): LOG.info('No sorting selected. Sorting by time on default.') + self.speak_dialog('by_time_sorting') return self.other_shops_by_time(open, closed, first_shop) else: LOG.info('Nothing matched. Sorting by time on default.') + self.speak_dialog('by_time_sorting') return self.other_shops_by_time(open, closed, first_shop) else: LOG.info(f"found shop {shop_info}") diff --git a/locale/en-us/dialog/en/by_time_sorting.dialog b/locale/en-us/dialog/en/by_time_sorting.dialog new file mode 100644 index 0000000..f5299af --- /dev/null +++ b/locale/en-us/dialog/en/by_time_sorting.dialog @@ -0,0 +1 @@ +Sorting stores by time. \ No newline at end of file diff --git a/locale/en-us/dialog/en/closing_minutes.dialog b/locale/en-us/dialog/en/closing_minutes.dialog index 38e8d9f..d17473d 100644 --- a/locale/en-us/dialog/en/closing_minutes.dialog +++ b/locale/en-us/dialog/en/closing_minutes.dialog @@ -1 +1 @@ -Closes in {{wait_min}} minutes. \ No newline at end of file +Closes in {{closing_minutes}} minutes. \ No newline at end of file diff --git a/locale/en-us/dialog/en/found_shop.dialog b/locale/en-us/dialog/en/found_shop.dialog index bab6a9f..3385cce 100644 --- a/locale/en-us/dialog/en/found_shop.dialog +++ b/locale/en-us/dialog/en/found_shop.dialog @@ -1 +1 @@ -{{name}}, work hours are {{hours}}. You can find this store on {{location}}. \ No newline at end of file +{{name}}, work hours are {{hours}}. You can find this store {{on}} {{location}}. \ No newline at end of file From cab57122369a992789fb0940bcc479bb5dce3586 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Tue, 4 Oct 2022 11:05:12 -0400 Subject: [PATCH 04/14] open - close logic changes, scenario changes, fixing 3rd time shop bug --- __init__.py | 158 ++++++++------------ locale/en-us/dialog/en/all_locations.dialog | 2 +- locale/en-us/dialog/en/closed_now.dialog | 1 - locale/en-us/dialog/en/found_shop.dialog | 2 +- locale/en-us/dialog/en/open_now.dialog | 1 - locale/en-us/dialog/en/which_floor.dialog | 3 - request_handling.py | 2 +- test/test_skill.py | 4 +- time_calculations_handling.py | 14 +- 9 files changed, 77 insertions(+), 110 deletions(-) delete mode 100644 locale/en-us/dialog/en/closed_now.dialog delete mode 100644 locale/en-us/dialog/en/open_now.dialog delete mode 100644 locale/en-us/dialog/en/which_floor.dialog diff --git a/__init__.py b/__init__.py index fa3a75c..18225cf 100644 --- a/__init__.py +++ b/__init__.py @@ -121,107 +121,54 @@ def start_again(self): self.speak_dialog('unexpected_error') return None - def speak_shops(self, shop_info): + def speak_shops(self, shop): """ Speaks shop info that was found. Substitutes time format for better pronunciation. - speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location}) + speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location, "open": shop['open']}) Shows shop label image in gui. Args: shop_info (list): found shops on user's request """ - for shop in shop_info: - LOG.info(shop) - location = location_format(shop['location']) - hours = re.sub('(\d+)am.+(\d+)pm', r'from \1 A M to \2 P M', shop['hours']) - if 'level' in location.lower(): - self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location, "on": 'on'}) - else: - self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location, "on": ''}) - LOG.info({"name": shop['name'], "hours": hours, "location": location}) - # self.gui.show_image(shop['logo'], caption=f'{hours} {location}', title=shop['name']) - - def location_selection(self, shop_info): - """ - If there are several shops in found shops list - and user wants to get shop info on the certain - floor. If shop on that floor exists speaks - this shop info. Else speaks all shops info. - Args: - shop_info (list): found shops on user's - request - Returns: - 3, None (to ask for another shop info) - """ - LOG.info(f"Shop by location selection {shop_info}") - floor = self.get_response('which_floor') - shops = shop_selection_by_floors(floor, shop_info) - if shops: - self.speak_shops(shops) + LOG.info(shop) + location = location_format(shop['location']) + hours = re.sub('(\d+)am.+(\d+)pm', r'from \1 A M to \2 P M', shop['hours']) + if 'level' in location.lower(): + self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location, "on": 'on', "open": shop['open']}) else: - self.speak_dialog('no_shop_on_level') - #To do: the nearest shop search - self.speak_dialog('all_locations', {'n':len(shop_info), 'store_name':shop_info[0]['name']}) - self.speak_shops(shop_info) - return 3, None + self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location, "on": '', "open": shop['open']}) + LOG.info({"name": shop['name'], "hours": hours, "location": location, "open": shop['open']}) + # self.gui.show_image(shop['logo'], caption=f'{hours} {location}', title=shop['name']) - def speak_in_time_order(self, shop, open_info): - if open_info: - if shop[0]: - self.speak_dialog('closing_minutes', {'closing_minutes': shop[0]}) - self.speak_shops([shop[2]]) - else: - if shop[0] and shop[1]: - self.speak_dialog('opening_hours', {'wait_h': shop[1], 'wait_min': shop[0]}) - elif shop[0]: + + def speak_in_time_order(self, shop): + self.speak_shops(shop[2]) + if shop[2]['open'] == 'open' and shop[0]: + self.speak_dialog('closing_minutes', {'closing_minutes': shop[0]}) + elif shop[2]['open'] == 'closed' and shop[0] and shop[1]: + self.speak_dialog('opening_hours', {'wait_h': shop[1], 'wait_min': shop[0]}) + elif shop[2]['open'] == 'closed' and shop[0]: self.speak_dialog('opening_minutes', {'wait_min': shop[0]}) - self.speak_shops([shop[2]]) - return 3, None - def first_from_many_by_time(self, open, closed, shops_the_floor): - LOG.info(f'open: {open}, closed: {closed}') - first_shop = [] - if len(open) != 0: - for shop in open: - if shop[2] in shops_the_floor: + def first_from_many(self, shop_info, shops_on_the_floor): + first_shop = {} + for shop in shop_info: + if shop[2]['open'] == 'open': + if shop in shops_on_the_floor: first_shop = shop - self.speak_dialog('open_now', {'shop_name': first_shop[2]['name']}) - self.speak_in_time_order(first_shop, True) return first_shop - elif first_shop == []: - first_shop = open[0] - self.speak_dialog('open_now', {'shop_name': first_shop[2]['name']}) - self.speak_in_time_order(first_shop, True) + for shop in shop_info: + if shop[2]['open'] == 'open': + first_shop = shop return first_shop - else: - for shop in closed: - if shop[2] in shops_the_floor: + for shop in shop_info: + if shop in shops_on_the_floor: first_shop = shop - self.speak_dialog('closed_now', {'shop_name': first_shop[2]['name']}) - self.speak_in_time_order(first_shop, False) return first_shop - first_shop = closed[0] - self.speak_dialog('closed_now', {'shop_name': first_shop[2]['name']}) - self.speak_in_time_order(first_shop, False) + first_shop = shops_on_the_floor[0] return first_shop - return first_shop - - def other_shops_by_time(self, open, closed, first_shop): - if first_shop in open: - for shop in open: - if shop != first_shop: - self.speak_in_time_order(shop, True) - if len(closed) != 0: - self.speak_dialog('closed_now', {'shop_name': open[0][2]['name']}) - for shop in closed: - self.speak_in_time_order(shop, False) - else: - for shop in closed: - if shop != first_shop: - self.speak_in_time_order(shop, True) - return 3, None def find_shop(self, user_request, mall_link): """ @@ -268,11 +215,13 @@ def find_shop(self, user_request, mall_link): elif len(shop_info) > 2: LOG.info(f"more_than_two: n = {len(shop_info)}, store {shop_info[0]['name']}") self.speak_dialog('more_than_two', {'n': len(shop_info), 'store': shop_info[0]["name"]}) - # contains lists of open and closed shops - open, closed = time_calculation(shop_info, day_time, hour, min) - # speak first shop + # contains list of open and closed shops + shop_info = time_calculation(shop_info, day_time, hour, min) + # collect list of shops on user's floor shops_on_the_floor = shop_selection_by_floors(self.user_floor, shop_info) - first_shop = self.first_from_many_by_time(open, closed, shops_on_the_floor) + # speak first shop + first_shop = self.first_from_many(shop_info, shops_on_the_floor) + self.speak_in_time_order(first_shop) more_info = self.ask_yesno('more_shops_info') if more_info == 'yes': # ask for the way of selection: time, location, nothing @@ -280,30 +229,45 @@ def find_shop(self, user_request, mall_link): if sorting_selection: LOG.info(f'Users answer on sorting options: {sorting_selection}') if self.voc_match(sorting_selection, "time"): - return self.other_shops_by_time(open, closed, first_shop) + for shop in shop_info: + self.speak_in_time_order(shop) + return 3, None elif self.voc_match(sorting_selection, "location"): LOG.info('Location sorting selected') - return self.location_selection(shop_info) - elif self.voc_match(sorting_selection, "no"): - LOG.info('No sorting selected. Sorting by time on default.') - self.speak_dialog('by_time_sorting') - return self.other_shops_by_time(open, closed, first_shop) + if shops_on_the_floor: + for shop in shops_on_the_floor: + self.speak_in_time_order(shop) + return 3, None + else: + self.speak_dialog('no_shop_on_level') + self.speak_dialog('all_locations', {'store_name':shop_info[0]['name']}) + for shop in shop_info: + self.speak_in_time_order(shop) + return 3, None else: - LOG.info('Nothing matched. Sorting by time on default.') + LOG.info('No sorting selected. Sorting by time on default.') self.speak_dialog('by_time_sorting') - return self.other_shops_by_time(open, closed, first_shop) + for shop in shop_info: + self.speak_in_time_order(shop) + return 3, None else: - LOG.info(f"found shop {shop_info}") + LOG.info(f"found shop/s {shop_info}") self.speak('I found') - self.speak_shops(shop_info) + shop_info = time_calculation(shop_info, day_time, hour, min) + LOG.info(f'shop info after time calculation {shop_info}') + for shop in shop_info: + self.speak_in_time_order(shop) return 3, None def execute(self, user_request, mall_link): count = 0 LOG.info('Start execute') - while count < 3 and user_request is not None and mall_link is not None: + while count < 2 and user_request is not None and mall_link is not None: + LOG.info(str(count)) new_count, user_request = self.find_shop(user_request, mall_link) count = count + new_count + if count == 2 and user_request is not None and mall_link is not None: + new_count, user_request = self.find_shop(user_request, mall_link) user_request = self.start_again() LOG.info(str(user_request)) if user_request is not None: diff --git a/locale/en-us/dialog/en/all_locations.dialog b/locale/en-us/dialog/en/all_locations.dialog index d147ddb..bd65901 100644 --- a/locale/en-us/dialog/en/all_locations.dialog +++ b/locale/en-us/dialog/en/all_locations.dialog @@ -1 +1 @@ -There are {{n}} {{store_name}} locations. The nearest {{store_name}} is: \ No newline at end of file +All {{store_name}} locations are: \ No newline at end of file diff --git a/locale/en-us/dialog/en/closed_now.dialog b/locale/en-us/dialog/en/closed_now.dialog deleted file mode 100644 index 852974e..0000000 --- a/locale/en-us/dialog/en/closed_now.dialog +++ /dev/null @@ -1 +0,0 @@ -{{shop_name}} is closed now. \ No newline at end of file diff --git a/locale/en-us/dialog/en/found_shop.dialog b/locale/en-us/dialog/en/found_shop.dialog index 3385cce..2676dee 100644 --- a/locale/en-us/dialog/en/found_shop.dialog +++ b/locale/en-us/dialog/en/found_shop.dialog @@ -1 +1 @@ -{{name}}, work hours are {{hours}}. You can find this store {{on}} {{location}}. \ No newline at end of file +{{name}}, work hours are {{hours}}. You can find this store {{on}} {{location}}. Store is {{open}}. \ No newline at end of file diff --git a/locale/en-us/dialog/en/open_now.dialog b/locale/en-us/dialog/en/open_now.dialog deleted file mode 100644 index f79ce72..0000000 --- a/locale/en-us/dialog/en/open_now.dialog +++ /dev/null @@ -1 +0,0 @@ -{{shop_name}} is open now. \ No newline at end of file diff --git a/locale/en-us/dialog/en/which_floor.dialog b/locale/en-us/dialog/en/which_floor.dialog deleted file mode 100644 index 4333de5..0000000 --- a/locale/en-us/dialog/en/which_floor.dialog +++ /dev/null @@ -1,3 +0,0 @@ -Which floor are you staying on? -On which floor are you? -what is your floor? \ No newline at end of file diff --git a/request_handling.py b/request_handling.py index e38c95f..4ba65cb 100644 --- a/request_handling.py +++ b/request_handling.py @@ -182,7 +182,7 @@ def shop_selection_by_floors(user_request, found_shops): """ shops_by_floor = [] for shop in found_shops: - numbers = re.findall(r'\d+', shop['location']) + numbers = re.findall(r'\d+', shop[2]['location']) if len(numbers) > 0: numbers = numbers[0] num = pronounce_number(int(numbers), ordinals=False) diff --git a/test/test_skill.py b/test/test_skill.py index 743ef50..f7e7b8c 100644 --- a/test/test_skill.py +++ b/test/test_skill.py @@ -80,8 +80,8 @@ def test_en_skill_init(self): self.skill.ask_yesno = Mock(return_value="yes") self.skill.gui._pages2uri = Mock() self.skill._start_mall_parser_prompt( - Message('test', {'utterance': 'find ABC stores', - 'shop': 'ABC stores', + Message('test', {'utterance': 'find Apple', + 'shop': 'Apple', 'lang': 'en-us'}, {'context_key': 'MallParsing'}) ) diff --git a/time_calculations_handling.py b/time_calculations_handling.py index aac044b..bd42e4b 100644 --- a/time_calculations_handling.py +++ b/time_calculations_handling.py @@ -91,23 +91,31 @@ def time_calculation(shop_info, day_time, hour, min): open_time = int(normalized_time[0]) close_time = int(normalized_time[1]) LOG.info(f'work_time {work_time}') - parse_time = work_time.split('-') - LOG.info(f'parse_time {parse_time}') + LOG.info(f'open_time {open_time}, close_time {close_time}') # time left wait_h = open_time - hour - 1 wait_min = 60 - min if day_time[1] == 'pm' and 0 < (close_time - hour) <= 1: + shop['open'] = 'open' open_shops.append([wait_min, None, shop]) elif day_time[1] == 'pm' and close_time > hour: + shop['open'] = 'open' open_shops.append([None, None, shop]) elif day_time[1] == 'am' and open_time <= hour: + shop['open'] = 'open' open_shops.append([None, None, shop]) elif day_time[1] == 'am' and hour < open_time: if wait_h == 0: + shop['open'] = 'closed' closed_shops.append([wait_min, None, shop]) else: + shop['open'] = 'closed' closed_shops.append([wait_min, wait_h, shop]) elif day_time[1] == 'am' and hour >= close_time: + shop['open'] = 'closed' closed_shops.append([None, open_time, shop]) - return open_shops, closed_shops + LOG.info(f'open and closed shops: {open_shops}, {closed_shops}') + found_shops = open_shops + closed_shops + LOG.info(found_shops) + return found_shops From c57046904e406be5773bfa75eabc30a740c9e65f Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Tue, 4 Oct 2022 11:52:12 -0400 Subject: [PATCH 05/14] closed shop in time calculation fixes --- __init__.py | 3 ++- {test => skill}/test_skill.py | 19 +++++++++++++++++-- time_calculations_handling.py | 7 ++++--- 3 files changed, 23 insertions(+), 6 deletions(-) rename {test => skill}/test_skill.py (89%) diff --git a/__init__.py b/__init__.py index 18225cf..bb0ec24 100644 --- a/__init__.py +++ b/__init__.py @@ -208,7 +208,8 @@ def find_shop(self, user_request, mall_link): LOG.info(f'file_path {file_path}') shop_info = get_shop_data(mall_link, user_request, file_path) LOG.info(f"shop list: {shop_info}") - day_time, hour, min = current_time_extraction() + # day_time, hour, min = current_time_extraction() + day_time, hour, min = ['9:15', 'pm'], 9, 15 if len(shop_info) == 0: user_request = self.get_response('shop_not_found') return 1, user_request diff --git a/test/test_skill.py b/skill/test_skill.py similarity index 89% rename from test/test_skill.py rename to skill/test_skill.py index f7e7b8c..83b48be 100644 --- a/test/test_skill.py +++ b/skill/test_skill.py @@ -38,6 +38,9 @@ from mycroft_bus_client import Message +# from .time_calculations_handling import current_time_extraction,\ +# time_calculation + class TestSkill(unittest.TestCase): @@ -92,14 +95,26 @@ def test_en_skill_init(self): {'context_key': 'MallParsing'}) self.skill.user_request_handling(message) + def test_find_shop(self): + mall_link = "https://www.alamoanacenter.com/en/directory/" + + user_request = 'starbucks' + new_count, user_request = self.skill.find_shop(user_request, mall_link) + self.assertEqual(new_count, 3) + + user_request = 'bubble' + new_count, user_request = self.skill.find_shop(user_request, mall_link) + self.assertEqual(new_count, 1) + + # def test_en_time_extraction(self): # shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, # {'name': 'ABC Stores', 'hours': '10am – 8pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}] # day_time, hour, min = ['10:15', 'am'], 10, 15 - # result_shops = self.skill.open_shops_search(shop_info, day_time, hour, min) + # result_shops = time_calculation(shop_info, day_time, hour, min) # self.assertEqual(shop_info, result_shops) - # day_time, hour, min = ['9:15', 'am'], 9, 15 + # day_time, hour, min = ['9:15', 'pm'], 9, 15 # result_shops = self.skill.open_shops_search(shop_info, day_time, hour, min) # self.assertEqual(shop_info[0], result_shops[0]) diff --git a/time_calculations_handling.py b/time_calculations_handling.py index bd42e4b..0315759 100644 --- a/time_calculations_handling.py +++ b/time_calculations_handling.py @@ -111,11 +111,12 @@ def time_calculation(shop_info, day_time, hour, min): else: shop['open'] = 'closed' closed_shops.append([wait_min, wait_h, shop]) - elif day_time[1] == 'am' and hour >= close_time: + elif day_time[1] == 'pm' and hour >= close_time: shop['open'] = 'closed' closed_shops.append([None, open_time, shop]) - LOG.info(f'open and closed shops: {open_shops}, {closed_shops}') + LOG.info(f'found shops open: {open_shops}') + LOG.info(f'found shops closed: {closed_shops}') found_shops = open_shops + closed_shops - LOG.info(found_shops) + LOG.info(f'open + closed: {found_shops}') return found_shops From d028fd07bc67dbb32a39539af5ed2fe9aca78854 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Tue, 4 Oct 2022 12:07:44 -0400 Subject: [PATCH 06/14] test directory --- test/test_skill.py | 133 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 test/test_skill.py diff --git a/test/test_skill.py b/test/test_skill.py new file mode 100644 index 0000000..3bc7e29 --- /dev/null +++ b/test/test_skill.py @@ -0,0 +1,133 @@ +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2022 Neongecko.com Inc. +# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, +# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo +# BSD-3 License +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import shutil +import unittest + +from os import mkdir +from os.path import dirname, join, exists +from mock import Mock +from ovos_utils.messagebus import FakeBus + +from mycroft.skills.skill_loader import SkillLoader + +from mycroft_bus_client import Message + +# from .time_calculations_handling import current_time_extraction,\ +# time_calculation + + +class TestSkill(unittest.TestCase): + + @classmethod + def setUpClass(cls) -> None: + bus = FakeBus() + bus.run_in_thread() + skill_loader = SkillLoader(bus, dirname(dirname(__file__))) + skill_loader.load() + cls.skill = skill_loader.instance + + # Define a directory to use for testing + cls.test_fs = join(dirname(__file__), "skill") + if not exists(cls.test_fs): + mkdir(cls.test_fs) + + # Override the configuration and fs paths to use the test directory + cls.skill.settings_write_path = cls.test_fs + cls.skill.file_system.path = cls.test_fs + cls.skill._init_settings() + cls.skill.initialize() + + # Override speak and speak_dialog to test passed arguments + cls.skill.speak = Mock() + cls.skill.speak_dialog = Mock() + + # TODO: Put any skill method overrides here + + def setUp(self): + self.skill.speak.reset_mock() + self.skill.speak_dialog.reset_mock() + + # TODO: Put any cleanup here that runs before each test case + + @classmethod + def tearDownClass(cls) -> None: + shutil.rmtree(cls.test_fs) + + def test_en_skill_init(self): + self.skill.ask_yesno = Mock(return_value="yes") + self.skill.gui._pages2uri = Mock() + self.skill._start_mall_parser_prompt( + Message('test', {'utterance': 'find Apple', + 'shop': 'Apple', + 'lang': 'en-us'}, + {'context_key': 'MallParsing'}) + ) + + message = Message('test', {'utterance': 'find ABC stores', + 'shop': 'ABC stores', + 'lang': 'en-us'}, + {'context_key': 'MallParsing'}) + self.skill.user_request_handling(message) + + def test_find_shop(self): + mall_link = "https://www.alamoanacenter.com/en/directory/" + + user_request = 'starbucks' + new_count, user_request = self.skill.find_shop(user_request, mall_link) + self.assertEqual(new_count, 3) + + user_request = 'bubble' + new_count, user_request = self.skill.find_shop(user_request, mall_link) + self.assertEqual(new_count, 1) + + # def test_en_time_extraction(self): + # shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, + # {'name': 'ABC Stores', 'hours': '10am – 8pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}] + # day_time, hour, min = ['10:15', 'am'], 10, 15 + # result_shops = time_calculation(shop_info, day_time, hour, min) + # self.assertEqual(shop_info, result_shops) + + # day_time, hour, min = ['9:15', 'pm'], 9, 15 + # result_shops = self.skill.open_shops_search(shop_info, day_time, hour, min) + # self.assertEqual(shop_info[0], result_shops[0]) + + # def test_en_time_extraction(self): + # shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, + # {'name': 'ABC Stores', 'hours': '10am – 8pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}, + # {'name': 'ABC Stores', 'hours': '10am – 9pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}] + + # day_time, hour, min = ['9:15', 'pm'], 9, 15 + # print(self.skill.time_calculation(shop_info, False, day_time, hour, min)) + + # day_time, hour, min = ['8:15', 'pm'], 8, 15 + # print(self.skill.time_calculation(shop_info, False, day_time, hour, min)) + + +if __name__ == '__main__': + unittest.main() From a2f272a1ffa1aceeec6af3505d46059a3c168a7d Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Wed, 5 Oct 2022 11:13:57 -0400 Subject: [PATCH 07/14] static cache file, additional location dialogs, tests fixes --- __init__.py | 31 ++-- .../dialog/en/another_shop_locations.dialog | 1 + .../en-us/dialog/en/choose_selection.dialog | 2 +- locale/en-us/dialog/en/more_shops_info.dialog | 2 +- locale/en-us/dialog/en/more_than_two.dialog | 1 - locale/en-us/dialog/en/shops_amount.dialog | 1 + .../dialog/en/shops_on_user_floor.dialog | 1 + locale/en-us/dialog/en/stop.dialog | 1 - mall_cache.json | 1 + request_handling.py | 21 ++- skill/test_skill.py | 134 ------------------ test/test_skill.py | 4 +- time_calculations_handling.py | 1 - 13 files changed, 37 insertions(+), 164 deletions(-) create mode 100644 locale/en-us/dialog/en/another_shop_locations.dialog delete mode 100644 locale/en-us/dialog/en/more_than_two.dialog create mode 100644 locale/en-us/dialog/en/shops_amount.dialog create mode 100644 locale/en-us/dialog/en/shops_on_user_floor.dialog delete mode 100644 locale/en-us/dialog/en/stop.dialog create mode 100644 mall_cache.json delete mode 100644 skill/test_skill.py diff --git a/__init__.py b/__init__.py index bb0ec24..6d6cda8 100644 --- a/__init__.py +++ b/__init__.py @@ -131,15 +131,14 @@ def speak_shops(self, shop): shop_info (list): found shops on user's request """ - LOG.info(shop) + LOG.info(f"speak shops {shop}") location = location_format(shop['location']) hours = re.sub('(\d+)am.+(\d+)pm', r'from \1 A M to \2 P M', shop['hours']) if 'level' in location.lower(): self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location, "on": 'on', "open": shop['open']}) else: self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location, "on": '', "open": shop['open']}) - LOG.info({"name": shop['name'], "hours": hours, "location": location, "open": shop['open']}) - # self.gui.show_image(shop['logo'], caption=f'{hours} {location}', title=shop['name']) + self.gui.show_image(shop['logo'], caption=f'{hours} {location}', title=shop['name']) def speak_in_time_order(self, shop): @@ -204,26 +203,25 @@ def find_shop(self, user_request, mall_link): LOG.info(f'user_request {user_request}') LOG.info(f'mall_link {mall_link}') if user_request is not None: - file_path = self.file_system.path - LOG.info(f'file_path {file_path}') - shop_info = get_shop_data(mall_link, user_request, file_path) - LOG.info(f"shop list: {shop_info}") - # day_time, hour, min = current_time_extraction() - day_time, hour, min = ['9:15', 'pm'], 9, 15 + dir_path = self.root_dir + shop_info = get_shop_data(mall_link, user_request, dir_path) + day_time, hour, min = current_time_extraction() if len(shop_info) == 0: user_request = self.get_response('shop_not_found') return 1, user_request elif len(shop_info) > 2: LOG.info(f"more_than_two: n = {len(shop_info)}, store {shop_info[0]['name']}") - self.speak_dialog('more_than_two', {'n': len(shop_info), 'store': shop_info[0]["name"]}) + self.speak_dialog('shops_amount', {'n': len(shop_info), 'store_name': shop_info[0]["name"]}) # contains list of open and closed shops shop_info = time_calculation(shop_info, day_time, hour, min) # collect list of shops on user's floor shops_on_the_floor = shop_selection_by_floors(self.user_floor, shop_info) + LOG.info(f'shops_on_the_floor {shops_on_the_floor}') # speak first shop first_shop = self.first_from_many(shop_info, shops_on_the_floor) self.speak_in_time_order(first_shop) - more_info = self.ask_yesno('more_shops_info') + # ask for other shops info showing + more_info = self.ask_yesno('more_shops_info', {'shop_name': shop_info[0][2]["name"]}) if more_info == 'yes': # ask for the way of selection: time, location, nothing sorting_selection = self.get_response('choose_selection') @@ -236,12 +234,19 @@ def find_shop(self, user_request, mall_link): elif self.voc_match(sorting_selection, "location"): LOG.info('Location sorting selected') if shops_on_the_floor: + self.speak_dialog('shops_on_user_floor', {'n': len(shops_on_the_floor), + 'store_name': shop_info[0]["name"]}) for shop in shops_on_the_floor: self.speak_in_time_order(shop) + if len(shops_on_the_floor) != len(shop_info): + self.speak_dialog('another_shop_locations') + for shop in shop_info: + if shop not in shops_on_the_floor: + self.speak_in_time_order(shop) return 3, None else: self.speak_dialog('no_shop_on_level') - self.speak_dialog('all_locations', {'store_name':shop_info[0]['name']}) + self.speak_dialog('all_locations', {'store_name':shop_info[0][2]['name']}) for shop in shop_info: self.speak_in_time_order(shop) return 3, None @@ -253,8 +258,8 @@ def find_shop(self, user_request, mall_link): return 3, None else: LOG.info(f"found shop/s {shop_info}") - self.speak('I found') shop_info = time_calculation(shop_info, day_time, hour, min) + self.speak_dialog('shops_amount', {'n': len(shop_info), 'store_name': shop_info[0][2]["name"]}) LOG.info(f'shop info after time calculation {shop_info}') for shop in shop_info: self.speak_in_time_order(shop) diff --git a/locale/en-us/dialog/en/another_shop_locations.dialog b/locale/en-us/dialog/en/another_shop_locations.dialog new file mode 100644 index 0000000..a7b68f8 --- /dev/null +++ b/locale/en-us/dialog/en/another_shop_locations.dialog @@ -0,0 +1 @@ +Another locations. \ No newline at end of file diff --git a/locale/en-us/dialog/en/choose_selection.dialog b/locale/en-us/dialog/en/choose_selection.dialog index 23ac161..01ba75b 100644 --- a/locale/en-us/dialog/en/choose_selection.dialog +++ b/locale/en-us/dialog/en/choose_selection.dialog @@ -1 +1 @@ -Do you want to select store by work hours or location? \ No newline at end of file +Do you want to sort stores by work hours or location? \ No newline at end of file diff --git a/locale/en-us/dialog/en/more_shops_info.dialog b/locale/en-us/dialog/en/more_shops_info.dialog index d4227bb..7d08729 100644 --- a/locale/en-us/dialog/en/more_shops_info.dialog +++ b/locale/en-us/dialog/en/more_shops_info.dialog @@ -1 +1 @@ -Do you want to get more stores info? \ No newline at end of file +Do you want to get other {{shop_name}} stores info? \ No newline at end of file diff --git a/locale/en-us/dialog/en/more_than_two.dialog b/locale/en-us/dialog/en/more_than_two.dialog deleted file mode 100644 index 735b6e6..0000000 --- a/locale/en-us/dialog/en/more_than_two.dialog +++ /dev/null @@ -1 +0,0 @@ -There are {{n}} {{store}} stores. \ No newline at end of file diff --git a/locale/en-us/dialog/en/shops_amount.dialog b/locale/en-us/dialog/en/shops_amount.dialog new file mode 100644 index 0000000..013542b --- /dev/null +++ b/locale/en-us/dialog/en/shops_amount.dialog @@ -0,0 +1 @@ +I found {{n}} {{store_name}}. \ No newline at end of file diff --git a/locale/en-us/dialog/en/shops_on_user_floor.dialog b/locale/en-us/dialog/en/shops_on_user_floor.dialog new file mode 100644 index 0000000..f0ae44e --- /dev/null +++ b/locale/en-us/dialog/en/shops_on_user_floor.dialog @@ -0,0 +1 @@ +I found {{n}} {{store_name}} stores on your level. \ No newline at end of file diff --git a/locale/en-us/dialog/en/stop.dialog b/locale/en-us/dialog/en/stop.dialog deleted file mode 100644 index 2874c27..0000000 --- a/locale/en-us/dialog/en/stop.dialog +++ /dev/null @@ -1 +0,0 @@ -Do you want to exit the mall directory? \ No newline at end of file diff --git a/mall_cache.json b/mall_cache.json new file mode 100644 index 0000000..e763be5 --- /dev/null +++ b/mall_cache.json @@ -0,0 +1 @@ +{"ABC Stores": [{"name": "ABC Stores", "hours": "9am – 9pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png"}, {"name": "ABC Stores", "hours": "9am – 8pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png"}], "Abercrombie & Fitch": [{"name": "Abercrombie & Fitch", "hours": "10am – 8pm", "location": "Level 3, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/stores/380.svg"}], "Aēsop": [{"name": "Aēsop", "hours": "10am – 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1597705044914-aesop%20logo.png"}], "Agave And Vine": [{"name": "Agave And Vine", "hours": "11am – 10pm", "location": "Mall Level 2, in The Lanai", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/agave-vine-553x260-v21.png"}], "Ahi & Vegetable": [{"name": "Ahi & Vegetable", "hours": "9am – 8pm", "location": "Mall Level 2, in The Lanai", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Aja Sushi & Bento": [{"name": "Aja Sushi & Bento", "hours": "9am – 6pm", "location": "Street Level 1, in Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Ala Moana Poi Bowl": [{"name": "Ala Moana Poi Bowl", "hours": "9am – 8pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Alexander Mcqueen": [{"name": "Alexander Mcqueen", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/alexander-mcqueen-logo-553x260-v1.png"}], "The Alley": [{"name": "The Alley", "hours": "11am – 9pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Aloha Dry Cleaners And Laundry": [{"name": "Aloha Dry Cleaners And Laundry", "hours": "11am – 7pm", "location": "Street Level 1, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1658192953454-ADCL%20logo.jpg"}], "Aloha Lane": [{"name": "Aloha Lane", "hours": "11am – 7pm", "location": "Street Level 1, near Old Navy", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/aloha-lane-logo-v1.png"}], "Al Phillips the Cleaner": [{"name": "Al Phillips the Cleaner", "hours": "6:30am – 8pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/Bunny-logo-553x260-v1.png"}], "American Eagle Outfitters": [{"name": "American Eagle Outfitters", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/467ed6fe0951a49572064dde41aca8da"}], "Amy's Hallmark": [{"name": "Amy's Hallmark", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/703.svg"}], "Anthropologie": [{"name": "Anthropologie", "hours": "11am – 7pm", "location": "Level 3, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/stores/2137020339.svg"}], "Apm Monaco": [{"name": "Apm Monaco", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1594332951496-APM%20Monaco%20Logo.jpg"}], "Apple": [{"name": "Apple", "hours": "10am – 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/10327.svg"}], "AQUA BLU": [{"name": "AQUA BLU", "hours": "10am – 10pm", "location": "Level 3, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/aqua-blu-logo-553x260-v1.png"}], "Aritzia": [{"name": "Aritzia", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1610044848337-Aritzia-Logo-REGULAR-Final.jpg"}], "Around The Pearl": [{"name": "Around The Pearl", "hours": "11am – 2:30pm; 4:30pm – 8pm", "location": "Mall Level 2, near Target", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Assaggio": [{"name": "Assaggio", "hours": "11am – 2:30pm; 4:30pm – 8pm", "location": "Street Level 1, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "AT&T": [{"name": "AT&T", "hours": "10am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "https://placewise.imgix.net/images/api/retailhubjs/4a035067bbae402d523f151cc89c26fd"}], "Auntie Anne's": [{"name": "Auntie Anne's", "hours": "11am – 7pm", "location": "Street Level 1, in Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Bae Beauty Hi": [{"name": "Bae Beauty Hi", "hours": "10am – 8pm", "location": "Street Level 1, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615934194333-balenciaga-logo-1.png"}], "Balenciaga": [{"name": "Balenciaga", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615934194333-balenciaga-logo-1.png"}], "Bally": [{"name": "Bally", "hours": "9:30am – 9pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/retailhubjs/97bd76329546dad6dc34754fd0a29365"}], "The Bar": [{"name": "The Bar", "hours": "12pm – 8pm", "location": "Inside Foodland Farms", "logo": "https://placewise.imgix.net/images/api/stores/2137060504.svg"}], "bareMinerals": [{"name": "bareMinerals", "hours": "11am – 7pm", "location": "Mall Level 2, near Target", "logo": "https://placewise.imgix.net/images/api/stores/2137060504.svg"}], "Barnes & Noble Booksellers": [{"name": "Barnes & Noble Booksellers", "hours": "10am – 9pm", "location": "Anchor Store", "logo": "https://placewise.imgix.net/images/api/stores/225.svg"}], "Barnes & Noble Café": [{"name": "Barnes & Noble Café", "hours": "10am – 9pm", "location": "Inside Barnes & Noble Booksellers", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Bath & Body Works": [{"name": "Bath & Body Works", "hours": "10am – 9pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/3.svg"}], "Ben Bridge Jeweler": [{"name": "Ben Bridge Jeweler", "hours": "10am – 7pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/retailhubjs/e5102aca8047112c6e61df58197158e8"}], "Ben Bridge Timeworks": [{"name": "Ben Bridge Timeworks", "hours": "10am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Big Island Candies": [{"name": "Big Island Candies", "hours": "10am – 7pm", "location": "Street Level 1, near Centerstage", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Bloomingdale's": [{"name": "Bloomingdale's", "hours": "11am – 8pm", "location": "Anchor Store", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/bloomingdales-logo-553x260-v1.png"}], "Blue Hawaii Lifestyle": [{"name": "Blue Hawaii Lifestyle", "hours": "11am – 6pm", "location": "Mall Level 2, near Target", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Body Play": [{"name": "Body Play", "hours": "11am – 7pm", "location": "Street Level 1", "logo": "https://placewise.imgix.net/images/api/stores/1921.svg"}, {"name": "Body Play", "hours": "11am – 7pm", "location": "Street Level 1", "logo": "https://placewise.imgix.net/images/api/stores/1921.svg"}], "The Body Shop": [{"name": "The Body Shop", "hours": "11am – 7pm", "location": "Street Level 1, near Centerstage", "logo": "https://placewise.imgix.net/images/api/stores/1921.svg"}], "Bottega Veneta": [{"name": "Bottega Veneta", "hours": "11am – 7pm", "location": "Mall Level 2, near Neiman Marcus", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/bottega-veneta-logo-553x260-v1.png"}], "BoxLunch": [{"name": "BoxLunch", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://images.shoptopia.com/mcache/450/store/2137064563"}], "Brandy Melville": [{"name": "Brandy Melville", "hours": "11am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/f9c0aef5eb4c2b858a76681d2419b21b"}], "Brighton Collectibles": [{"name": "Brighton Collectibles", "hours": "10am – 8pm", "location": "Level 3, near Target", "logo": "https://placewise.imgix.net/images/api/stores/5415.svg"}], "Brug": [{"name": "Brug", "hours": "9am – 6pm", "location": "Mall Level 2, in The Lanai", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "BRUG Hokkaido Bakery": [{"name": "BRUG Hokkaido Bakery", "hours": "8am – 6pm", "location": "Street Level 1, near Centerstage", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Brunello Cucinelli": [{"name": "Brunello Cucinelli", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1586484780201-BRUNELLO_08__PantCoolGrey10-resized.jpg"}], "Buffalo Wild Wings": [{"name": "Buffalo Wild Wings", "hours": "11am – 1am", "location": "Level 3, near Target", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Bulgari": [{"name": "Bulgari", "hours": "10am – 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1581816067950-Bulgari_header-logo.png"}], "Burberry": [{"name": "Burberry", "hours": "10am – 8pm", "location": "Mall Level 2, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/stores/1075.svg"}], "California Pizza Kitchen": [{"name": "California Pizza Kitchen", "hours": "11am – 10pm", "location": "Ho'okipa Terrace Upper Level 4, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Cartier": [{"name": "Cartier", "hours": "10am – 8pm", "location": "Mall Level 2, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/retailhubjs/304c1130bab67cd6cf6f894fe248214b"}], "CELINE": [{"name": "CELINE", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/celine-logo-553x260-v2.png"}], "Cellreon": [{"name": "Cellreon", "hours": "9:30am – 9pm", "location": "Mall Level 2, near Target", "logo": "https://placewise.imgix.net/images/api/stores/457.svg"}], "Centerstage": [{"name": "Centerstage", "hours": "9:30am – 9pm", "location": "Anchor Store", "logo": "https://placewise.imgix.net/images/api/stores/457.svg"}], "Champs Sports/Champs Women": [{"name": "Champs Sports/Champs Women", "hours": "10am – 8pm", "location": "Street Level 1, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/457.svg"}], "Chanel": [{"name": "Chanel", "hours": "10am – 8pm", "location": "Mall Level 2, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/retailhubjs/ce522af6364a85b0ad28b4dd9b6a41dc"}], "Chapel Hats": [{"name": "Chapel Hats", "hours": "11am – 7:30pm", "location": "Street Level 1, near Centerstage", "logo": "https://assets.digitalservices.ggp.com/content/dam/ggp-digital-assets/Images/Tenant-Images/Tenant-Logos/Chapel_Hats_553x260.png/jcr:content/renditions/original.png"}], "Charleys Philly Steaks": [{"name": "Charleys Philly Steaks", "hours": "11am – 7pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "CH Carolina Herrera": [{"name": "CH Carolina Herrera", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/carolina-herrera-logo-553x260-v1.png"}], "Cheeseburger Factory": [{"name": "Cheeseburger Factory", "hours": "11am – 7pm", "location": "Street Level 1, in Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Chick-fil-A": [{"name": "Chick-fil-A", "hours": "10am – 8pm", "location": "Street Level 1, near Old Navy", "logo": "https://placewise.imgix.net/images/api/stores/779.svg"}, {"name": "Chick-fil-A", "hours": "10am – 8pm", "location": "Street Level 1, near Old Navy", "logo": "https://placewise.imgix.net/images/api/stores/779.svg"}], "CINNABON": [{"name": "CINNABON", "hours": "10am – 8pm", "location": "Street Level 1, in Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Claire's": [{"name": "Claire's", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/5112.svg"}], "Coach": [{"name": "Coach", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/603.svg"}], "Cocolini": [{"name": "Cocolini", "hours": "7am – 8pm", "location": "Level 3, near Neiman Marcus", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "The Coffee Bean & Tea Leaf": [{"name": "The Coffee Bean & Tea Leaf", "hours": "7am – 8pm", "location": "Street Level 1, in Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}, {"name": "The Coffee Bean & Tea Leaf", "hours": "10am – 8pm", "location": "Inside Foodland Farms", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Cole Haan": [{"name": "Cole Haan", "hours": "10am – 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/9162.svg"}], "The Cookie Corner": [{"name": "The Cookie Corner", "hours": "10am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Crazy Shirts": [{"name": "Crazy Shirts", "hours": "11am – 7pm", "location": "Level 3, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/retailhubjs/c98cef49df3649a0a77e7b1b9281b8e0.jpeg"}], "Crispy Beef Jerky": [{"name": "Crispy Beef Jerky", "hours": "11am – 7pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1649363200135-HCBJ%201%20Color%20Black%20sm.jpg"}], "Crocs": [{"name": "Crocs", "hours": "11am – 7pm", "location": "Street Level 1, near Centerstage", "logo": "https://placewise.imgix.net/images/api/stores/21370269690.svg"}], "Curry House CoCo Ichibanya": [{"name": "Curry House CoCo Ichibanya", "hours": "11am – 7pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Da Spot": [{"name": "Da Spot", "hours": "10am – 8pm", "location": "Mall Level 2, in The Lanai", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Dior": [{"name": "Dior", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1619635806579-dior%20logo.jpg"}], "Dior Cafe": [{"name": "Dior Cafe", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/dg-logo-553x260-v1.png"}], "Dolce&Gabbana": [{"name": "Dolce&Gabbana", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/dg-logo-553x260-v1.png"}], "E&K Jewelry": [{"name": "E&K Jewelry", "hours": "10am – 7pm", "location": "Street Level 1, near Centerstage", "logo": "https://placewise.imgix.net/images/api/retailhubjs/ee6d3bd2687d1654da3d5d650c101214"}], "Ebar": [{"name": "Ebar", "hours": "8am – 9pm", "location": "Inside Nordstrom", "logo": "https://placewise.imgix.net/images/api/retailhubjs/ee6d3bd2687d1654da3d5d650c101214"}], "Eco Town": [{"name": "Eco Town", "hours": "10am – 8pm", "location": "Street Level 1, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1597707707663-eco%20town%20logo.jpg"}], "ELEVEN": [{"name": "ELEVEN", "hours": "Closed Today", "location": "Inside Foodland Farms", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Espresso Bar": [{"name": "Espresso Bar", "hours": "11am – 5pm", "location": "Inside Neiman Marcus", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Ewa Wing Stage": [{"name": "Ewa Wing Stage", "hours": "9:30am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1655846433321-Search%20For%20Snoopy-live%20AMC.png"}], "The Experience": [{"name": "The Experience", "hours": "9:30am – 7pm", "location": "Level 3, near Neiman Marcus", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1655846433321-Search%20For%20Snoopy-live%20AMC.png"}], "The Eye Gallery": [{"name": "The Eye Gallery", "hours": "9:30am – 7pm", "location": "Level 3, near Neiman Marcus", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/eyegallery.png"}], "Eztech Electronics": [{"name": "Eztech Electronics", "hours": "11am – 7pm", "location": "Street Level 1, near Centerstage", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/the-face-shop-logo.png"}], "The Face Shop": [{"name": "The Face Shop", "hours": "11am – 7pm", "location": "Mall Level 2, near Target", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/the-face-shop-logo.png"}], "Famous Footwear": [{"name": "Famous Footwear", "hours": "10am – 8pm", "location": "Street Level 1, near Old Navy", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1583269470608-FamousLogo_FullColor.png"}], "Fendi": [{"name": "Fendi", "hours": "10am – 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/retailhubjs/5b05a987eadec96d8dc5be054703636c"}], "Foodland Farms": [{"name": "Foodland Farms", "hours": "6am – 9pm", "location": "Street Level 1, near Foodland Farms", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/foodland-farms-logo-553x260-v1.png"}], "Foot Locker": [{"name": "Foot Locker", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/320.svg"}], "Formidables Boutique": [{"name": "Formidables Boutique", "hours": "11am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/forty-carrots-logo-553x260-v1.png"}], "Forty Carrots": [{"name": "Forty Carrots", "hours": "11am – 8pm", "location": "Inside Bloomingdale's", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/forty-carrots-logo-553x260-v1.png"}], "Fossil": [{"name": "Fossil", "hours": "11am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/5b3dd971f101b3f6b044403e4389e2eb"}], "Freaky Tiki Tropical Optical": [{"name": "Freaky Tiki Tropical Optical", "hours": "10am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1581739164758-FREAKYTIKILogo.svg"}], "Gap": [{"name": "Gap", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/698.svg"}], "GapKids": [{"name": "GapKids", "hours": "10am – 9pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/2137026349.svg"}], "Gelato Bar": [{"name": "Gelato Bar", "hours": "10am – 9pm", "location": "Inside Nordstrom", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Genki Sushi": [{"name": "Genki Sushi", "hours": "11am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "GEN Korean BBQ House": [{"name": "GEN Korean BBQ House", "hours": "10am – 10pm", "location": "Ho'okipa Terrace Upper Level 4, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Genova International": [{"name": "Genova International", "hours": "11am – 7pm", "location": "Mall Level 2, near Target", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1649103585232-Genova%20Logo.jpg"}], "Giorgio Armani": [{"name": "Giorgio Armani", "hours": "11am – 7pm", "location": "Mall Level 2, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/stores/2137048880.svg"}], "GNC": [{"name": "GNC", "hours": "11am – 7pm", "location": "Street Level 1, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/1931.svg"}], "Golden Eye": [{"name": "Golden Eye", "hours": "10am – 7pm", "location": "Mall Level 2, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Goma Tei Ramen": [{"name": "Goma Tei Ramen", "hours": "11am – 9pm", "location": "Street Level 1, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "GRATiAE": [{"name": "GRATiAE", "hours": "9:30am – 5pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1581818733224-GRATiEA_logo.png"}], "Gucci": [{"name": "Gucci", "hours": "11am – 7pm", "location": "Mall Level 2, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/stores/622.svg"}], "Guest Services": [{"name": "Guest Services", "hours": "11am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "https://placewise.imgix.net/images/api/stores/6401.svg"}], "H.I.S. Hawaii": [{"name": "H.I.S. Hawaii", "hours": "11am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "https://placewise.imgix.net/images/api/retailhubjs/62c4b28322444d5632cdbc12f740d89d.jpeg"}], "Häagen-Dazs": [{"name": "Häagen-Dazs", "hours": "11am – 8pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "https://placewise.imgix.net/images/api/retailhubjs/62c4b28322444d5632cdbc12f740d89d.jpeg"}], "Habitant": [{"name": "Habitant", "hours": "11am – 8pm", "location": "Inside Nordstrom", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1581818789612-HappyWahine_Logo-1.png"}], "Happy Wahine Boutique": [{"name": "Happy Wahine Boutique", "hours": "11am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1581818789612-HappyWahine_Logo-1.png"}], "Harry Winston": [{"name": "Harry Winston", "hours": "10am – 8pm", "location": "Mall Level 2, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/retailhubjs/f5dd3d516082391544c8a2d18ac309c0.jpeg"}], "Hawaii's Finest": [{"name": "Hawaii's Finest", "hours": "10am – 8pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1654890093461-HIFI%20SIMPLE%20LOGO-01.png"}], "Hawaiian Island Creations": [{"name": "Hawaiian Island Creations", "hours": "11am – 7pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/Hawaiian-Island-Creations-logo.png"}], "Hawaii Vault": [{"name": "Hawaii Vault", "hours": "11am – 7pm", "location": "Street Level 1, near Centerstage", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/health-korea-logo-553x260-v1.png"}], "Health Korea": [{"name": "Health Korea", "hours": "11am – 7pm", "location": "Mall Level 2, near Target", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/health-korea-logo-553x260-v1.png"}], "Hermès": [{"name": "Hermès", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1581739699694-hermes.svg"}], "Herve Chapelier": [{"name": "Herve Chapelier", "hours": "11am – 7pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/retailhubjs/6ff80c687b2ddc49ff6dc152447a91aa"}], "Hic Surf": [{"name": "Hic Surf", "hours": "10am – 8pm", "location": "Level 3, near Macy's", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/Hawaiian-Island-Creations-logo.png"}], "Hilo Hattie": [{"name": "Hilo Hattie", "hours": "11am – 7pm", "location": "Street Level 1, in the Diamond Head Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1581739239969-Hilo_Hattie.svg"}], "Himalayan Kitchen": [{"name": "Himalayan Kitchen", "hours": "11am – 9pm", "location": "Street Level 1, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1614824768037-Hiro%20logo.png"}], "Hiro Systems Hawaii": [{"name": "Hiro Systems Hawaii", "hours": "8am – 7pm", "location": "Street Level 1, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1614824768037-Hiro%20logo.png"}], "HI Steaks": [{"name": "HI Steaks", "hours": "10am – 8pm", "location": "Inside Foodland Farms", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Hitea Cafe": [{"name": "Hitea Cafe", "hours": "10:30am – 7pm", "location": "Mall Level 2, in The Lanai", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Ho'āla Salon and Spa - Aveda": [{"name": "Ho'āla Salon and Spa - Aveda", "hours": "9am – 7pm", "location": "Level 3, near Neiman Marcus", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/hoala-logo-553x260-v1.png"}], "Hollister Co.": [{"name": "Hollister Co.", "hours": "10am – 8pm", "location": "Level 3, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/6328.svg"}], "Homecoming Honolulu": [{"name": "Homecoming Honolulu", "hours": "10am – 6pm", "location": "Level 3, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1659040688744-Honolulu%20Bistro%20Round%20Logo.jpg"}], "Honolulu Bistro": [{"name": "Honolulu Bistro", "hours": "10am – 6pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1659040688744-Honolulu%20Bistro%20Round%20Logo.jpg"}], "Honolulu Coffee Co.": [{"name": "Honolulu Coffee Co.", "hours": "10am – 8pm", "location": "Level 3, near Neiman Marcus", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}, {"name": "Honolulu Coffee Co.", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Honolulu Cookie Company": [{"name": "Honolulu Cookie Company", "hours": "10am – 8pm", "location": "Street Level 1, in Makai Market Food Court", "logo": "https://placewise.imgix.net/images/api/retailhubjs/8971d844fa1e65263feb243f146c25b1"}], "Honolulu Satellite City Hall": [{"name": "Honolulu Satellite City Hall", "hours": "9am – 5pm", "location": "Street Level 1, near Macy's", "logo": "https://placewise.imgix.net/images/api/retailhubjs/dcc46eee9b06c66d4219fdd713599c57"}], "Hope And Henry": [{"name": "Hope And Henry", "hours": "11am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/dcc46eee9b06c66d4219fdd713599c57"}], "Hot Topic": [{"name": "Hot Topic", "hours": "11am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/801.svg"}], "Hublot": [{"name": "Hublot", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Illy Caffe": [{"name": "Illy Caffe", "hours": "10:30am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Island Brew Coffeehouse": [{"name": "Island Brew Coffeehouse", "hours": "7:30am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Island Crepes & Lemonade": [{"name": "Island Crepes & Lemonade", "hours": "10am – 7pm", "location": "Street Level 1, in Makai Market Food Court", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/island-crepes-lemonade-logo.png"}], "Island Slipper": [{"name": "Island Slipper", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/island-slipper-logo-553x260-v1.png"}], "Island Sole": [{"name": "Island Sole", "hours": "11am – 6pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1581739204334-island_sole.svg"}], "Island Vintage Coffee": [{"name": "Island Vintage Coffee", "hours": "8am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "It'sugar": [{"name": "It'sugar", "hours": "11am – 8pm", "location": "Level 3, near Target", "logo": "https://placewise.imgix.net/images/api/retailhubjs/12afdfb4259bca255dfe83854328d3ff"}], "Jade Dynasty Seafood Restaurant": [{"name": "Jade Dynasty Seafood Restaurant", "hours": "10:30am – 9pm", "location": "Ho'okipa Terrace Upper Level 4, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Jamba Juice": [{"name": "Jamba Juice", "hours": "9am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}, {"name": "Jamba Juice", "hours": "10am – 9pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Jams World": [{"name": "Jams World", "hours": "10am – 9pm", "location": "Level 3, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/retailhubjs/d4da457fd8fcb5ca60a45ecf2a7b07d6"}], "Jeans Warehouse": [{"name": "Jeans Warehouse", "hours": "10am – 9pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/af7e23e5e3a899680c0ef823cb76e68f.png"}], "Jejubing Dessert Cafe": [{"name": "Jejubing Dessert Cafe", "hours": "12pm – 8pm", "location": "Street Level 1, near Centerstage", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Jimmy Choo": [{"name": "Jimmy Choo", "hours": "11am – 6pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1627608398102-Jimmy%20Choo%20Master%20Logo%20Black.jpg"}], "Johnny Was": [{"name": "Johnny Was", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/aa75ff1e567b611c008778387f8064ae"}], "Jollibee": [{"name": "Jollibee", "hours": "11am – 7pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Journeys": [{"name": "Journeys", "hours": "11am – 7pm", "location": "Level 3, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/2137048313.svg"}], "JTB 'OLI'OLI Station": [{"name": "JTB 'OLI'OLI Station", "hours": "8am – 5pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/jtb-logo-553x260-v1.png"}], "JTB USA": [{"name": "JTB USA", "hours": "8am – 5pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1593133191103-Jungle%20Fun%20Island%20Logo_RGB_No%20Blue%20Background.png"}], "Jungle Fun Island": [{"name": "Jungle Fun Island", "hours": "10am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1593133191103-Jungle%20Fun%20Island%20Logo_RGB_No%20Blue%20Background.png"}], "Kahala": [{"name": "Kahala", "hours": "11am – 7pm", "location": "Mall Level 2, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1663028129166-Kahala-Logo.jpg"}], "Kahulale'a": [{"name": "Kahulale'a", "hours": "11am – 7pm", "location": "Level 3, near Neiman Marcus", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1649875297885-logo.jpg"}], "Kamukura": [{"name": "Kamukura", "hours": "11am – 7pm", "location": "Mall Level 2, in The Lanai", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1649183466013-LOGO2.JPG"}], "Kate Spade New York": [{"name": "Kate Spade New York", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615597970407-2017%20ksny_logo.jpg"}], "Kawaii Kawaii": [{"name": "Kawaii Kawaii", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1626229304345-27A5E5E6-79B5-4734-BFA6-D3142D155A51.png"}], "Kay Jewelers": [{"name": "Kay Jewelers", "hours": "11am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/1755a594e1e40e37d73b3a06fd1c254e.png"}], "Koho": [{"name": "Koho", "hours": "11am – 6pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1635206131179-Koho_Logo.png%20(1).png"}], "Kpop Friends": [{"name": "Kpop Friends", "hours": "11am – 6pm", "location": "Mall Level 2, near Target", "logo": "https://placewise.imgix.net/images/api/stores/2137039695.svg"}], "L'Occitane": [{"name": "L'Occitane", "hours": "11am – 6pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/2137039695.svg"}], "Lacoste": [{"name": "Lacoste", "hours": "11am – 7pm", "location": "Level 3, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/retailhubjs/e2b15706500483595634fc39468a9b2a"}], "Laha'ole Designs": [{"name": "Laha'ole Designs", "hours": "10am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1663002609207-Logo.png"}], "Lahaina Chicken": [{"name": "Lahaina Chicken", "hours": "10am – 7pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Laine Honolulu": [{"name": "Laine Honolulu", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1636490297934-logo.png"}], "Land And Sea Golf": [{"name": "Land And Sea Golf", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1663198328472-LASG%20Logo.png"}], "The LEGO Store": [{"name": "The LEGO Store", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/2137023663.svg"}], "LensCrafters": [{"name": "LensCrafters", "hours": "9:30am – 8pm", "location": "Ho'okipa Terrace Upper Level 4, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1622760771134-LensCrafters_Logo_Black.jpg"}], "LeSportsac": [{"name": "LeSportsac", "hours": "10am – 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/retailhubjs/65303a344cb9c77b79750d823c934872"}], "Lids": [{"name": "Lids", "hours": "11am – 7pm", "location": "Street Level 1, near Centerstage", "logo": "https://placewise.imgix.net/images/api/stores/900.svg"}, {"name": "Lids", "hours": "11am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/900.svg"}], "Liliha Bakery": [{"name": "Liliha Bakery", "hours": "7am – 8pm", "location": "Inside macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Little Café Siam": [{"name": "Little Café Siam", "hours": "9:30am – 9pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Local Motion": [{"name": "Local Motion", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/local-motion.png"}], "Locker Room by Lids": [{"name": "Locker Room by Lids", "hours": "11am – 7pm", "location": "Street Level 1, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/2137048183.svg"}], "Loewe": [{"name": "Loewe", "hours": "11am – 7pm", "location": "Mall Level 2, near Macy's", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/longines-logo-553x260-v1.png"}], "Longines": [{"name": "Longines", "hours": "11am – 7pm", "location": "Mall Level 2, near Neiman Marcus", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/longines-logo-553x260-v1.png"}], "Longs Drugs": [{"name": "Longs Drugs", "hours": "6am – 11pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/db6359b416299d2fedd19a061720335f.png"}], "Louis Vuitton": [{"name": "Louis Vuitton", "hours": "10am – 8pm", "location": "Level 3, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/stores/901.svg"}], "Love At Dawn": [{"name": "Love At Dawn", "hours": "10am – 6:30pm", "location": "Mall Level 2, near Target", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1617406505608-Main-PMS%20186-Horizontal.png"}], "LUCKY STRIKE SOCIAL": [{"name": "LUCKY STRIKE SOCIAL", "hours": "2pm – Midnight", "location": "Level 3, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1617406505608-Main-PMS%20186-Horizontal.png"}], "Lulu Hawaii": [{"name": "Lulu Hawaii", "hours": "10am – 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/retailhubjs/c70a1f1f3f4b9d07fb6a8bb353871e7c"}], "Lululemon Athletica": [{"name": "Lululemon Athletica", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/c70a1f1f3f4b9d07fb6a8bb353871e7c"}], "LUPICIA": [{"name": "LUPICIA", "hours": "11am – 6pm", "location": "Street Level 1, near Centerstage", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/lupicia-logo-553x260-v1.png"}], "Lyca Mobile": [{"name": "Lyca Mobile", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/8cf9aebfb241b16fe45cc173ec966468.jpeg"}], "M.A.C": [{"name": "M.A.C", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/8cf9aebfb241b16fe45cc173ec966468.jpeg"}], "macy's": [{"name": "macy's", "hours": "9:30am – 8pm", "location": "Anchor Store", "logo": "https://placewise.imgix.net/images/api/stores/222.svg"}], "Madame Saigon Inc": [{"name": "Madame Saigon Inc", "hours": "10am – 8pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "https://placewise.imgix.net/images/api/retailhubjs/eee58f53e17a2ef6401fed29624e66c3.svg"}], "Madewell": [{"name": "Madewell", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/eee58f53e17a2ef6401fed29624e66c3.svg"}], "Madsci Lab": [{"name": "Madsci Lab", "hours": "10am – 8pm", "location": "Mall Level 2, near Target", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Mahaloha Burger": [{"name": "Mahaloha Burger", "hours": "10am – 8pm", "location": "Mall Level 2, in The Lanai", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Maison Margiela Paris": [{"name": "Maison Margiela Paris", "hours": "12pm – Midnight", "location": "Mall Level 2, near Neiman Marcus", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1662689149841-Maison_Margiela_logo.png"}], "Mai Tai's": [{"name": "Mai Tai's", "hours": "12pm – Midnight", "location": "Ho'okipa Terrace Upper Level 4, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1611958759854-Mai%20Tais%20logo%20translucent.png"}], "mālie": [{"name": "mālie", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/curbside-white.svg"}], "Mall Management": [{"name": "Mall Management", "hours": "10am – 7pm", "location": "Street Level 1, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Mama's Spaghetti House": [{"name": "Mama's Spaghetti House", "hours": "10am – 7pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Mama Pho": [{"name": "Mama Pho", "hours": "11am – 9pm", "location": "Ho'okipa Terrace Upper Level 4, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "MANAOLA HAWAII": [{"name": "MANAOLA HAWAII", "hours": "10am – 7pm", "location": "Street Level 1, near Centerstage", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/Manaola-logo.jpg"}], "Mana Sandwiches": [{"name": "Mana Sandwiches", "hours": "9am – 6pm", "location": "Mall Level 2, in The Lanai", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Mango Mango Dessert": [{"name": "Mango Mango Dessert", "hours": "11am – 9pm", "location": "Level 3, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Mani Pedi Spa": [{"name": "Mani Pedi Spa", "hours": "10am – 8pm", "location": "Mall Level 2, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/curbside-white.svg"}], "Manna Myon": [{"name": "Manna Myon", "hours": "10am – 7:30pm", "location": "Street Level 1, in Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Mariposa Restaurant": [{"name": "Mariposa Restaurant", "hours": "11:30am – 6pm", "location": "Inside Neiman Marcus", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/mariposa.png"}], "Marshalls": [{"name": "Marshalls", "hours": "9:30am – 9:30pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/3550.svg"}], "Martin & MacArthur": [{"name": "Martin & MacArthur", "hours": "11am – 7pm", "location": "Mall Level 2, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/curbside-white.svg"}], "Massage Palace": [{"name": "Massage Palace", "hours": "10am – 9pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1638411410564-logo_mauidivers_stacked_blue_02.png"}], "Maui Divers Jewelry": [{"name": "Maui Divers Jewelry", "hours": "10am – 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1638411410564-logo_mauidivers_stacked_blue_02.png"}], "Meet Fresh": [{"name": "Meet Fresh", "hours": "12pm – 10pm", "location": "Street Level 1", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/mermaid-bar-logo.png"}], "Mermaid Bar": [{"name": "Mermaid Bar", "hours": "11am – 3:30pm", "location": "Mall Level 2, near Macy's", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/mermaid-bar-logo.png"}], "Michael Kors": [{"name": "Michael Kors", "hours": "10am – 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/2137039706.svg"}], "Mika": [{"name": "Mika", "hours": "10am – 7pm", "location": "Mall Level 2, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Minamoto Kitchoan": [{"name": "Minamoto Kitchoan", "hours": "10am – 7pm", "location": "Street Level 1, near Centerstage", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Miu Miu": [{"name": "Miu Miu", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1581815758592-miu_miu-logo.jpg"}], "Moncler": [{"name": "Moncler", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/moncler-logo-553x260-v1.png"}], "Montblanc": [{"name": "Montblanc", "hours": "11am – 7pm", "location": "Mall Level 2, near Neiman Marcus", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1663771368109-MB%20Logo%20with%20Snowcap%20Positive%20(1)1024_1.jpg"}], "Moomin Shop Hawaii": [{"name": "Moomin Shop Hawaii", "hours": "11am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/curbside-white.svg"}], "Morphe": [{"name": "Morphe", "hours": "4pm – 9pm", "location": "Level 3, near Neiman Marcus", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/morphe-logo-553x260-v1.png"}], "Morton's The Steakhouse": [{"name": "Morton's The Steakhouse", "hours": "4pm – 9pm", "location": "Mall Level 2", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Mr. Cow Hawaii": [{"name": "Mr. Cow Hawaii", "hours": "9am – 7pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1651708815528-Mr%20Cow%20Logo%2010222.jpg"}], "Musubi Cafe Iyasume": [{"name": "Musubi Cafe Iyasume", "hours": "9am – 7pm", "location": "Mall Level 2, in The Lanai", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Nagasaki Champon By Ringer Hut": [{"name": "Nagasaki Champon By Ringer Hut", "hours": "11am – 7:30pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Na Hoku": [{"name": "Na Hoku", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/curbside-white.svg"}], "Naniwa-Ya Ramen": [{"name": "Naniwa-Ya Ramen", "hours": "9am – 9pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1644957597424-NR%20Logo-2.jpg"}], "Nature Republic": [{"name": "Nature Republic", "hours": "11am – 6pm", "location": "Level 3, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1644957597424-NR%20Logo-2.jpg"}], "Neiman Marcus": [{"name": "Neiman Marcus", "hours": "11am – 6pm", "location": "Anchor Store", "logo": "https://placewise.imgix.net/images/api/stores/1110.svg"}], "NET Electronics": [{"name": "NET Electronics", "hours": "8am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1647379072894-05-21%20Niu%20Health%20-%20Urgent%20Care%20Plus%20Sublogo%20v1%20-%20RGB%20Vertical.png"}], "Niu Health": [{"name": "Niu Health", "hours": "8am – 8pm", "location": "Mall Level 2, near Mauka Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1647379072894-05-21%20Niu%20Health%20-%20Urgent%20Care%20Plus%20Sublogo%20v1%20-%20RGB%20Vertical.png"}], "No'eau Designers": [{"name": "No'eau Designers", "hours": "10am – 8pm", "location": "Level 3", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1649184076308-Logo.jpg"}], "Nordstrom": [{"name": "Nordstrom", "hours": "10am – 9pm", "location": "Anchor Store", "logo": "https://assets.digitalservices.ggp.com/content/dam/ggp-digital-assets/Images/Tenant-Images/Tenant-Logos/nordstrom-bw-vector.svg/jcr:content/renditions/original.svg"}], "The North Face": [{"name": "The North Face", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/1139.svg"}], "Oakley": [{"name": "Oakley", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/curbside-white.svg"}], "Ocean Creations": [{"name": "Ocean Creations", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1598311501061-old-navy.jpg"}], "Ocean Queen": [{"name": "Ocean Queen", "hours": "10am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1598311501061-old-navy.jpg"}], "Old Navy": [{"name": "Old Navy", "hours": "10am – 8pm", "location": "Anchor Store", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1598311501061-old-navy.jpg"}], "Olive Garden": [{"name": "Olive Garden", "hours": "11am – 10pm", "location": "Ho'okipa Terrace Upper Level 4, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "OMEGA": [{"name": "OMEGA", "hours": "11am – 7pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/retailhubjs/e9ee6f494d191d393bec2902d357a454.jpeg"}], "PacSun": [{"name": "PacSun", "hours": "11am – 7pm", "location": "Level 3, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/2137031778.svg"}], "Panda Express": [{"name": "Panda Express", "hours": "10am – 8pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "PANDORA": [{"name": "PANDORA", "hours": "11am – 7pm", "location": "Mall Level 2, near Target", "logo": "https://placewise.imgix.net/images/api/stores/2137019886.svg"}], "Papaya": [{"name": "Papaya", "hours": "11am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/25d55ffc0fcbe33387374071b4e8d064"}], "Paris Miki": [{"name": "Paris Miki", "hours": "10am – 7pm", "location": "Street Level 1, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1581817440517-logo_basic_parismiki.png"}], "Patisserie La Palme D'or": [{"name": "Patisserie La Palme D'or", "hours": "9:30am – 7pm", "location": "Mall Level 2, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Pearl Plus": [{"name": "Pearl Plus", "hours": "Midnight – 10pm", "location": "Mall Level 2, near Target", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1622248999134-Logo.png"}], "Pink Box": [{"name": "Pink Box", "hours": "Midnight – 10pm", "location": "Mall Level 2, near Target", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1622248999134-Logo.png"}], "Pipe Dreams Surf Co": [{"name": "Pipe Dreams Surf Co", "hours": "Midnight – 10pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1622248999134-Logo.png"}], "Planet Fitness": [{"name": "Planet Fitness", "hours": "Midnight – 10pm", "location": "Street Level 1, near Saks Fifth Avenue OFF 5th", "logo": "https://placewise.imgix.net/images/api/retailhubjs/9c42bfed57038f5b63e356358cc4b08f"}], "Poke & Box": [{"name": "Poke & Box", "hours": "11am – 7pm", "location": "Street Level 1, in Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Popoki Massage": [{"name": "Popoki Massage", "hours": "10am – 9pm", "location": "Street Level 1, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/curbside-white.svg"}], "Prada": [{"name": "Prada", "hours": "10am – 8pm", "location": "Mall Level 2, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/retailhubjs/2b9a3e6a7cacf9e5d627e0b11fd8a1ee.png"}, {"name": "Prada", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/2b9a3e6a7cacf9e5d627e0b11fd8a1ee.png"}], "Premier Barbershop": [{"name": "Premier Barbershop", "hours": "9:30am – 7pm", "location": "Mall Level 2, near Macy's", "logo": "https://www.alamoanacenter.com/content/dam/rw-2/images/tenant-images/tenant-logos/premier-barbershop-logo-553x260-v2.png"}], "Pressed": [{"name": "Pressed", "hours": "8am – 9pm", "location": "Street Level 1, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Pups Of War": [{"name": "Pups Of War", "hours": "11am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Raising Cane's": [{"name": "Raising Cane's", "hours": "9am – 8pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Rakuten Card Lounge": [{"name": "Rakuten Card Lounge", "hours": "10am – 6pm", "location": "Level 3, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Ramen Bario": [{"name": "Ramen Bario", "hours": "11am – 7pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Reformation": [{"name": "Reformation", "hours": "10am – 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1658536892884-reformation-logo-01.jpg"}], "Reyn Spooner": [{"name": "Reyn Spooner", "hours": "10am – 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1581739122871-ReynSpooner.svg"}], "Rimowa": [{"name": "Rimowa", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/rimowa-logo-553x260-v1.png"}], "Rip Curl": [{"name": "Rip Curl", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/251bf2d7769fa510c41dc011f2e80735.gif"}], "Rokkaku Hamakatsu": [{"name": "Rokkaku Hamakatsu", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Rolex": [{"name": "Rolex", "hours": "11am – 9pm", "location": "Mall Level 2, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1663101199998-Retailer_plaque_240x120-us.jpg"}], "Romano's Macaroni Grill": [{"name": "Romano's Macaroni Grill", "hours": "11am – 9pm", "location": "Ho'okipa Terrace Upper Level 4, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Ross Dress For Less": [{"name": "Ross Dress For Less", "hours": "9am – 10pm", "location": "Street Level 1, near Old Navy", "logo": "https://placewise.imgix.net/images/api/stores/4071.svg"}], "Royale": [{"name": "Royale", "hours": "11am – 8pm", "location": "Mall Level 2", "logo": "https://placewise.imgix.net/images/api/retailhubjs/d2d9f93c7bff2b67fb21f755a295d64d"}], "Ruscello": [{"name": "Ruscello", "hours": "11am – 8pm", "location": "Inside Nordstrom", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Saint Laurent": [{"name": "Saint Laurent", "hours": "11am – 7pm", "location": "Mall Level 2, near Macy's", "logo": "https://assets.digitalservices.ggp.com/content/dam/ggp-digital-assets/Images/Tenant-Images/Tenant-Logos/saint-laurent-logo-v2.png/jcr:content/renditions/original.png"}], "Saks Fifth Avenue OFF 5TH": [{"name": "Saks Fifth Avenue OFF 5TH", "hours": "10am – 8pm", "location": "Anchor Store", "logo": "https://placewise.imgix.net/images/api/retailhubjs/b6635b648d8cd57d8dc8b58400a059f1.png"}], "Salvatore Ferragamo": [{"name": "Salvatore Ferragamo", "hours": "11am – 8pm", "location": "Mall Level 2, near Neiman Marcus", "logo": "https://s3.amazonaws.com/placewisesitecontent/images/api/stores/2137031822.svg"}], "San Lorenzo Bikinis": [{"name": "San Lorenzo Bikinis", "hours": "11am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/san-lorenzo-logo-553x260-v1.png"}], "Sears Appliances And Mattresses": [{"name": "Sears Appliances And Mattresses", "hours": "10am – 7pm", "location": "Street Level 1, near Saks Fifth Avenue OFF 5th", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1612300714395-Sears_Mattresses&AppliancesExteriorLogo_K.jpg"}], "Security Office": [{"name": "Security Office", "hours": "10am – 8pm", "location": "Street Level 1, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/242.svg"}], "See's Candies": [{"name": "See's Candies", "hours": "10am – 8pm", "location": "Street Level 1, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/242.svg"}], "Señor Pepé": [{"name": "Señor Pepé", "hours": "11am – 7pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1597707389497-senorpepe_logo.png"}], "Seoul Mix": [{"name": "Seoul Mix", "hours": "9am – 7pm", "location": "Mall Level 2, in The Lanai", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Sephora": [{"name": "Sephora", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/a11151fedd146744181733c3a955e72d.svg"}], "Sera's Surf 'N Shore": [{"name": "Sera's Surf 'N Shore", "hours": "11am – 6pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/shabuya-logo-553x260-v1.png"}], "Shabuya": [{"name": "Shabuya", "hours": "11am – 10pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/shabuya-logo-553x260-v1.png"}], "Sharetea": [{"name": "Sharetea", "hours": "10am – 7pm", "location": "Mall Level 2, in The Lanai", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Singmatei": [{"name": "Singmatei", "hours": "10am – 7pm", "location": "Street Level 1, in Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Skechers": [{"name": "Skechers", "hours": "10am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/4287.svg"}], "Somisomi Soft Serve & Taiyaki": [{"name": "Somisomi Soft Serve & Taiyaki", "hours": "11am – 8pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Spectrum": [{"name": "Spectrum", "hours": "10am – 6pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/fcec6ed13969051a0fcc73bc7eb1d445"}], "Sports Box": [{"name": "Sports Box", "hours": "10am – 8pm", "location": "Mall Level 2, near Target", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1648154654467-Sports%20Box%20Logo.JPG"}], "SP Shoe Palace": [{"name": "SP Shoe Palace", "hours": "10am – 8pm", "location": "Level 3, near Macy's", "logo": "https://placewise.imgix.net/images/api/retailhubjs/f956de69f13fb5833f76d01b4c38c249"}], "Squishable": [{"name": "Squishable", "hours": "10am – 8pm", "location": "Level 3, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/retailhubjs/292148fdfa40fb57e2a05ff5660350a5"}], "Starbucks": [{"name": "Starbucks", "hours": "10am – 7pm", "location": "Street Level 1, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}, {"name": "Starbucks", "hours": "10am – 7pm", "location": "Inside macy's", "logo": "https://placewise.imgix.net/images/api/stores/5170.svg"}, {"name": "Starbucks", "hours": "10am – 7pm", "location": "Inside Target", "logo": "https://placewise.imgix.net/images/api/stores/5170.svg"}], "Steak & Fish Company": [{"name": "Steak & Fish Company", "hours": "10am – 7pm", "location": "Street Level 1, in Makai Market Food Court", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1581818177874-Steak-and-Fish-Company-Logo.jpg"}], "Steak Teppei": [{"name": "Steak Teppei", "hours": "11am – 7pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Sugar Sugar Hawaii": [{"name": "Sugar Sugar Hawaii", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/584.svg"}], "Sunglass Hut": [{"name": "Sunglass Hut", "hours": "10am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "https://placewise.imgix.net/images/api/stores/584.svg"}, {"name": "Sunglass Hut", "hours": "8am – 6pm", "location": "Level 3, near Target", "logo": "https://placewise.imgix.net/images/api/stores/584.svg"}, {"name": "Sunglass Hut", "hours": "8am – 6pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/584.svg"}], "Sunrise Shack": [{"name": "Sunrise Shack", "hours": "8am – 6pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1622657582480-surfersbakery_logo%20(1).jpg"}], "Surfers Bakery": [{"name": "Surfers Bakery", "hours": "8am – 6pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1622657582480-surfersbakery_logo%20(1).jpg"}], "Swarovski": [{"name": "Swarovski", "hours": "11am – 7pm", "location": "Level 3, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/retailhubjs/8a9d9a28c3bb16f50cd4f075faad302d"}], "Swatch": [{"name": "Swatch", "hours": "10am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "https://placewise.imgix.net/images/api/retailhubjs/030b2d7cc3705f4f4945e31ebf8eb66d"}], "Sweet Honey Hawaii": [{"name": "Sweet Honey Hawaii", "hours": "11am – 3pm; 4pm – 9:30pm", "location": "Level 3, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Sxy Szechuan": [{"name": "Sxy Szechuan", "hours": "11am – 3pm; 4pm – 9:30pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "T&C Surf Designs": [{"name": "T&C Surf Designs", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/surf-designs-logo-553x260-v1.png"}], "Tanaka Of Tokyo": [{"name": "Tanaka Of Tokyo", "hours": "5pm – 9pm", "location": "Ho'okipa Terrace Upper Level 4, near Macy's", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Tanaka Ramen & Izakaya": [{"name": "Tanaka Ramen & Izakaya", "hours": "11am – 9:30pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Tanoa": [{"name": "Tanoa", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1652128897602-Logo%20and%20name%202.jpg"}], "Target": [{"name": "Target", "hours": "8am – 10pm", "location": "Anchor Store", "logo": "https://placewise.imgix.net/images/api/stores/2544.svg"}], "Teapresso Bar": [{"name": "Teapresso Bar", "hours": "10am – 7pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1662689771253-Teapresso%20Bar%20Logo.png"}], "Tech Armor": [{"name": "Tech Armor", "hours": "10am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://s3.amazonaws.com/placewisesitecontent/images/api/stores/2137046709.svg"}, {"name": "Tech Armor", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://s3.amazonaws.com/placewisesitecontent/images/api/stores/2137046709.svg"}], "Ted Baker London": [{"name": "Ted Baker London", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://s3.amazonaws.com/placewisesitecontent/images/api/stores/2137046709.svg"}], "Teppanyaki Farmer": [{"name": "Teppanyaki Farmer", "hours": "11am – 7pm", "location": "Mall Level 2, in The Lanai", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Territorial Savings Bank": [{"name": "Territorial Savings Bank", "hours": "9am – 5pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/territorial-bank-logo-553x260-v1.png"}], "Tesla": [{"name": "Tesla", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1581739732249-Tesla.svg"}], "Tiffany & Co.": [{"name": "Tiffany & Co.", "hours": "11am – 7pm", "location": "Mall Level 2, near Neiman Marcus", "logo": "https://placewise.imgix.net/images/api/stores/924.svg"}], "Tiger Sugar": [{"name": "Tiger Sugar", "hours": "11am – 9pm", "location": "Mall Level 2, near Target", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1650487282961-LOGO_直%20(1).png"}], "T-Mobile": [{"name": "T-Mobile", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/1250.svg"}], "Tod's": [{"name": "Tod's", "hours": "10am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1581817873976-Tods-Logo.jpg"}], "Tommy Bahama": [{"name": "Tommy Bahama", "hours": "10am – 8pm", "location": "Mall Level 2, near Target", "logo": "https://placewise.imgix.net/images/api/stores/02137020401.svg"}], "Tori Richard": [{"name": "Tori Richard", "hours": "11am – 7pm", "location": "Mall Level 2, near Macy's", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/tori-richard-logo-553x260-v1.png"}], "Tory Burch": [{"name": "Tory Burch", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/21370443810.svg"}], "TravisMathew": [{"name": "TravisMathew", "hours": "10am – 6pm", "location": "Mall Level 2, near Macy's", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1585179514507-True%20Friends%20logo.jpg"}], "True Friends": [{"name": "True Friends", "hours": "10am – 6pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1585179514507-True%20Friends%20logo.jpg"}], "TUMI": [{"name": "TUMI", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/3115.svg"}], "Two Palms": [{"name": "Two Palms", "hours": "11am – 7pm", "location": "Street Level 1, near Centerstage", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/curbside-white.svg"}], "U.S. Post Office": [{"name": "U.S. Post Office", "hours": "9am – 5pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/2137025526.svg"}], "Uniqlo": [{"name": "Uniqlo", "hours": "10am – 8pm", "location": "Mall Level 2, and Level 3 in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/2137047654.svg"}], "Valentino": [{"name": "Valentino", "hours": "11am – 7pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1582157782045-LOGO%20NEW%20JPG%20Valentino%20HIGH%20RES.gif"}], "Valextra": [{"name": "Valextra", "hours": "12pm – 6pm", "location": "Mall Level 2, near Neiman Marcus", "logo": "https://assets.digitalservices.ggp.com/content/dam/rw-2/images/tenant-images/tenant-logos/Valextra_Logo_553X260_v1.png#"}], "Vans": [{"name": "Vans", "hours": "11am – 7pm", "location": "Level 3, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/996.svg"}], "Victoria's Secret": [{"name": "Victoria's Secret", "hours": "10am – 8pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/588.svg"}], "Vim N\" Vigor Health & Fitness": [{"name": "Vim N\" Vigor Health & Fitness", "hours": "9am – 8pm", "location": "Street Level 1, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "The Vitamin Shoppe": [{"name": "The Vitamin Shoppe", "hours": "9am – 9pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/stores/2137029560.svg"}], "Volcanoecigs": [{"name": "Volcanoecigs", "hours": "11am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1602900084809-TTTAsset%202.png"}], "Volcom": [{"name": "Volcom", "hours": "11am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1602900084809-TTTAsset%202.png"}], "Wahlburgers": [{"name": "Wahlburgers", "hours": "10:30am – 10pm", "location": "Mall Level 2, in the Ewa Wing", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Wai Nani Gifts": [{"name": "Wai Nani Gifts", "hours": "10am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1611953046696-2020-09-03.jpg"}], "Waterline Designs": [{"name": "Waterline Designs", "hours": "10am – 8pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1611953046696-2020-09-03.jpg"}], "Windsor": [{"name": "Windsor", "hours": "10am – 8pm", "location": "Level 3, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1611953046696-2020-09-03.jpg"}], "Xtreme 7d Dark Rides": [{"name": "Xtreme 7d Dark Rides", "hours": "3pm – 8pm", "location": "Ho'okipa Terrace Upper Level 4, near Macy's", "logo": "https://placewise.imgix.net/images/api/retailhubjs/0ec8202cdca398abb3e040cfb1de64a4"}], "Yomie's Rice X Yogurt": [{"name": "Yomie's Rice X Yogurt", "hours": "3pm – 8pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Yummy Korean BBQ": [{"name": "Yummy Korean BBQ", "hours": "10:30am – 8pm", "location": "Street Level 1, near Makai Market Food Court", "logo": "/etc/clientlibs/ggpcorp-malls/assets/directory/carry-out.svg"}], "Zagu": [{"name": "Zagu", "hours": "11am – 9pm", "location": "Street Level 1, near Centerstage", "logo": "https://placewise.imgix.net/images/api/stores/2137039918.svg"}], "ZARA": [{"name": "ZARA", "hours": "10am – 8pm", "location": "Anchor Store", "logo": "https://placewise.imgix.net/images/api/stores/2137039918.svg"}], "Zumiez": [{"name": "Zumiez", "hours": "11am – 7pm", "location": "Level 3, in the Ewa Wing", "logo": "https://placewise.imgix.net/images/api/retailhubjs/ff8a1fa869af18da93c8cfae76448959.jpeg"}]} \ No newline at end of file diff --git a/request_handling.py b/request_handling.py index 4ba65cb..e193120 100644 --- a/request_handling.py +++ b/request_handling.py @@ -45,7 +45,7 @@ class RequestHandler(): caching_file = '' -def find_cached_stores(user_request: str, url, file_path): +def find_cached_stores(user_request: str, url, dir_path): """ Check shop name existence in cache keys Args: @@ -60,12 +60,13 @@ def find_cached_stores(user_request: str, url, file_path): {"name": "ABS stores", "time": "8am-10pm", "location": "2 level"} ] """ - caching_file = file_path+'/cached_stores.json' + caching_file = os.path.join(dir_path, 'mall_cache.json') if os.path.isfile(caching_file) == False: LOG.info("Cache file doesn't exist") - caching_stores_in_mall(file_path, url) - return find_cached_stores(user_request, url, file_path) + caching_stores_in_mall(caching_file, url) + return find_cached_stores(user_request, url, caching_file) else: + LOG.info("Cache file exists") with open(caching_file, 'r', encoding='utf-8') as readfile: data = json.load(readfile) found_key = [key for key in data.keys() @@ -75,12 +76,12 @@ def find_cached_stores(user_request: str, url, file_path): if len(found_key) >=1 : store_name = str(found_key[0]) LOG.info(f'Shop exists {data[store_name]}') - return data[store_name], data + return data[store_name] else: LOG.info("Shop doesn't exist in cache") - return None, data + return None -def caching_stores_in_mall(file_path, url): +def caching_stores_in_mall(caching_file, url): """ Creates caching file in the current class. Creates empty dictionary for cache. Parses @@ -100,7 +101,6 @@ def caching_stores_in_mall(file_path, url): {"name": "ABS stores", "time": "8am-10pm", "location": "2 level"} ]} """ - caching_file = file_path+'/cached_stores.json' LOG.info(f'caching_file {caching_file}') shop_cache = {} soup = parse(url) @@ -205,7 +205,7 @@ def parse(url): LOG.info("Failed url parsing") -def get_shop_data(url, user_request, file_path): +def get_shop_data(url, user_request, dir_path): """ Check existence of user's request store in cache if shop was found returns list with shop info, @@ -222,8 +222,7 @@ def get_shop_data(url, user_request, file_path): : found_shops (list): found shops' info """ # search for store existence in cache - LOG.info(file_path) - found_shops, data = find_cached_stores(user_request, url, file_path) + found_shops = find_cached_stores(user_request, url, dir_path) LOG.info(found_shops) if found_shops: LOG.info(f"found_shops: {found_shops}") diff --git a/skill/test_skill.py b/skill/test_skill.py deleted file mode 100644 index 83b48be..0000000 --- a/skill/test_skill.py +++ /dev/null @@ -1,134 +0,0 @@ -# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework -# All trademark and other rights reserved by their respective owners -# Copyright 2008-2022 Neongecko.com Inc. -# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, -# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo -# BSD-3 License -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from this -# software without specific prior written permission. -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import shutil -import unittest - -from os import mkdir -from os.path import dirname, join, exists -from mock import Mock -from ovos_utils.messagebus import FakeBus - -from mycroft.skills.skill_loader import SkillLoader - -from mycroft_bus_client import Message - -# from .time_calculations_handling import current_time_extraction,\ -# time_calculation - - -class TestSkill(unittest.TestCase): - - @classmethod - def setUpClass(cls) -> None: - bus = FakeBus() - bus.run_in_thread() - skill_loader = SkillLoader(bus, dirname(dirname(__file__))) - skill_loader.load() - cls.skill = skill_loader.instance - - # Define a directory to use for testing - cls.test_fs = join(dirname(__file__), "skill") - if not exists(cls.test_fs): - mkdir(cls.test_fs) - - # Override the configuration and fs paths to use the test directory - cls.skill.settings_write_path = cls.test_fs - cls.skill.file_system.path = cls.test_fs - cls.skill._init_settings() - cls.skill.initialize() - - # Override speak and speak_dialog to test passed arguments - cls.skill.speak = Mock() - cls.skill.speak_dialog = Mock() - - # TODO: Put any skill method overrides here - - def setUp(self): - self.skill.speak.reset_mock() - self.skill.speak_dialog.reset_mock() - - # TODO: Put any cleanup here that runs before each test case - - @classmethod - def tearDownClass(cls) -> None: - shutil.rmtree(cls.test_fs) - - def test_en_skill_init(self): - self.skill.ask_yesno = Mock(return_value="yes") - self.skill.gui._pages2uri = Mock() - self.skill._start_mall_parser_prompt( - Message('test', {'utterance': 'find Apple', - 'shop': 'Apple', - 'lang': 'en-us'}, - {'context_key': 'MallParsing'}) - ) - - message = Message('test', {'utterance': 'find ABC stores', - 'shop': 'ABC stores', - 'lang': 'en-us'}, - {'context_key': 'MallParsing'}) - self.skill.user_request_handling(message) - - def test_find_shop(self): - mall_link = "https://www.alamoanacenter.com/en/directory/" - - user_request = 'starbucks' - new_count, user_request = self.skill.find_shop(user_request, mall_link) - self.assertEqual(new_count, 3) - - user_request = 'bubble' - new_count, user_request = self.skill.find_shop(user_request, mall_link) - self.assertEqual(new_count, 1) - - - # def test_en_time_extraction(self): - # shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, - # {'name': 'ABC Stores', 'hours': '10am – 8pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}] - # day_time, hour, min = ['10:15', 'am'], 10, 15 - # result_shops = time_calculation(shop_info, day_time, hour, min) - # self.assertEqual(shop_info, result_shops) - - # day_time, hour, min = ['9:15', 'pm'], 9, 15 - # result_shops = self.skill.open_shops_search(shop_info, day_time, hour, min) - # self.assertEqual(shop_info[0], result_shops[0]) - - # def test_en_time_extraction(self): - # shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, - # {'name': 'ABC Stores', 'hours': '10am – 8pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}, - # {'name': 'ABC Stores', 'hours': '10am – 9pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}] - - # day_time, hour, min = ['9:15', 'pm'], 9, 15 - # print(self.skill.time_calculation(shop_info, False, day_time, hour, min)) - - # day_time, hour, min = ['8:15', 'pm'], 8, 15 - # print(self.skill.time_calculation(shop_info, False, day_time, hour, min)) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/test_skill.py b/test/test_skill.py index 3bc7e29..852d625 100644 --- a/test/test_skill.py +++ b/test/test_skill.py @@ -67,6 +67,9 @@ def setUpClass(cls) -> None: cls.skill.speak = Mock() cls.skill.speak_dialog = Mock() + # Override gui show to test passed arguments + cls.skill.gui.show_image = Mock() + # TODO: Put any skill method overrides here def setUp(self): @@ -81,7 +84,6 @@ def tearDownClass(cls) -> None: def test_en_skill_init(self): self.skill.ask_yesno = Mock(return_value="yes") - self.skill.gui._pages2uri = Mock() self.skill._start_mall_parser_prompt( Message('test', {'utterance': 'find Apple', 'shop': 'Apple', diff --git a/time_calculations_handling.py b/time_calculations_handling.py index 0315759..f07cfbd 100644 --- a/time_calculations_handling.py +++ b/time_calculations_handling.py @@ -91,7 +91,6 @@ def time_calculation(shop_info, day_time, hour, min): open_time = int(normalized_time[0]) close_time = int(normalized_time[1]) LOG.info(f'work_time {work_time}') - LOG.info(f'open_time {open_time}, close_time {close_time}') # time left wait_h = open_time - hour - 1 wait_min = 60 - min From edbc99e0af9fe30fecbfa17dee30617128d3a7e8 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Wed, 5 Oct 2022 11:54:20 -0400 Subject: [PATCH 08/14] small fixes --- __init__.py | 2 +- locale/en-us/dialog/en/another_shop.dialog | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/__init__.py b/__init__.py index 6d6cda8..927dbf0 100644 --- a/__init__.py +++ b/__init__.py @@ -235,7 +235,7 @@ def find_shop(self, user_request, mall_link): LOG.info('Location sorting selected') if shops_on_the_floor: self.speak_dialog('shops_on_user_floor', {'n': len(shops_on_the_floor), - 'store_name': shop_info[0]["name"]}) + 'store_name': shop_info[0][2]["name"]}) for shop in shops_on_the_floor: self.speak_in_time_order(shop) if len(shops_on_the_floor) != len(shop_info): diff --git a/locale/en-us/dialog/en/another_shop.dialog b/locale/en-us/dialog/en/another_shop.dialog index 0967824..932bcda 100644 --- a/locale/en-us/dialog/en/another_shop.dialog +++ b/locale/en-us/dialog/en/another_shop.dialog @@ -1 +1 @@ -What store you are looking for? \ No newline at end of file +What store are you looking for? \ No newline at end of file From 818d2a5e225e6b166749799220d890d418b112e3 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Wed, 5 Oct 2022 12:07:52 -0400 Subject: [PATCH 09/14] updated skill name, tests cleaning --- README.md | 6 +++--- skill.json | 8 ++++---- test/skill/settings.json | 3 +++ test/test_skill.py | 28 ---------------------------- 4 files changed, 10 insertions(+), 35 deletions(-) create mode 100644 test/skill/settings.json diff --git a/README.md b/README.md index eed73de..9d22bd5 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# {Malls parser skill neon} +# {Mall guide skill neon} ## Summary -Skill for mall parsing +Skill for guiding around the mall. ## Description -Skill parses mall web page and returns user name, location and work hours of requested shop, store +Skill parses mall web page and returns user: name, location and work hours of requested shop, store ## Examples - where is apple? diff --git a/skill.json b/skill.json index 82862a5..fb9ed21 100644 --- a/skill.json +++ b/skill.json @@ -1,9 +1,9 @@ { - "title": "{Malls parser skill neon}", + "title": "{Malls guide skill neon}", "url": "https://github.com/NeonGeckoCom/mall_guide_skill", - "summary": "Skill for mall parsing", - "short_description": "Skill for mall parsing", - "description": "Skill parses mall web page and returns user name, location and work hours of requested shop, store", + "summary": "Skill for mall guiding", + "short_description": "Guides user inside the mall", + "description": "Skill parses mall web page and returns user: name, location and work hours of requested shop, store", "examples": [ "where is apple?", "where can i find abc stores?" diff --git a/test/skill/settings.json b/test/skill/settings.json new file mode 100644 index 0000000..9984b6f --- /dev/null +++ b/test/skill/settings.json @@ -0,0 +1,3 @@ +{ + "prompt_on_start": "true" +} \ No newline at end of file diff --git a/test/test_skill.py b/test/test_skill.py index 852d625..cdf2cd3 100644 --- a/test/test_skill.py +++ b/test/test_skill.py @@ -38,9 +38,6 @@ from mycroft_bus_client import Message -# from .time_calculations_handling import current_time_extraction,\ -# time_calculation - class TestSkill(unittest.TestCase): @@ -70,14 +67,10 @@ def setUpClass(cls) -> None: # Override gui show to test passed arguments cls.skill.gui.show_image = Mock() - # TODO: Put any skill method overrides here - def setUp(self): self.skill.speak.reset_mock() self.skill.speak_dialog.reset_mock() - # TODO: Put any cleanup here that runs before each test case - @classmethod def tearDownClass(cls) -> None: shutil.rmtree(cls.test_fs) @@ -108,27 +101,6 @@ def test_find_shop(self): new_count, user_request = self.skill.find_shop(user_request, mall_link) self.assertEqual(new_count, 1) - # def test_en_time_extraction(self): - # shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, - # {'name': 'ABC Stores', 'hours': '10am – 8pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}] - # day_time, hour, min = ['10:15', 'am'], 10, 15 - # result_shops = time_calculation(shop_info, day_time, hour, min) - # self.assertEqual(shop_info, result_shops) - - # day_time, hour, min = ['9:15', 'pm'], 9, 15 - # result_shops = self.skill.open_shops_search(shop_info, day_time, hour, min) - # self.assertEqual(shop_info[0], result_shops[0]) - - # def test_en_time_extraction(self): - # shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, - # {'name': 'ABC Stores', 'hours': '10am – 8pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}, - # {'name': 'ABC Stores', 'hours': '10am – 9pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}] - - # day_time, hour, min = ['9:15', 'pm'], 9, 15 - # print(self.skill.time_calculation(shop_info, False, day_time, hour, min)) - - # day_time, hour, min = ['8:15', 'pm'], 8, 15 - # print(self.skill.time_calculation(shop_info, False, day_time, hour, min)) if __name__ == '__main__': From c775e7dabb9cd3a6d515d125a4e4eeebd4cf8c93 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Wed, 5 Oct 2022 16:26:35 +0000 Subject: [PATCH 10/14] Update skill.json --- skill.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skill.json b/skill.json index fb9ed21..3fd5643 100644 --- a/skill.json +++ b/skill.json @@ -1,8 +1,8 @@ { - "title": "{Malls guide skill neon}", + "title": "{Mall guide skill neon}", "url": "https://github.com/NeonGeckoCom/mall_guide_skill", - "summary": "Skill for mall guiding", - "short_description": "Guides user inside the mall", + "summary": "Skill for guiding around the mall.", + "short_description": "Skill for guiding around the mall.", "description": "Skill parses mall web page and returns user: name, location and work hours of requested shop, store", "examples": [ "where is apple?", From 2997e54e38d7e5448722be84e772fcc6f9ba8328 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Wed, 5 Oct 2022 12:29:27 -0400 Subject: [PATCH 11/14] finish dialogs changes --- __init__.py | 2 +- locale/en-us/dialog/en/finished.dialog | 2 +- locale/en-us/dialog/en/no_shop_request.dialog | 1 - test/skill/settings.json | 3 --- 4 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 locale/en-us/dialog/en/no_shop_request.dialog delete mode 100644 test/skill/settings.json diff --git a/__init__.py b/__init__.py index 927dbf0..9dceca7 100644 --- a/__init__.py +++ b/__init__.py @@ -116,7 +116,7 @@ def start_again(self): LOG.info(f'another shop {another_shop}') return another_shop elif start_again == "no": - self.speak_dialog('no_shop_request') + return None else: self.speak_dialog('unexpected_error') return None diff --git a/locale/en-us/dialog/en/finished.dialog b/locale/en-us/dialog/en/finished.dialog index 17ed73d..d570316 100644 --- a/locale/en-us/dialog/en/finished.dialog +++ b/locale/en-us/dialog/en/finished.dialog @@ -1 +1 @@ -Finished. Goodbye! \ No newline at end of file +Ok. Goodbye! \ No newline at end of file diff --git a/locale/en-us/dialog/en/no_shop_request.dialog b/locale/en-us/dialog/en/no_shop_request.dialog deleted file mode 100644 index 6ca56f4..0000000 --- a/locale/en-us/dialog/en/no_shop_request.dialog +++ /dev/null @@ -1 +0,0 @@ -Okay. I will stop mall parsing. \ No newline at end of file diff --git a/test/skill/settings.json b/test/skill/settings.json deleted file mode 100644 index 9984b6f..0000000 --- a/test/skill/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "prompt_on_start": "true" -} \ No newline at end of file From a35f0fd12b22f75da7793b00553d1c463cd62863 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Thu, 6 Oct 2022 04:32:05 -0400 Subject: [PATCH 12/14] dialog fixes --- __init__.py | 7 +++++-- locale/en-us/dialog/en/another_shop_locations.dialog | 3 ++- locale/en-us/dialog/en/more_shops_info.dialog | 2 +- locale/en-us/dialog/en/shops_amount.dialog | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/__init__.py b/__init__.py index 9dceca7..58747ff 100644 --- a/__init__.py +++ b/__init__.py @@ -211,7 +211,7 @@ def find_shop(self, user_request, mall_link): return 1, user_request elif len(shop_info) > 2: LOG.info(f"more_than_two: n = {len(shop_info)}, store {shop_info[0]['name']}") - self.speak_dialog('shops_amount', {'n': len(shop_info), 'store_name': shop_info[0]["name"]}) + self.speak_dialog('shops_amount', {'n': len(shop_info), 'loc_amount': 'locations', 'store_name': shop_info[0][2]["name"]}) # contains list of open and closed shops shop_info = time_calculation(shop_info, day_time, hour, min) # collect list of shops on user's floor @@ -259,7 +259,10 @@ def find_shop(self, user_request, mall_link): else: LOG.info(f"found shop/s {shop_info}") shop_info = time_calculation(shop_info, day_time, hour, min) - self.speak_dialog('shops_amount', {'n': len(shop_info), 'store_name': shop_info[0][2]["name"]}) + if len(shop_info) == 1: + self.speak_dialog('shops_amount', {'n': len(shop_info), 'loc_amount': 'location', 'store_name': shop_info[0][2]["name"]}) + else: + self.speak_dialog('shops_amount', {'n': len(shop_info), 'loc_amount': 'locations', 'store_name': shop_info[0][2]["name"]}) LOG.info(f'shop info after time calculation {shop_info}') for shop in shop_info: self.speak_in_time_order(shop) diff --git a/locale/en-us/dialog/en/another_shop_locations.dialog b/locale/en-us/dialog/en/another_shop_locations.dialog index a7b68f8..0670503 100644 --- a/locale/en-us/dialog/en/another_shop_locations.dialog +++ b/locale/en-us/dialog/en/another_shop_locations.dialog @@ -1 +1,2 @@ -Another locations. \ No newline at end of file +This store has more locations. +There are more locations. \ No newline at end of file diff --git a/locale/en-us/dialog/en/more_shops_info.dialog b/locale/en-us/dialog/en/more_shops_info.dialog index 7d08729..e9d0603 100644 --- a/locale/en-us/dialog/en/more_shops_info.dialog +++ b/locale/en-us/dialog/en/more_shops_info.dialog @@ -1 +1 @@ -Do you want to get other {{shop_name}} stores info? \ No newline at end of file +Do you want to get other {{shop_name}} locations info? \ No newline at end of file diff --git a/locale/en-us/dialog/en/shops_amount.dialog b/locale/en-us/dialog/en/shops_amount.dialog index 013542b..2f4035c 100644 --- a/locale/en-us/dialog/en/shops_amount.dialog +++ b/locale/en-us/dialog/en/shops_amount.dialog @@ -1 +1 @@ -I found {{n}} {{store_name}}. \ No newline at end of file +I found {{n}} {{loc_amount}} for {{store_name}}. \ No newline at end of file From d775df302a9394d93f13de17629cabb1d18a0463 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Thu, 6 Oct 2022 04:41:30 -0400 Subject: [PATCH 13/14] small fixes --- __init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index 58747ff..0631755 100644 --- a/__init__.py +++ b/__init__.py @@ -135,9 +135,9 @@ def speak_shops(self, shop): location = location_format(shop['location']) hours = re.sub('(\d+)am.+(\d+)pm', r'from \1 A M to \2 P M', shop['hours']) if 'level' in location.lower(): - self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location, "on": 'on', "open": shop['open']}) + self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "loc_amount": location, "on": 'on', "open": shop['open']}) else: - self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location, "on": '', "open": shop['open']}) + self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "loc_amount": location, "on": '', "open": shop['open']}) self.gui.show_image(shop['logo'], caption=f'{hours} {location}', title=shop['name']) @@ -211,7 +211,7 @@ def find_shop(self, user_request, mall_link): return 1, user_request elif len(shop_info) > 2: LOG.info(f"more_than_two: n = {len(shop_info)}, store {shop_info[0]['name']}") - self.speak_dialog('shops_amount', {'n': len(shop_info), 'loc_amount': 'locations', 'store_name': shop_info[0][2]["name"]}) + self.speak_dialog('shops_amount', {'n': len(shop_info), 'loc_amount': 'locations', 'store_name': shop_info[0]["name"]}) # contains list of open and closed shops shop_info = time_calculation(shop_info, day_time, hour, min) # collect list of shops on user's floor From ef12b1e51d77082e99fa9b1ae674fad1635c7fed Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Thu, 6 Oct 2022 11:51:09 -0400 Subject: [PATCH 14/14] small fixes --- __init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__init__.py b/__init__.py index 0631755..1c96346 100644 --- a/__init__.py +++ b/__init__.py @@ -135,9 +135,9 @@ def speak_shops(self, shop): location = location_format(shop['location']) hours = re.sub('(\d+)am.+(\d+)pm', r'from \1 A M to \2 P M', shop['hours']) if 'level' in location.lower(): - self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "loc_amount": location, "on": 'on', "open": shop['open']}) + self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location, "on": 'on', "open": shop['open']}) else: - self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "loc_amount": location, "on": '', "open": shop['open']}) + self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location, "on": '', "open": shop['open']}) self.gui.show_image(shop['logo'], caption=f'{hours} {location}', title=shop['name'])