From d8fe6012b425bebb881abbec953cbe81aae3aadc Mon Sep 17 00:00:00 2001 From: Vegar Andreas Bergum Date: Tue, 27 Aug 2019 14:15:51 +0200 Subject: [PATCH 01/11] refr: updated formatting in api models --- chatbot/api/v2/api.py | 49 +++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/chatbot/api/v2/api.py b/chatbot/api/v2/api.py index 419e0c2..6380207 100644 --- a/chatbot/api/v2/api.py +++ b/chatbot/api/v2/api.py @@ -40,30 +40,30 @@ }) keyword_model = api.model('Keyword', { - 'keyword': fields.String, - 'confidence': fields.Float + 'keyword': fields.String, + 'confidence': fields.Float }) inner_content_model = api.model('InnerContent', { - 'title': fields.String, - 'keywords': fields.List(fields.Nested(keyword_model)), - 'texts': fields.List(fields.String) + 'title': fields.String, + 'keywords': fields.List(fields.Nested(keyword_model)), + 'texts': fields.List(fields.String) }) content_model = api.model('Content', { - 'id': fields.String, - 'url': fields.String, - 'content': fields.Nested(inner_content_model) + 'id': fields.String, + 'url': fields.String, + 'content': fields.Nested(inner_content_model) }) content_collection_model = api.model('ContentCol', { - 'prod': fields.Nested(content_model), - 'manual': fields.Nested(content_model), - 'url': fields.String + 'prod': fields.Nested(content_model), + 'manual': fields.Nested(content_model), + 'url': fields.String }) unknown_query_model = api.model('UnknownQuery', { - 'query_text': fields.String + 'query_text': fields.String }) @@ -223,34 +223,23 @@ def delete(self, unknown_query): abort(404, 'Unknown query not found') -api.add_resource(HelloWorld, - '/', - methods=['GET']) +api.add_resource(HelloWorld, '/', methods=['GET']) -api.add_resource(Response, - '/response//', - methods=['GET']) -api.add_resource(FullResponse, - '/response/', - methods=['GET']) +api.add_resource(Response, '/response//', methods=['GET']) -api.add_resource(ConflictIDs, - '/conflict_ids/', - methods=['GET']) +api.add_resource(FullResponse, '/response/', methods=['GET']) + +api.add_resource(ConflictIDs, '/conflict_ids/', methods=['GET']) api.add_resource(ConflictIDs, '/conflict_ids//', methods=['DELETE']) -api.add_resource(Contents, - '/contents/', - methods=['GET']) +api.add_resource(Contents, '/contents/', methods=['GET']) api.add_resource(Content, '/content//', methods=['GET', 'PUT', 'DELETE']) -api.add_resource(UnknownQueries, - '/unknown_queries/', - methods=['GET']) +api.add_resource(UnknownQueries, '/unknown_queries/', methods=['GET']) api.add_resource(UnknownQueries, '/unknown_queries//', methods=['DELETE']) From 7bb984ee777757bba98903fb4e63a3f009f93769 Mon Sep 17 00:00:00 2001 From: Vegar Andreas Bergum Date: Tue, 27 Aug 2019 14:16:06 +0200 Subject: [PATCH 02/11] refr: extended model factory tests --- chatbot/launch.py | 2 +- chatbot/model/test/test_model_factory.py | 31 +++++++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/chatbot/launch.py b/chatbot/launch.py index 5e48111..facf83c 100644 --- a/chatbot/launch.py +++ b/chatbot/launch.py @@ -66,7 +66,7 @@ def insert_documents(data): "title": title}) print("Conflicts: {}".format(conflicts)) - factory.get_collection(conflict_col).create_index([("id", 1)], + factory.get_collection(conflict_col).create_index([("title", 1)], unique=True) for conflict in conflicts: try: diff --git a/chatbot/model/test/test_model_factory.py b/chatbot/model/test/test_model_factory.py index 4192f86..6eeeaf9 100644 --- a/chatbot/model/test/test_model_factory.py +++ b/chatbot/model/test/test_model_factory.py @@ -15,7 +15,12 @@ def test_get_document(): fact.get_database().drop_collection("test") try: - fact.post_document(data[0], "test") + test_col = fact.get_database().get_collection('test') + + # Insert data + for d in data: + test_col.insert_one(d) + fact.post_document(data[1], "test") fact.get_collection("test").create_index( [("keywords", pymongo.TEXT), @@ -46,13 +51,13 @@ def test_get_document(): def test_update_document(): - data = '{"name": "testname", "manually_changed": false }' + data = {"name": "testname", "manually_changed": False} try: - fact.post_document(data, "test") + fact.get_database().get_collection('test').insert_one(data) fact.get_collection("test").create_index( [("name", pymongo.TEXT)], default_language="norwegian") - newdata = '{"name": "nottestname"}' + newdata = {"name": "nottestname"} fact.update_document({"name": "testname"}, newdata, "test") doc = fact.get_document("nottestname", prod_col="test")[0] @@ -60,3 +65,21 @@ def test_update_document(): finally: fact.get_database().drop_collection("test") + + +def test_delete_document(): + idx = '#321_test_delete_id' + doc = {'id': idx, 'data': 'some data to be deleted'} + try: + fact.get_database().get_collection('test').insert_one(doc) + + query = {'id': idx} + fact.delete_document(query, 'test') + + response_data = next(fact.get_database() + .get_collection('test') + .find(query), None) + assert response_data is None + + finally: + fact.get_database().drop_collection('test') From 564141903f15f27639d838f80d07cba352f11e26 Mon Sep 17 00:00:00 2001 From: Vegar Andreas Bergum Date: Tue, 27 Aug 2019 14:40:09 +0200 Subject: [PATCH 03/11] fix: start scrape on docker startup --- scripts/start_server_docker.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/start_server_docker.sh b/scripts/start_server_docker.sh index 3ad8565..783b462 100755 --- a/scripts/start_server_docker.sh +++ b/scripts/start_server_docker.sh @@ -1,4 +1,5 @@ #!/bin/sh cron #cd .. +./scripts/launch.sh ./chatbot/api/start_server.sh From b72c83fc3582d9185bb149e779f5c52265759172 Mon Sep 17 00:00:00 2001 From: Vegar Andreas Bergum Date: Tue, 27 Aug 2019 15:57:07 +0200 Subject: [PATCH 04/11] fix: website local url --- chatbot/web/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chatbot/web/Dockerfile b/chatbot/web/Dockerfile index 1acd3eb..18446ae 100644 --- a/chatbot/web/Dockerfile +++ b/chatbot/web/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /temp ADD package*.json ./ RUN npm install -ENV REACT_APP_SERVER_URL="localhost:8080" +ENV REACT_APP_SERVER_URL="http://localhost:8080/" ADD . ./ From 6c2815fb1918c9f1b25b23522068da19b2f97a1c Mon Sep 17 00:00:00 2001 From: Vegar Andreas Bergum Date: Wed, 28 Aug 2019 09:18:37 +0200 Subject: [PATCH 05/11] fix: CORS requests over v2 response --- chatbot/chat/scripts/chat.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/chatbot/chat/scripts/chat.js b/chatbot/chat/scripts/chat.js index b19aac5..6732edf 100644 --- a/chatbot/chat/scripts/chat.js +++ b/chatbot/chat/scripts/chat.js @@ -33,18 +33,19 @@ function query() { populateChatWithMessage(input, "user") - http.open("POST", host+"v1/dialogflow/response", true); - http.setRequestHeader("Content-Type", "text/plain"); + http.open("GET", host+"v2/response/"+input+"/", true); + http.setRequestHeader("Content-Type", "text/plain"); http.onreadystatechange = function() { if(this.readyState == 4 && this.status == 200) { var json = JSON.parse(http.responseText); - var response = json.fulfillmentText; + var response = json.response; response = response.replace(/\n/g, '
'); populateChatWithMessage(response, "bot"); } } - var data = JSON.stringify({"queryResult": { "queryText": input }}); - http.send(data); + http.send(); + //var data = JSON.stringify({"queryResult": { "queryText": input }}); + //http.send(data); } /* From accc07af239300d57aed57d71eb60aa3402a79c9 Mon Sep 17 00:00:00 2001 From: Vegar Andreas Bergum Date: Wed, 28 Aug 2019 10:18:52 +0200 Subject: [PATCH 06/11] fix: local URL reference in web app --- chatbot/web/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chatbot/web/Dockerfile b/chatbot/web/Dockerfile index 18446ae..fc5ced2 100644 --- a/chatbot/web/Dockerfile +++ b/chatbot/web/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /temp ADD package*.json ./ RUN npm install -ENV REACT_APP_SERVER_URL="http://localhost:8080/" +ENV REACT_APP_SERVER_URL="http://$SERVER_URL/" ADD . ./ From bd51e273a289ef75375918ca7c9e8bf0b0525b95 Mon Sep 17 00:00:00 2001 From: Vegar Andreas Bergum Date: Wed, 28 Aug 2019 11:07:46 +0200 Subject: [PATCH 07/11] fix: server IP reference in container ENV --- chatbot/web/Dockerfile | 2 +- docker-compose.yml | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/chatbot/web/Dockerfile b/chatbot/web/Dockerfile index fc5ced2..e4300d7 100644 --- a/chatbot/web/Dockerfile +++ b/chatbot/web/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /temp ADD package*.json ./ RUN npm install -ENV REACT_APP_SERVER_URL="http://$SERVER_URL/" +ENV REACT_APP_SERVER_URL="http://${SERVER_URL}/" ADD . ./ diff --git a/docker-compose.yml b/docker-compose.yml index f42f00d..ab49eef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,12 +20,12 @@ services: image: mongo:latest container_name: "mongodb" environment: - - MONGO_DATA_DIR=/data/db - - MONGO_LOG_DIR=/dev/null - - MONGODB_USER=${DB_USER} - - MONGODB_PASS=${DB_PWD} + - MONGO_DATA_DIR=/data/db + - MONGO_LOG_DIR=/dev/null + - MONGODB_USER=${DB_USER} + - MONGODB_PASS=${DB_PWD} volumes: - - mongodb:/data/db + - mongodb:/data/db ports: - 27017:27017 command: mongod --logpath=/dev/null @@ -38,9 +38,9 @@ services: command: - /scripts/entrypoint_db.sh environment: - - DB_USER=${DB_USER} - - DB_PWD=${DB_PWD} - - DB_NAME=prod_chatbot + - DB_USER=${DB_USER} + - DB_PWD=${DB_PWD} + - DB_NAME=prod_chatbot links: - mongodb:mongodb depends_on: @@ -52,6 +52,8 @@ services: - 8000:80 volumes: - .:/srv/chatbot-web + environment: + - SERVER_URL=${SERVER_URL} working_dir: /usr/src/app volumes: From 30ab43d8f11c4bd58c13a1e6bdd24bcf320e15c1 Mon Sep 17 00:00:00 2001 From: Vegar Andreas Bergum Date: Thu, 29 Aug 2019 14:19:01 +0200 Subject: [PATCH 08/11] feat #10: query system and scraper updated to work with new url patterns --- chatbot/model/serializer.py | 19 +++---- chatbot/model/test/test_data/test_data.json | 2 +- .../test/test_data/test_data_serialized.json | 2 +- chatbot/model/test/test_serializer.py | 10 ++-- chatbot/nlp/query.py | 53 +++++++++++++------ chatbot/nlp/test/evaluation.py | 2 +- .../scraper/spiders/info_gathering_spider.py | 10 ++-- chatbot/scraper/test/test.html | 2 +- chatbot/scraper/test/test_html.json | 2 +- chatbot/scraper/test/test_spider.py | 26 +++++---- chatbot/settings.json | 2 +- 11 files changed, 76 insertions(+), 54 deletions(-) diff --git a/chatbot/model/serializer.py b/chatbot/model/serializer.py index af931fa..803dce3 100644 --- a/chatbot/model/serializer.py +++ b/chatbot/model/serializer.py @@ -10,10 +10,6 @@ class KeyWord: """ Keyword to fill keyword-list in model schema contents-list """ - - __word = None - __confidence = None - def __init__(self, word, confidence): self.__word = word self.__confidence = confidence @@ -24,14 +20,10 @@ def get_keyword(self): class Content: """ Content to fill contents-list in model schema """ - - __title = "" - __keywords = [] - __texts = [] - - def __init__(self, title, texts, keywords=[]): + def __init__(self, title, text, links=[], keywords=[]): self.__title = title - self.__texts = texts + self.__text = text + self.__links = links if keywords: if not all(isinstance(keyword, KeyWord) for keyword in keywords): @@ -43,7 +35,8 @@ def get_content(self): return { "title": self.__title, "keywords": [keyword.get_keyword() for keyword in self.__keywords], - "texts": self.__texts, + "text": self.__text, + "links": self.__links } def __repr__(self): @@ -130,7 +123,7 @@ def visit_node(self, data, model_template, models, title=None): .format(title, child["text"]))] - content = Content(title, [child["text"]], keywords) + content = Content(title, child["text"], child["links"], keywords) new_model = copy.deepcopy(model_template) new_model["id"] = child["id"] new_model["content"] = content.get_content() diff --git a/chatbot/model/test/test_data/test_data.json b/chatbot/model/test/test_data/test_data.json index 9aa584b..d01c38c 100644 --- a/chatbot/model/test/test_data/test_data.json +++ b/chatbot/model/test/test_data/test_data.json @@ -1 +1 @@ -[{"url":"https://ntnu.no","tree":{"tag":"root","text":"root","id":"391","children":[{"tag":"meta","text":"revisjon,forvaltningsrevisjon","id":"123"},{"tag":"p","text":"emner2","id":"832"},{"tag":"h3","text":"mange bra kommune emner22","id":"542","children":[{"tag":"p","text":"Informatikk prosjektarbeid 2 er et kjempe bra emne!2","id":"654"}]}]}}] +[{"url":"https://ntnu.no","tree":{"tag":"root","text":"root","id":"391","children":[{"tag":"meta","text":"revisjon,forvaltningsrevisjon","id":"123","links":[]},{"tag":"p","text":"emner2","id":"832","links":[]},{"tag":"h3","text":"mange bra kommune emner22","id":"542","children":[{"tag":"p","text":"Informatikk prosjektarbeid 2 er et kjempe bra emne!2","id":"654","links":[["emne","https://emne.no"]]}]}]}}] diff --git a/chatbot/model/test/test_data/test_data_serialized.json b/chatbot/model/test/test_data/test_data_serialized.json index 2b991b6..dc4bc89 100644 --- a/chatbot/model/test/test_data/test_data_serialized.json +++ b/chatbot/model/test/test_data/test_data_serialized.json @@ -1 +1 @@ -[{"id":"832","title":"","description":"","url":"https://ntnu.no","last_modified":"","header_meta_keywords":["revisjon","forvaltningsrevisjon"],"keywords":[],"content":{"title":"","keywords":[],"texts":["emner2"]},"manually_changed":false},{"title":"","description":"","url":"https://ntnu.no","last_modified":"","header_meta_keywords":["revisjon","forvaltningsrevisjon"],"keywords":[],"content":{"title":"mange bra kommune emner22","keywords":[],"texts":["Informatikk prosjektarbeid 2 er et kjempe bra emne!2"]},"manually_changed":false}] +[{"id":"832","title":"","description":"","url":"https://ntnu.no","last_modified":"","header_meta_keywords":["revisjon","forvaltningsrevisjon"],"keywords":[],"content":{"title":"","keywords":[],"text":"emner2","links":[]},"manually_changed":false},{"title":"","description":"","url":"https://ntnu.no","last_modified":"","header_meta_keywords":["revisjon","forvaltningsrevisjon"],"keywords":[],"content":{"title":"mange bra kommune emner22","keywords":[],"text":"Informatikk prosjektarbeid 2 er et kjempe bra emne!2","links":[["emne","https://emne.com"]]},"manually_changed":false}] diff --git a/chatbot/model/test/test_serializer.py b/chatbot/model/test/test_serializer.py index cd7e385..7581f5f 100644 --- a/chatbot/model/test/test_serializer.py +++ b/chatbot/model/test/test_serializer.py @@ -17,12 +17,12 @@ def test_serialize_data(): assert test_data[0]["url"] == serialized_data[0]["url"] assert test_data[0]["id"] == serialized_data[0]["id"] assert ( - test_data[0]["content"]["texts"] - == serialized_data[0]["content"]["texts"] + test_data[0]["content"]["text"] + == serialized_data[0]["content"]["text"] ) assert ( - test_data[1]["content"]["texts"] - == serialized_data[1]["content"]["texts"] + test_data[1]["content"]["text"] + == serialized_data[1]["content"]["text"] ) assert ( test_data[0]["header_meta_keywords"][0] @@ -43,7 +43,7 @@ def test_instance_of_KeyWord(): Content( "Åpningstider", "Svømmehallen er åpen alle dager 09:00-20:00", - [["svømme", 0.82], ["åpningstid", 0.87]], + keywords=[["svømme", 0.82], ["åpningstid", 0.87]], ) # Test that no TypeError is raised when creating a Content object with diff --git a/chatbot/nlp/query.py b/chatbot/nlp/query.py index f8c4753..3584ae0 100644 --- a/chatbot/nlp/query.py +++ b/chatbot/nlp/query.py @@ -45,18 +45,38 @@ def _handle_not_found(query_text): def _get_corpus_text(doc): ''' Converts a document from the model into a string which will be used in - a corpus. All possible answers are used to generate the corpus, if - multiple answers exist.''' - content = ' '.join(doc['content']['texts']) + a corpus. ''' + content = doc['content']['text'] return doc['content']['title'] + ' ' + content -def _get_answer_text(doc): - ''' Converts a document from the model into a readable string. ''' - content = random.choice(doc['content']['texts']) - content = content + '\n' + URL_FROM_TEXT + doc['url'] +def _get_answer(doc): + ''' Converts a document from the model into a (text, [links])-answer tuple ''' + answer = [doc['content']['text'], + doc['content']['links'] if 'links' in doc['content'] else []] + # Add the source-ur to the list of urls + answer[1].append([URL_FROM_TEXT, doc['url']]) - return doc['content']['title'] + ':\n' + content + answer[0] = doc['content']['title'] + ':\n' + answer[0] + return answer + + +_url_styles = { + 'plain': '{} {}', + 'html': "{0}" + } + + +def _format_answer(answer, url_style): + ''' Format an answer (text, links) with a specific url_style. Supports plain + for '{} {}'-like 'text, link' format, and html for a full -tag. Returns + a plain string ''' + for link in answer[1]: + answer[0] = answer[0].replace(link[0], _url_styles[url_style].format(*link)) + + # Appends source (URL_FROM_TEXT) to the end + answer[0] += '\n ' + _url_styles[url_style].format(*answer[1][-1]) + return answer[0] def expand_query(query): @@ -137,7 +157,7 @@ def expand_query(query): return ' '.join(result) -def _perform_search(query_text): +def _perform_search(query_text, url_style): ''' Takes a query string and finds the best matching document in the database. ''' @@ -183,11 +203,11 @@ def _perform_search(query_text): break # Add this result to the list of answers. - answers.append(_get_answer_text(docs[scores.index(score)])) + answers.append(_get_answer(docs[scores.index(score)])) if len(answers) == 1: # Return the answer straight away if there is only 1 result/ - return answers[0] + return _format_answer(answers[0], url_style) # Append answers until we reach the CHAR_LIMIT i, n_chars = 0, 0 @@ -198,12 +218,13 @@ def _perform_search(query_text): # If we only have 1 answer after threshold we don't want to add the # MULTI_ANSWERS option to the response if max(i, 1) == 1: - return answers[0] + return _format_answer(answers[0], url_style) # Join the results with a separator. Still setting a max number of # answers - return '\n\n---\n\n'.join([MULTIPLE_ANSWERS] + answers[0:min(max(i, 1), - MAX_ANSWERS)]) + answers = answers[0:min(max(i, 1,), MAX_ANSWERS)] + answers = [_format_answer(ans, url_style) for ans in answers] + return '\n\n---\n\n'.join([MULTIPLE_ANSWERS] + answers) except KeyError: raise Exception('Document does not have content and texts.') except ValueError: @@ -211,5 +232,5 @@ def _perform_search(query_text): class QueryHandler: - def get_response(self, query): - return _perform_search(query) + def get_response(self, query, url_style='html'): + return _perform_search(query, url_style) diff --git a/chatbot/nlp/test/evaluation.py b/chatbot/nlp/test/evaluation.py index 069a60d..17d36c5 100644 --- a/chatbot/nlp/test/evaluation.py +++ b/chatbot/nlp/test/evaluation.py @@ -17,7 +17,7 @@ def evaluate_test(test): for question in questions: # The answer our system gave. - our_answer = _perform_search(question) + our_answer = _perform_search(question, 'plain') # The score for this specific question. score_question = 0 score_url = 0 diff --git a/chatbot/scraper/spiders/info_gathering_spider.py b/chatbot/scraper/spiders/info_gathering_spider.py index 4c2b1a5..8b467fd 100644 --- a/chatbot/scraper/spiders/info_gathering_spider.py +++ b/chatbot/scraper/spiders/info_gathering_spider.py @@ -22,12 +22,13 @@ class TreeElement(NodeMixin): # A counter used to give an unique ID to all nodes. counter = 0 - def __init__(self, tag, page_id, text=None, parent=None): + def __init__(self, tag, page_id, text=None, parent=None, links=[]): ''' Tree node which stores information about an HTML tag. ''' self.tag = tag self.text = text self.parent = parent + self.links = [] # We hash the URL of all pages and add a counter for the element # after it. This is used to diff new and stored HTML pages. @@ -311,12 +312,11 @@ def generate_tree(self, response): # Add the element text to parent instead of creating a # new element if elem_text in self.normalize(parent.text): - parent.text += '\n' + url + current_parent.links.append([elem_text, url]) continue - # Add the URL and elem_text into the end of the parent's - # text - parent.text += '\n' + elem_text + ' ' + url + current_parent.links.append([url, url]) + elif elem_tag in self.ignored_child_tags \ and current_parent.tag \ in self.ignored_child_tags[elem_tag]: diff --git a/chatbot/scraper/test/test.html b/chatbot/scraper/test/test.html index f3499a5..bc30ffb 100644 --- a/chatbot/scraper/test/test.html +++ b/chatbot/scraper/test/test.html @@ -1 +1 @@ -Svømmehall - Husebybadet - Trondheim kommune
Hjem Kultur og fritidSvømmehall - Husebybadet

Svømmehall - Husebybadet

 Ordinære åpningstider

Mandag, onsdag, fredag: 15.00–20.00
Tirsdag, torsdag: 15.00–17.30 og 19.30–22.30
Lørdag, søndag: 10.00–16.00

Badetiden er minimum 30 min, og du kan bade så lenge du vil. Stengetiden angir når badegjestene må opp av bassenget. 

Morgenbading

Alle hverdager: 06.30-08.30

Trimsvømming for voksen

Alle hverdager: 14.00-15.00

Kr 60

Vanngym

Tirsdag, torsdag: 14.00–15.00

Babysvømming

Mandag, onsdag, fredag: 14.00–15.00
Her er det drop-in timer med instruktør i vannet. Foresatte må ta kontakt med instruktøren hvis de ønsker hjelp. Babyene kan starte fra de er seks til åtte uker gamle. Temperaturen i vannet er 33 grader.

Billettpriser

Voksne

Enkeltbillett: 105,-
Trimsvømming*: 60,-
Klippekort med 12 klipp: 920,-
Årskort: 3135,-
Halvårskort: 2060,-

*Svømming når det foregår tilrettelagt aktivitet i deler av badet.

Barn (0-16) og honnør

Enkeltbillett barn 0-2 år: Gratis
Enkeltbillett barn 3-16 år: 60,-                                                 

Enkeltbillett honnør: 60,-

Klippekort med 12 klipp: 450,-
Årskort: 1750,-

Studenter (fra 17 år med gyldig studiebevis)

Enkeltbillett: 75,-
Klippekort med 12 klipp: 595,-
Årskort: 2380,-

Familie (maks 2 voksne og 3 barn)

Enkeltbillett: 200,-
Klippekort med 10 klipp: 1575,-

Cup/turnering

Deltakere med bevis: 45,-

Diverse

Badetøy (utlån per gang): 75,-

Vi selger også badebleier, håndkle og diverse svømmeutstyr. 

Medlemmer i Actic treningssenter kan benytte Husebybadet i badets åpningstider. Svømming når det foregår tilrettelagt aktivitet i deler av badet.

Leie av Husebybadet

Du kan leie Husebybadet til bursdager og andre arrangementer, på lørdager og søndager mellom 16.00 og 20.00.

Har du spørsmål eller ønsker å avtale leie, ta kontakt på telefon 95263688 (betjenes i åpningstid).

Priser

Pris for leie er kr 1605,- per time. Du kan også leie foajéen for et tillegg på kr 205,-

Betaling av leie gjøres ved ankomst.

Badevakt

Ved leie i helgene stiller Husebybadet med en badevakt. Du som er leietaker må i tillegg stille med minimum én vakt over 18 år per 15 barn.

Svømmekurs

Svømmekurs (Vestbyen IL)

Fasiliteter

Husebybadet er åpent for alle, men har egne tider for blant annet trimsvømming, vanngym og babysvømming. Se under åpningstider for når disse aktivitetene foregår. Har du spørsmål om aldersgrenser for når du kan bade alene, ta kontakt på telefon 95263688.

Fasiliteter i Husebybadet

  • Idrettsbasseng (25 meter)
  • Terapibasseng
  • Plaskebasseng for de minste
  • Boblebad
  • Garderober
  • Publikumstribune
  • SwimTag

I Husebybadet finner du også Actic treningssenter. Actic-medlemmer kan bruke Husebybadet i åpningstiden.

Swim Tag

Vil du vite hvor langt du har svømt eller hvor fort det gikk? Med SwimTag blir alt du gjør i bassenget registrert.

Rundt bassenget i Husebybadet er det plassert en rekke sensorer.

Med et SwimTag-armbånd, vil du kunne registrere hvor langt du har svømt, hvor fort du svømte, hvor lange pauser du har tatt, hvilken stilart du benyttet og hvor mange og lange tak du har tatt. Du kan også få vite ditt estimerte kaloriforbruk.

Etter endt treningsøkt er informasjonen lett tilgjengelig i din egen brukerprofil, enkelt presentert med fargekodede grafer og oversiktlig grafikk. Du kan også velge å knytte din brukerprofil opp mot sosiale medier som Facebook og Twitter, og dele sin trening og fremgang med venner.

SwimTag er også et godt redskap for å gi barn og unge motivasjon til å lære seg å svømme. Det finnes ulike nivåer av treningsprogrammer integrert. Armbåndet koster ingenting.

Andre kommunale svømmehaller

Du kan også booke tid i andre kommunale svømmehaller via denne siden.

Kontakt oss

AdresseSaupstadringen 13, 7078 TRONDHEIM

Telefon: 95263688 (betjenes i Husebybadets åpningstid)

Følg Husebybadet på Facebook

E-post: bydrift.postmottak@trondheim.kommune.no

Sist oppdatert: 25.02.2019

Fant du det du lette etter?

Takk for din tilbakemelding

Hva forsøkte du å finne?


\ No newline at end of file +Svømmehall - Husebybadet - Trondheim kommune
Hjem Kultur og fritidSvømmehall - Husebybadet

Svømmehall - Husebybadet

 Ordinære åpningstider

Mandag, onsdag, fredag: 15.00–20.00
Tirsdag, torsdag: 15.00–17.30 og 19.30–22.30
Lørdag, søndag: 10.00–16.00

Badetiden er minimum 30 min, og du kan bade så lenge du vil. Stengetiden angir når badegjestene må opp av bassenget. 

Morgenbading

Alle hverdager: 06.30-08.30

Trimsvømming for voksen

Alle hverdager: 14.00-15.00

Kr 60

Vanngym

Tirsdag, torsdag: 14.00–15.00

Babysvømming

Mandag, onsdag, fredag: 14.00–15.00
Her er det drop-in timer med instruktør i vannet. Foresatte må ta kontakt med instruktøren hvis de ønsker hjelp. Babyene kan starte fra de er seks til åtte uker gamle. Temperaturen i vannet er 33 grader.

Billettpriser

Voksne

Enkeltbillett: 105,-
Trimsvømming*: 60,-
Klippekort med 12 klipp: 920,-
Årskort: 3135,-
Halvårskort: 2060,-

*Svømming når det foregår tilrettelagt aktivitet i deler av badet.

Barn (0-16) og honnør

Enkeltbillett barn 0-2 år: Gratis
Enkeltbillett barn 3-16 år: 60,-                                                 

Enkeltbillett honnør: 60,-

Klippekort med 12 klipp: 450,-
Årskort: 1750,-

Studenter (fra 17 år med gyldig studiebevis)

Enkeltbillett: 75,-
Klippekort med 12 klipp: 595,-
Årskort: 2380,-

Familie (maks 2 voksne og 3 barn)

Enkeltbillett: 200,-
Klippekort med 10 klipp: 1575,-

Cup/turnering

Deltakere med bevis: 45,-

Diverse

Badetøy (utlån per gang): 75,-

Vi selger også badebleier, håndkle og diverse svømmeutstyr. 

Medlemmer i Actic treningssenter kan benytte Husebybadet i badets åpningstider. Svømming når det foregår tilrettelagt aktivitet i deler av badet.

Leie av Husebybadet

Du kan leie Husebybadet til bursdager og andre arrangementer, på lørdager og søndager mellom 16.00 og 20.00.

Har du spørsmål eller ønsker å avtale leie, ta kontakt på telefon 95263688 (betjenes i åpningstid).

Priser

Pris for leie er kr 1605,- per time. Du kan også leie foajéen for et tillegg på kr 205,-

Betaling av leie gjøres ved ankomst.

Badevakt

Ved leie i helgene stiller Husebybadet med en badevakt. Du som er leietaker må i tillegg stille med minimum én vakt over 18 år per 15 barn.

Svømmekurs

Svømmekurs (Vestbyen IL)

Fasiliteter

Husebybadet er åpent for alle, men har egne tider for blant annet trimsvømming, vanngym og babysvømming. Se under åpningstider for når disse aktivitetene foregår. Har du spørsmål om aldersgrenser for når du kan bade alene, ta kontakt på telefon 95263688.

Fasiliteter i Husebybadet

  • Idrettsbasseng (25 meter)
  • Terapibasseng
  • Plaskebasseng for de minste
  • Boblebad
  • Garderober
  • Publikumstribune
  • SwimTag

I Husebybadet finner du også Actic treningssenter. Actic-medlemmer kan bruke Husebybadet i åpningstiden.

Swim Tag

Vil du vite hvor langt du har svømt eller hvor fort det gikk? Med SwimTag blir alt du gjør i bassenget registrert.

Rundt bassenget i Husebybadet er det plassert en rekke sensorer.

Med et SwimTag-armbånd, vil du kunne registrere hvor langt du har svømt, hvor fort du svømte, hvor lange pauser du har tatt, hvilken stilart du benyttet og hvor mange og lange tak du har tatt. Du kan også få vite ditt estimerte kaloriforbruk.

Etter endt treningsøkt er informasjonen lett tilgjengelig i din egen brukerprofil, enkelt presentert med fargekodede grafer og oversiktlig grafikk. Du kan også velge å knytte din brukerprofil opp mot sosiale medier som Facebook og Twitter, og dele sin trening og fremgang med venner.

SwimTag er også et godt redskap for å gi barn og unge motivasjon til å lære seg å svømme. Det finnes ulike nivåer av treningsprogrammer integrert. Armbåndet koster ingenting.

Andre kommunale svømmehaller

Du kan også booke tid i andre kommunale svømmehaller via denne siden.

Kontakt oss

AdresseSaupstadringen 13, 7078 TRONDHEIM

Telefon: 95263688 (betjenes i Husebybadets åpningstid)

Følg Husebybadet på Facebook

E-post: bydrift.postmottak@trondheim.kommune.no

Sist oppdatert: 25.02.2019

Fant du det du lette etter?

Takk for din tilbakemelding

Hva forsøkte du å finne?


diff --git a/chatbot/scraper/test/test_html.json b/chatbot/scraper/test/test_html.json index 0813d39..4fdde1d 100644 --- a/chatbot/scraper/test/test_html.json +++ b/chatbot/scraper/test/test_html.json @@ -1 +1 @@ -{"url": "https://www.trondheim.kommune.no/tema/kultur-og-fritid/lokaler/husebybadet/", "tree": {"tag": "title", "text": "Sv\u00f8mmehall - Husebybadet - Trondheim kommune", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-0", "children": [{"tag": "meta", "text": "sv\u00f8mmehall,husebybadet,bad,badebleier,sv\u00f8mmeutstyr", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-1"}, {"tag": "h1", "text": "Sv\u00f8mmehall - Husebybadet", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-2", "children": [{"tag": "h2", "text": "Ordin\u00e6re \u00e5pningstider", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-3", "children": [{"tag": "p", "text": "Mandag, onsdag, fredag: 15.00\u201320.00\nTirsdag, torsdag: 15.00\u201317.30 og 19.30\u201322.30\nL\u00f8rdag, s\u00f8ndag: 10.00\u201316.00", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-4"}, {"tag": "p", "text": "Badetiden er minimum 30 min, og du kan bade s\u00e5 lenge du vil. Stengetiden angir n\u00e5r badegjestene m\u00e5 opp av bassenget.", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-5"}, {"tag": "h3", "text": "Morgenbading", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-6", "children": [{"tag": "p", "text": "Alle hverdager: 06.30-08.30", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-7"}]}, {"tag": "h3", "text": "Trimsv\u00f8mming for voksen", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-8", "children": [{"tag": "p", "text": "Alle hverdager: 14.00-15.00\nKr 60", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-9"}]}, {"tag": "h3", "text": "Vanngym", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-10", "children": [{"tag": "p", "text": "Tirsdag, torsdag: 14.00\u201315.00", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-11"}]}, {"tag": "h3", "text": "Babysv\u00f8mming", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-12", "children": [{"tag": "p", "text": "Mandag, onsdag, fredag: 14.00\u201315.00\nHer er det drop-in timer med instrukt\u00f8r i vannet. Foresatte m\u00e5 ta kontakt med instrukt\u00f8ren hvis de \u00f8nsker hjelp. Babyene kan starte fra de er seks til \u00e5tte uker gamle. Temperaturen i vannet er 33 grader.", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-13"}]}]}, {"tag": "h2", "text": "Billettpriser", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-14", "children": [{"tag": "h3", "text": "Voksne", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-15", "children": [{"tag": "p", "text": "Enkeltbillett: 105,-\nTrimsv\u00f8mming*: 60,-\nKlippekort med 12 klipp: 920,-\n\u00c5rskort: 3135,-\nHalv\u00e5rskort: 2060,-", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-16"}, {"tag": "p", "text": "*Sv\u00f8mming n\u00e5r det foreg\u00e5r tilrettelagt aktivitet i deler av badet.", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-17"}]}, {"tag": "h3", "text": "Barn (0-16) og honn\u00f8r", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-18", "children": [{"tag": "p", "text": "Enkeltbillett barn 0-2 \u00e5r: Gratis\nEnkeltbillett barn 3-16 \u00e5r: 60,-\nEnkeltbillett honn\u00f8r: 60,-\nKlippekort med 12 klipp: 450,-\n\u00c5rskort: 1750,-", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-19"}]}, {"tag": "h3", "text": "Studenter (fra 17 \u00e5r med gyldig studiebevis)", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-20", "children": [{"tag": "p", "text": "Enkeltbillett: 75,-\nKlippekort med 12 klipp: 595,-\n\u00c5rskort: 2380,-", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-21"}]}, {"tag": "h3", "text": "Familie (maks 2 voksne og 3 barn)", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-22", "children": [{"tag": "p", "text": "Enkeltbillett: 200,-\nKlippekort med 10 klipp: 1575,-", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-23"}]}, {"tag": "h3", "text": "Cup/turnering", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-24", "children": [{"tag": "p", "text": "Deltakere med bevis: 45,-", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-25"}]}, {"tag": "h3", "text": "Diverse", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-26", "children": [{"tag": "p", "text": "Badet\u00f8y (utl\u00e5n per gang): 75,-", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-27"}, {"tag": "p", "text": "Vi selger ogs\u00e5 badebleier, h\u00e5ndkle og diverse sv\u00f8mmeutstyr.", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-28"}, {"tag": "p", "text": "Medlemmer i Actic treningssenter kan benytte Husebybadet i badets \u00e5pningstider. Sv\u00f8mming n\u00e5r det foreg\u00e5r tilrettelagt aktivitet i deler av badet.", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-29"}]}]}, {"tag": "h2", "text": "Leie av Husebybadet", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-30", "children": [{"tag": "p", "text": "Du kan leie Husebybadet til bursdager og andre arrangementer, p\u00e5 l\u00f8rdager og s\u00f8ndager mellom 16.00 og 20.00.", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-31"}, {"tag": "p", "text": "Har du sp\u00f8rsm\u00e5l eller \u00f8nsker \u00e5 avtale leie, ta kontakt p\u00e5 telefon 95263688 (betjenes i \u00e5pningstid).", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-32"}, {"tag": "h6", "text": "Priser", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-33", "children": [{"tag": "p", "text": "Pris for leie er kr 1605,- per time. Du kan ogs\u00e5 leie foaj\u00e9en for et tillegg p\u00e5 kr 205,-\nBetaling av leie gj\u00f8res ved ankomst.", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-34"}]}, {"tag": "h6", "text": "Badevakt", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-35", "children": [{"tag": "p", "text": "Ved leie i helgene stiller Husebybadet med en badevakt. Du som er leietaker m\u00e5 i tillegg stille med minimum \u00e9n vakt over 18 \u00e5r per 15 barn.", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-36"}]}]}, {"tag": "h2", "text": "Sv\u00f8mmekurs", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-37", "children": [{"tag": "p", "text": "Sv\u00f8mmekurs (Vestbyen IL)\nhttp://vestbyentrondheim.no/svommekurs/", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-38"}]}, {"tag": "h2", "text": "Fasiliteter", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-39", "children": [{"tag": "p", "text": "Husebybadet er \u00e5pent for alle, men har egne tider for blant annet trimsv\u00f8mming, vanngym og babysv\u00f8mming. Se under \u00e5pningstider for n\u00e5r disse aktivitetene foreg\u00e5r. Har du sp\u00f8rsm\u00e5l om aldersgrenser for n\u00e5r du kan bade alene, ta kontakt p\u00e5 telefon 95263688.", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-40"}, {"tag": "h6", "text": "Fasiliteter i Husebybadet", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-41", "children": [{"tag": "li", "text": "- Idrettsbasseng (25 meter)\n- Terapibasseng\n- Plaskebasseng for de minste\n- Boblebad\n- Garderober\n- Publikumstribune\n- SwimTag\nActic treningssenter https://www.actic.no/finn-sentre/gym-trondheim/", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-42"}]}]}, {"tag": "h2", "text": "Swim Tag", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-43", "children": [{"tag": "p", "text": "Vil du vite hvor langt du har sv\u00f8mt eller hvor fort det gikk? Med SwimTag blir alt du gj\u00f8r i bassenget registrert.", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-44"}, {"tag": "p", "text": "Rundt bassenget i Husebybadet er det plassert en rekke sensorer.", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-45"}, {"tag": "p", "text": "Med et SwimTag-armb\u00e5nd, vil du kunne registrere hvor langt du har sv\u00f8mt, hvor fort du sv\u00f8mte, hvor lange pauser du har tatt, hvilken stilart du benyttet og hvor mange og lange tak du har tatt. Du kan ogs\u00e5 f\u00e5 vite ditt estimerte kaloriforbruk.", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-46"}, {"tag": "p", "text": "Etter endt trenings\u00f8kt er informasjonen lett tilgjengelig i din egen brukerprofil, enkelt presentert med fargekodede grafer og oversiktlig grafikk. Du kan ogs\u00e5 velge \u00e5 knytte din brukerprofil opp mot sosiale medier som Facebook og Twitter, og dele sin trening og fremgang med venner.", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-47"}, {"tag": "p", "text": "SwimTag er ogs\u00e5 et godt redskap for \u00e5 gi barn og unge motivasjon til \u00e5 l\u00e6re seg \u00e5 sv\u00f8mme. Det finnes ulike niv\u00e5er av treningsprogrammer integrert. Armb\u00e5ndet koster ingenting.", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-48"}]}, {"tag": "h2", "text": "Andre kommunale sv\u00f8mmehaller", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-49", "children": [{"tag": "p", "text": "Du kan ogs\u00e5 booke tid i andre kommunale sv\u00f8mmehaller via denne siden.\nhttps://www.trondheim.kommune.no/tema/kultur-og-fritid/lokaler1/lokaler/", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-50"}]}, {"tag": "h2", "text": "Kontakt oss", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-51", "children": [{"tag": "p", "text": "Adresse: Saupstadringen 13, 7078 TRONDHEIM\nhttps://www.google.no/maps/place/Saupstadringen+13,+7078+Saupstad/@63.3652201,10.3496189,17z/data=!3m1!4b1!4m5!3m4!1s0x466d2e73e1acf3b9:0xcacd2256aa1a6e1f!8m2!3d63.3652201!4d10.3518076\nTelefon: 95263688 (betjenes i Husebybadets \u00e5pningstid)\nF\u00f8lg Husebybadet p\u00e5 Facebook\nhttps://www.facebook.com/Husebybadet/\nE-post: bydrift.postmottak@trondheim.kommune.no\nmailto:bydrift.postmottak@trondheim.kommune.no", "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-52"}]}]}]}} \ No newline at end of file +{"url": "https://www.trondheim.kommune.no/tema/kultur-og-fritid/lokaler/husebybadet/", "tree": {"tag": "title", "text": "Sv\u00f8mmehall - Husebybadet - Trondheim kommune", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-0", "children": [{"tag": "meta", "text": "sv\u00f8mmehall,husebybadet,bad,badebleier,sv\u00f8mmeutstyr", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-1"}, {"tag": "h1", "text": "Sv\u00f8mmehall - Husebybadet", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-2", "children": [{"tag": "h2", "text": "Ordin\u00e6re \u00e5pningstider", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-3", "children": [{"tag": "p", "text": "Mandag, onsdag, fredag: 15.00\u201320.00\nTirsdag, torsdag: 15.00\u201317.30 og 19.30\u201322.30\nL\u00f8rdag, s\u00f8ndag: 10.00\u201316.00", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-4"}, {"tag": "p", "text": "Badetiden er minimum 30 min, og du kan bade s\u00e5 lenge du vil. Stengetiden angir n\u00e5r badegjestene m\u00e5 opp av bassenget.", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-5"}, {"tag": "h3", "text": "Morgenbading", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-6", "children": [{"tag": "p", "text": "Alle hverdager: 06.30-08.30", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-7"}]}, {"tag": "h3", "text": "Trimsv\u00f8mming for voksen", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-8", "children": [{"tag": "p", "text": "Alle hverdager: 14.00-15.00\nKr 60", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-9"}]}, {"tag": "h3", "text": "Vanngym", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-10", "children": [{"tag": "p", "text": "Tirsdag, torsdag: 14.00\u201315.00", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-11"}]}, {"tag": "h3", "text": "Babysv\u00f8mming", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-12", "children": [{"tag": "p", "text": "Mandag, onsdag, fredag: 14.00\u201315.00\nHer er det drop-in timer med instrukt\u00f8r i vannet. Foresatte m\u00e5 ta kontakt med instrukt\u00f8ren hvis de \u00f8nsker hjelp. Babyene kan starte fra de er seks til \u00e5tte uker gamle. Temperaturen i vannet er 33 grader.", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-13"}]}]}, {"tag": "h2", "text": "Billettpriser", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-14", "children": [{"tag": "h3", "text": "Voksne", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-15", "children": [{"tag": "p", "text": "Enkeltbillett: 105,-\nTrimsv\u00f8mming*: 60,-\nKlippekort med 12 klipp: 920,-\n\u00c5rskort: 3135,-\nHalv\u00e5rskort: 2060,-", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-16"}, {"tag": "p", "text": "*Sv\u00f8mming n\u00e5r det foreg\u00e5r tilrettelagt aktivitet i deler av badet.", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-17"}]}, {"tag": "h3", "text": "Barn (0-16) og honn\u00f8r", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-18", "children": [{"tag": "p", "text": "Enkeltbillett barn 0-2 \u00e5r: Gratis\nEnkeltbillett barn 3-16 \u00e5r: 60,-\nEnkeltbillett honn\u00f8r: 60,-\nKlippekort med 12 klipp: 450,-\n\u00c5rskort: 1750,-", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-19"}]}, {"tag": "h3", "text": "Studenter (fra 17 \u00e5r med gyldig studiebevis)", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-20", "children": [{"tag": "p", "text": "Enkeltbillett: 75,-\nKlippekort med 12 klipp: 595,-\n\u00c5rskort: 2380,-", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-21"}]}, {"tag": "h3", "text": "Familie (maks 2 voksne og 3 barn)", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-22", "children": [{"tag": "p", "text": "Enkeltbillett: 200,-\nKlippekort med 10 klipp: 1575,-", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-23"}]}, {"tag": "h3", "text": "Cup/turnering", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-24", "children": [{"tag": "p", "text": "Deltakere med bevis: 45,-", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-25"}]}, {"tag": "h3", "text": "Diverse", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-26", "children": [{"tag": "p", "text": "Badet\u00f8y (utl\u00e5n per gang): 75,-", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-27"}, {"tag": "p", "text": "Vi selger ogs\u00e5 badebleier, h\u00e5ndkle og diverse sv\u00f8mmeutstyr.", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-28"}, {"tag": "p", "text": "Medlemmer i Actic treningssenter kan benytte Husebybadet i badets \u00e5pningstider. Sv\u00f8mming n\u00e5r det foreg\u00e5r tilrettelagt aktivitet i deler av badet.", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-29"}]}]}, {"tag": "h2", "text": "Leie av Husebybadet", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-30", "children": [{"tag": "p", "text": "Du kan leie Husebybadet til bursdager og andre arrangementer, p\u00e5 l\u00f8rdager og s\u00f8ndager mellom 16.00 og 20.00.", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-31"}, {"tag": "p", "text": "Har du sp\u00f8rsm\u00e5l eller \u00f8nsker \u00e5 avtale leie, ta kontakt p\u00e5 telefon 95263688 (betjenes i \u00e5pningstid).", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-32"}, {"tag": "h6", "text": "Priser", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-33", "children": [{"tag": "p", "text": "Pris for leie er kr 1605,- per time. Du kan ogs\u00e5 leie foaj\u00e9en for et tillegg p\u00e5 kr 205,-\nBetaling av leie gj\u00f8res ved ankomst.", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-34"}]}, {"tag": "h6", "text": "Badevakt", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-35", "children": [{"tag": "p", "text": "Ved leie i helgene stiller Husebybadet med en badevakt. Du som er leietaker m\u00e5 i tillegg stille med minimum \u00e9n vakt over 18 \u00e5r per 15 barn.", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-36"}]}]}, {"tag": "h2", "text": "Sv\u00f8mmekurs", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-37", "children": [{"tag": "p", "text": "Sv\u00f8mmekurs (Vestbyen IL)", "links": [["Sv\u00f8mmekurs (Vestbyen IL)", "http://vestbyentrondheim.no/svommekurs/"]], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-38"}]}, {"tag": "h2", "text": "Fasiliteter", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-39", "children": [{"tag": "p", "text": "Husebybadet er \u00e5pent for alle, men har egne tider for blant annet trimsv\u00f8mming, vanngym og babysv\u00f8mming. Se under \u00e5pningstider for n\u00e5r disse aktivitetene foreg\u00e5r. Har du sp\u00f8rsm\u00e5l om aldersgrenser for n\u00e5r du kan bade alene, ta kontakt p\u00e5 telefon 95263688.", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-40"}, {"tag": "h6", "text": "Fasiliteter i Husebybadet", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-41", "children": [{"tag": "li", "text": "- Idrettsbasseng (25 meter)\n- Terapibasseng\n- Plaskebasseng for de minste\n- Boblebad\n- Garderober\n- Publikumstribune\n- SwimTag", "links": [["https://www.actic.no/finn-sentre/gym-trondheim/", "https://www.actic.no/finn-sentre/gym-trondheim/"]], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-42"}]}]}, {"tag": "h2", "text": "Swim Tag", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-43", "children": [{"tag": "p", "text": "Vil du vite hvor langt du har sv\u00f8mt eller hvor fort det gikk? Med SwimTag blir alt du gj\u00f8r i bassenget registrert.", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-44"}, {"tag": "p", "text": "Rundt bassenget i Husebybadet er det plassert en rekke sensorer.", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-45"}, {"tag": "p", "text": "Med et SwimTag-armb\u00e5nd, vil du kunne registrere hvor langt du har sv\u00f8mt, hvor fort du sv\u00f8mte, hvor lange pauser du har tatt, hvilken stilart du benyttet og hvor mange og lange tak du har tatt. Du kan ogs\u00e5 f\u00e5 vite ditt estimerte kaloriforbruk.", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-46"}, {"tag": "p", "text": "Etter endt trenings\u00f8kt er informasjonen lett tilgjengelig i din egen brukerprofil, enkelt presentert med fargekodede grafer og oversiktlig grafikk. Du kan ogs\u00e5 velge \u00e5 knytte din brukerprofil opp mot sosiale medier som Facebook og Twitter, og dele sin trening og fremgang med venner.", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-47"}, {"tag": "p", "text": "SwimTag er ogs\u00e5 et godt redskap for \u00e5 gi barn og unge motivasjon til \u00e5 l\u00e6re seg \u00e5 sv\u00f8mme. Det finnes ulike niv\u00e5er av treningsprogrammer integrert. Armb\u00e5ndet koster ingenting.", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-48"}]}, {"tag": "h2", "text": "Andre kommunale sv\u00f8mmehaller", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-49", "children": [{"tag": "p", "text": "Du kan ogs\u00e5 booke tid i andre kommunale sv\u00f8mmehaller via denne siden.", "links": [["Du kan ogs\u00e5 booke tid i andre kommunale sv\u00f8mmehaller via denne siden.", "https://www.trondheim.kommune.no/tema/kultur-og-fritid/lokaler1/lokaler/"]], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-50"}]}, {"tag": "h2", "text": "Kontakt oss", "links": [], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-51", "children": [{"tag": "p", "text": "Adresse: Saupstadringen 13, 7078 TRONDHEIM\nTelefon: 95263688 (betjenes i Husebybadets \u00e5pningstid)\nF\u00f8lg Husebybadet p\u00e5 Facebook\nE-post: bydrift.postmottak@trondheim.kommune.no", "links": [["Saupstadringen 13", "https://www.google.no/maps/place/Saupstadringen+13,+7078+Saupstad/@63.3652201,10.3496189,17z/data=!3m1!4b1!4m5!3m4!1s0x466d2e73e1acf3b9:0xcacd2256aa1a6e1f!8m2!3d63.3652201!4d10.3518076"], ["F\u00f8lg Husebybadet p\u00e5 Facebook", "https://www.facebook.com/Husebybadet/"], ["bydrift.postmottak@trondheim.kommune.no", "mailto:bydrift.postmottak@trondheim.kommune.no"]], "id": "e3878fe650dc125c5c70ada53cf266b93b4af782-52"}]}]}]}} \ No newline at end of file diff --git a/chatbot/scraper/test/test_spider.py b/chatbot/scraper/test/test_spider.py index 07ce75a..8985e88 100644 --- a/chatbot/scraper/test/test_spider.py +++ b/chatbot/scraper/test/test_spider.py @@ -39,20 +39,28 @@ def test_scraper_snapshot(): tree = spider.parse(fake_response_from_file("test.html")) tree_obj = next(tree) + # Handle absolute path responses_dir = os.path.dirname(os.path.realpath(__file__)) file_path = os.path.join(responses_dir, 'test_html.json') - # Create the JSON test file if non existing - if not os.path.isfile(file_path): - with open(file_path, "w") as f: - f.write(json.dumps(tree_obj)) - # Retrieve snapshot with open(file_path, "r") as data: html_tree_snapshot = data.readlines()[0] - # Sort and compare snapshots - assert sorted( - json.loads(str(html_tree_snapshot)).items() - ) == sorted(tree_obj.items()) + correct = json.loads(str(html_tree_snapshot)) + result = tree_obj + + assert correct["url"] == result["url"] + + assert correct["tree"]["id"] == result["tree"]["id"] + + # Get a very specific content that contains links + a = correct["tree"]["children"][1]["children"][3]["children"][0]["links"] + b = result["tree"]["children"][1]["children"][3]["children"][0]["links"] + assert a == b + + # Get a very specific content that contains a text + a = correct["tree"]["children"][1]["children"][0]["text"] + b = result["tree"]["children"][1]["children"][0]["text"] + assert a == b diff --git a/chatbot/settings.json b/chatbot/settings.json index cdf79ed..0f575af 100644 --- a/chatbot/settings.json +++ b/chatbot/settings.json @@ -94,7 +94,7 @@ "query_system": { "not_found": "Jeg fant ikke informasjonen du spurte etter.\nTa kontakt med Boligadministrasjonen dersom du har behov for assistanse.\nTelefon: 72 54 02 88", "multiple_answers": "Jeg har flere mulige svar til deg.", - "url_from_text": "Hentet fra: ", + "url_from_text": "Kilde", "custom_synset_file": "chatbot/nlp/statics/synset.json", "character_limit": 400, "max_answers": 4 From ad98f5e3b74ea844656f502506689ff1b53ab404 Mon Sep 17 00:00:00 2001 From: Vegar Andreas Bergum Date: Thu, 29 Aug 2019 15:05:22 +0200 Subject: [PATCH 09/11] feat #10: API v2 supporting new URL responses --- chatbot/api/v2/api.py | 26 ++++++++++++++------------ chatbot/api/v2/models.py | 16 ++++++---------- chatbot/api/v2/test/test_v2_api.py | 11 +++++++---- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/chatbot/api/v2/api.py b/chatbot/api/v2/api.py index 6380207..05a231a 100644 --- a/chatbot/api/v2/api.py +++ b/chatbot/api/v2/api.py @@ -19,9 +19,10 @@ unknown_col = Config.get_mongo_collection("unknown") -direct_response_model = api.model('DirectResponse', { +response_model = api.model('Response', { 'user_input': fields.String(description='User chat input'), - 'response': fields.String(description='Bot chat response') + 'response': fields.String(description='Bot chat response'), + 'style': fields.String }) conflict_model = api.model('Conflict', { @@ -47,7 +48,8 @@ inner_content_model = api.model('InnerContent', { 'title': fields.String, 'keywords': fields.List(fields.Nested(keyword_model)), - 'texts': fields.List(fields.String) + 'text': fields.String, + 'links': fields.List(fields.List(fields.String)) }) content_model = api.model('Content', { @@ -72,15 +74,17 @@ def get(self): return {'hello': 'world'} -class Response(Resource): - @api.marshal_with(direct_response_model) - def get(self, query): - return models.DirectResponse(user_input=query) +style_parser = reqparse.RequestParser() +style_parser.add_argument('style', required=False) -class FullResponse(Resource): - def get(self): - pass +class Response(Resource): + @api.marshal_with(response_model) + @api.expect(style_parser) + def get(self, query): + args = style_parser.parse_args() + style = args['style'] if 'style' in args else 'plain' + return models.Response(query, style) class ConflictIDs(Resource): @@ -227,8 +231,6 @@ def delete(self, unknown_query): api.add_resource(Response, '/response//', methods=['GET']) -api.add_resource(FullResponse, '/response/', methods=['GET']) - api.add_resource(ConflictIDs, '/conflict_ids/', methods=['GET']) api.add_resource(ConflictIDs, '/conflict_ids//', diff --git a/chatbot/api/v2/models.py b/chatbot/api/v2/models.py index c621526..9f108ae 100644 --- a/chatbot/api/v2/models.py +++ b/chatbot/api/v2/models.py @@ -4,18 +4,14 @@ handler = QueryHandler() -class DirectResponse(object): - def __init__(self, user_input=''): - self.user_input = user_input - self.response = handler.get_response(self.user_input) - - class Response(object): - def __init__(self, user_input='', response_format='plain'): + def __init__(self, user_input, style): self.user_input = user_input - self.response_format = response_format - self.response = handler.get_response(self.user_input, - self.response_format) + if style: + self.response = handler.get_response(self.user_input, style) + else: + self.response = handler.get_response(self.user_input) + self.style = style class Conflict(object): diff --git a/chatbot/api/v2/test/test_v2_api.py b/chatbot/api/v2/test/test_v2_api.py index 4a17c1b..5ae604e 100644 --- a/chatbot/api/v2/test/test_v2_api.py +++ b/chatbot/api/v2/test/test_v2_api.py @@ -78,7 +78,8 @@ def test_get_content(client): 'content': { 'title': 'test title', 'keywords': [], - 'texts': [] + 'text': '', + 'links': [] }, 'url': 'test_url'} factory.post_document(content, prod_col) @@ -103,7 +104,8 @@ def test_update_content(client): "confidence": 0.2010 } ], - "texts": ["some test text"] + "text": "some test text", + "links": [] } } factory.post_document(content.copy(), prod_col) @@ -140,7 +142,8 @@ def test_get_document(client): "confidence": 0.2010 } ], - "texts": ["some test text"] + "text": "some test text", + "links": [] } } factory.post_document(document.copy(), prod_col) @@ -166,7 +169,7 @@ def test_delete_content(client): "confidence": 0.2010 } ], - "texts": ["some test text"] + "text": "some test text" } } factory.post_document(document.copy(), prod_col) From 7910fc47ffd2189039aaace919fdd5d07668393f Mon Sep 17 00:00:00 2001 From: Vegar Andreas Bergum Date: Thu, 29 Aug 2019 15:22:02 +0200 Subject: [PATCH 10/11] feat #10: updates APIv2/v1 and website to handle URL responses. Also removes multiple answers from website --- chatbot/api/v2/models.py | 7 +-- chatbot/chat/scripts/chat.js | 2 +- chatbot/model/serializer.py | 3 +- chatbot/nlp/query.py | 15 +++--- chatbot/scraper/test/test_spider.py | 1 - chatbot/web/src/components/DocumentView.js | 63 +++------------------- 6 files changed, 22 insertions(+), 69 deletions(-) diff --git a/chatbot/api/v2/models.py b/chatbot/api/v2/models.py index 9f108ae..8165081 100644 --- a/chatbot/api/v2/models.py +++ b/chatbot/api/v2/models.py @@ -7,11 +7,12 @@ class Response(object): def __init__(self, user_input, style): self.user_input = user_input - if style: + if style: self.response = handler.get_response(self.user_input, style) - else: + self.style = style + else: self.response = handler.get_response(self.user_input) - self.style = style + self.style = 'plain' class Conflict(object): diff --git a/chatbot/chat/scripts/chat.js b/chatbot/chat/scripts/chat.js index 6732edf..945b99f 100644 --- a/chatbot/chat/scripts/chat.js +++ b/chatbot/chat/scripts/chat.js @@ -33,7 +33,7 @@ function query() { populateChatWithMessage(input, "user") - http.open("GET", host+"v2/response/"+input+"/", true); + http.open("GET", host+"v2/response/"+input+"/?style=html", true); http.setRequestHeader("Content-Type", "text/plain"); http.onreadystatechange = function() { if(this.readyState == 4 && this.status == 200) { diff --git a/chatbot/model/serializer.py b/chatbot/model/serializer.py index 803dce3..cce78be 100644 --- a/chatbot/model/serializer.py +++ b/chatbot/model/serializer.py @@ -123,7 +123,8 @@ def visit_node(self, data, model_template, models, title=None): .format(title, child["text"]))] - content = Content(title, child["text"], child["links"], keywords) + content = Content(title, child["text"], + child["links"], keywords) new_model = copy.deepcopy(model_template) new_model["id"] = child["id"] new_model["content"] = content.get_content() diff --git a/chatbot/nlp/query.py b/chatbot/nlp/query.py index 3584ae0..df197ba 100644 --- a/chatbot/nlp/query.py +++ b/chatbot/nlp/query.py @@ -1,5 +1,4 @@ import string -import random import os import pymongo @@ -51,9 +50,10 @@ def _get_corpus_text(doc): def _get_answer(doc): - ''' Converts a document from the model into a (text, [links])-answer tuple ''' - answer = [doc['content']['text'], - doc['content']['links'] if 'links' in doc['content'] else []] + ''' Converts a document from the model into a (text, [links])-answer tuple + ''' + answer = [doc['content']['text'], doc['content']['links'] + if 'links' in doc['content'] else []] # Add the source-ur to the list of urls answer[1].append([URL_FROM_TEXT, doc['url']]) @@ -72,8 +72,9 @@ def _format_answer(answer, url_style): for '{} {}'-like 'text, link' format, and html for a full -tag. Returns a plain string ''' for link in answer[1]: - answer[0] = answer[0].replace(link[0], _url_styles[url_style].format(*link)) - + answer[0] = answer[0].replace(link[0], + _url_styles[url_style].format(*link)) + # Appends source (URL_FROM_TEXT) to the end answer[0] += '\n ' + _url_styles[url_style].format(*answer[1][-1]) return answer[0] @@ -232,5 +233,5 @@ def _perform_search(query_text, url_style): class QueryHandler: - def get_response(self, query, url_style='html'): + def get_response(self, query, url_style='plain'): return _perform_search(query, url_style) diff --git a/chatbot/scraper/test/test_spider.py b/chatbot/scraper/test/test_spider.py index 8985e88..4138955 100644 --- a/chatbot/scraper/test/test_spider.py +++ b/chatbot/scraper/test/test_spider.py @@ -39,7 +39,6 @@ def test_scraper_snapshot(): tree = spider.parse(fake_response_from_file("test.html")) tree_obj = next(tree) - # Handle absolute path responses_dir = os.path.dirname(os.path.realpath(__file__)) file_path = os.path.join(responses_dir, 'test_html.json') diff --git a/chatbot/web/src/components/DocumentView.js b/chatbot/web/src/components/DocumentView.js index 0d00830..a0a37db 100644 --- a/chatbot/web/src/components/DocumentView.js +++ b/chatbot/web/src/components/DocumentView.js @@ -71,20 +71,6 @@ class DocumentView extends React.Component { }); }; - createNewAnswer = e => { - e.preventDefault(); - if (this.state.manual) { - this.setState(prevState => ({ - manual: { - content: { - ...prevState.manual.content, - texts: [...prevState.manual.content.texts, ''], - } - }, - })); - } - }; - createNewKeyword = e => { e.preventDefault(); if (this.state.manual) { @@ -110,28 +96,13 @@ class DocumentView extends React.Component { ...prevState.manual.content, keywords: [ ...prevState.manual.content.keywords.slice(0, i), - ...prevState.manual.content.keywords.slice(i + 1), + ...prevState.manual.content.keywords.slice(i + 1), ], } }, })); }; - deleteAnswer = (e, i) => { - e.preventDefault(); - this.setState(prevState => ({ - manual: { - content: { - ...prevState.manual.content, - texts: [ - ...prevState.manual.content.texts.slice(0, i), - ...prevState.manual.content.texts.slice(i + 1), - ], - } - }, - })); - }; - deleteDocument = (e, i) => { e.preventDefault(); fetchData( @@ -146,13 +117,13 @@ class DocumentView extends React.Component { let textAreasManual; if (this.state.manual) { /* map through the texts field from manual */ - textAreasManual = this.state.manual.content.texts.map((text, i) => ( -
+ textAreasManual = +