-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
108 changed files
with
5,299 additions
and
1,024 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
FROM rasa/rasa:1.10.3-full | ||
FROM rasa/rasa:1.10.11-full | ||
|
||
WORKDIR /app | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
FROM rasa/rasa:1.10.3-full | ||
FROM rasa/rasa:1.10.11-full | ||
|
||
WORKDIR /app | ||
|
||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,104 +1,196 @@ | ||
from typing import Dict, Text, Any, List, Union | ||
from datetime import datetime | ||
from typing import Dict, Text, Any, List, Union, Optional | ||
from json import loads, JSONDecodeError | ||
|
||
from pytz import utc | ||
from rasa_sdk import Tracker, Action | ||
from rasa_sdk.forms import FormAction | ||
from rasa_sdk.events import EventType, SlotSet | ||
from rasa_sdk.forms import FormAction, REQUESTED_SLOT, Form | ||
from rasa_sdk.executor import CollectingDispatcher | ||
from rasa_sdk.events import AllSlotsReset | ||
|
||
from parsedatetime import Calendar | ||
|
||
import requests | ||
|
||
|
||
def convert_date(raw_date: str) -> tuple: | ||
def _get_datetime_range(date: str) -> tuple: | ||
""" Makes one day period by received date. | ||
Parameters | ||
---------- | ||
date: | ||
Date for report generating. | ||
from .responses import RESPONSES, WORKFLOW | ||
|
||
Returns | ||
------- | ||
Datetime range. | ||
""" | ||
date = datetime.strptime(date, "%Y-%m-%d") | ||
from_date = date.astimezone(utc) | ||
from_date = from_date.strftime("%Y-%m-%dT%H:%M:%S.%f%z") | ||
|
||
to_date = ( | ||
date.replace(hour=23, minute=59, second=59) | ||
.astimezone(utc) | ||
.strftime("%Y-%m-%dT%H:%M:%S.%f%z") | ||
) | ||
|
||
return from_date, to_date | ||
from rasa_sdk import Tracker, Action | ||
|
||
parser = Calendar() | ||
date, status = parser.parse(raw_date) | ||
date = datetime(*date[:3]).strftime("%Y-%m-%d") | ||
return _get_datetime_range(date) | ||
from .api.report_generator import ( | ||
generate_report, | ||
request_values, | ||
generate_report_payload, | ||
check_issues, | ||
) | ||
|
||
|
||
def generate_report(date: str): | ||
""" Sends request for report generating. | ||
def get_action_with_help_intent(latest_intent: str) -> list: | ||
""" Get action name for intent name. | ||
Parameters | ||
---------- | ||
date: | ||
Date for which report will be generated. | ||
latest_intent: | ||
Bot intent. | ||
Returns | ||
------- | ||
Report data generator. | ||
---------- | ||
Actions name list. | ||
""" | ||
payload = {"date": date} | ||
url = "http://nostradamus-core:8000/virtual_assistant/generate_report/" | ||
|
||
request = requests.post(url, json=payload) | ||
yield request.json() | ||
actions = [] | ||
for action, intents in WORKFLOW.items(): | ||
for intent in intents: | ||
if intent == latest_intent: | ||
actions.append(action) | ||
return actions | ||
|
||
|
||
class ReportForm(FormAction): | ||
"""Collects data for report""" | ||
"""Collects data for report.""" | ||
|
||
def name(self) -> Text: | ||
return "report_form" | ||
|
||
def validate_period( | ||
self, | ||
value: Text, | ||
dispatcher: CollectingDispatcher, | ||
tracker: Tracker, | ||
domain: Dict[Text, Any], | ||
) -> Dict[Text, Any]: | ||
"""Validate period value.""" | ||
try: | ||
if isinstance(loads(value), list): | ||
return {"period": value} | ||
else: | ||
dispatcher.utter_message("Incorrect date. Please try again") | ||
return {"period": None} | ||
except (JSONDecodeError, TypeError): | ||
dispatcher.utter_message("Incorrect date. Please try again") | ||
return {"period": None} | ||
|
||
@staticmethod | ||
def required_slots(tracker): | ||
return ["period"] | ||
required_slots = ["project", "period"] | ||
|
||
if tracker.get_slot("project") == "Pick a project": | ||
required_slots.insert(1, "project_selection") | ||
|
||
return required_slots | ||
|
||
def slot_mappings(self) -> Dict[Text, Union[Dict, List[Dict]]]: | ||
return {"period": self.from_entity(entity="period")} | ||
return { | ||
"project": self.from_entity(entity="project"), | ||
"project_selection": self.from_text(), | ||
"period": self.from_text(), | ||
} | ||
|
||
def request_next_slot( | ||
self, | ||
dispatcher: "CollectingDispatcher", | ||
tracker: "Tracker", | ||
domain: Dict[Text, Any], | ||
) -> Optional[List[EventType]]: | ||
for slot in self.required_slots(tracker): | ||
|
||
if self._should_request_slot(tracker, slot): | ||
|
||
if not check_issues(): | ||
dispatcher.utter_message( | ||
text="Oops! Bugs haven't been uploaded yet. Please try again later" | ||
) | ||
|
||
return [Form(None), AllSlotsReset()] | ||
|
||
if slot == "project_selection": | ||
response = request_values("Project") | ||
response["operation"] = "filtration" | ||
|
||
dispatcher.utter_message( | ||
json_message=response, timeout=100, | ||
) | ||
elif slot == "period": | ||
response = { | ||
"operation": "calendar", | ||
"title": "Please choose a date", | ||
} | ||
dispatcher.utter_message(json_message=response) | ||
else: | ||
dispatcher.utter_message( | ||
template=f"utter_ask_{slot}", **tracker.slots | ||
) | ||
return [SlotSet(REQUESTED_SLOT, slot)] | ||
|
||
# no more required slots to fill | ||
return | ||
|
||
def submit( | ||
self, | ||
dispatcher: CollectingDispatcher, | ||
tracker: Tracker, | ||
domain: Dict[Text, Any], | ||
) -> List[Dict]: | ||
period = convert_date(tracker.get_slot("period")) | ||
message = generate_report(period) | ||
dispatcher.utter_message(json_message=next(message), timeout=100) | ||
payload = generate_report_payload(tracker) | ||
message = generate_report(payload) | ||
|
||
if not message.get("filename"): | ||
message = "Oops! There is no data you’re looking for 😔" | ||
dispatcher.utter_message(text=message, timeout=100) | ||
else: | ||
message["operation"] = "report" | ||
message["filters"] = payload | ||
dispatcher.utter_message(json_message=message, timeout=100) | ||
|
||
return [AllSlotsReset()] | ||
|
||
|
||
class ActionDefaultAskAffirmation(Action): | ||
"""Override action default ask affirmation""" | ||
class ActionFAQSelector(Action): | ||
"""Basic FAQ response selector.""" | ||
|
||
def name(self) -> Text: | ||
return "action_default_ask_affirmation" | ||
return "action_faq_selector" | ||
|
||
def run( | ||
self, | ||
dispatcher: CollectingDispatcher, | ||
tracker: Tracker, | ||
domain: Dict[Text, Any], | ||
) -> List[Dict]: | ||
dispatcher.utter_message(template="utter_cannot_help") | ||
intent = tracker.latest_message["intent"].get("name") | ||
messages = RESPONSES.get("faq").get(intent) | ||
for message in messages: | ||
dispatcher.utter_message(text=message) | ||
|
||
return [] | ||
|
||
|
||
class ActionCustomFallback(Action): | ||
"""Action custom fallback.""" | ||
|
||
def name(self) -> Text: | ||
return "action_custom_fallback" | ||
|
||
def run( | ||
self, | ||
dispatcher: CollectingDispatcher, | ||
tracker: Tracker, | ||
domain: Dict[Text, Any], | ||
) -> List[Dict]: | ||
|
||
latest_intent = tracker.latest_message["intent"].get("name") | ||
actions = get_action_with_help_intent(latest_intent) | ||
if actions: | ||
for action in actions: | ||
if action != tracker.latest_action_name: | ||
if action == "action_faq_selector": | ||
ActionFAQSelector().run( | ||
dispatcher=dispatcher, | ||
tracker=tracker, | ||
domain=domain, | ||
) | ||
else: | ||
dispatcher.utter_message(template=action) | ||
elif ( | ||
latest_intent == "affirm" | ||
and tracker.events[-4].get("text") == "Do you want to learn more?" | ||
): | ||
dispatcher.utter_message( | ||
template="utter_more_details_analysis_and_training" | ||
) | ||
else: | ||
dispatcher.utter_message(template="utter_cannot_help") | ||
|
||
return [Form(None), AllSlotsReset()] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import requests | ||
|
||
from json import loads, JSONDecodeError | ||
from parsedatetime import Calendar | ||
from datetime import datetime | ||
from pytz import utc | ||
|
||
from rasa_sdk import Tracker | ||
|
||
API_URL = "http://nostradamus-core:8000/virtual_assistant" | ||
|
||
|
||
def convert_date(raw_date: str) -> tuple: | ||
def _get_datetime_range(date: str) -> tuple: | ||
""" Makes one day period by received date. | ||
Parameters | ||
---------- | ||
date: | ||
Date for report generating. | ||
Returns | ||
------- | ||
Datetime range. | ||
""" | ||
date = datetime.strptime(date, "%Y-%m-%d") | ||
from_date = date.astimezone(utc) | ||
from_date = from_date.strftime("%Y-%m-%dT%H:%M:%S.%f%z") | ||
|
||
to_date = ( | ||
date.replace(hour=23, minute=59, second=59) | ||
.astimezone(utc) | ||
.strftime("%Y-%m-%dT%H:%M:%S.%f%z") | ||
) | ||
|
||
return from_date, to_date | ||
|
||
parser = Calendar() | ||
date, status = parser.parse(raw_date) | ||
date = datetime(*date[:3]).strftime("%Y-%m-%d") | ||
return _get_datetime_range(date) | ||
|
||
|
||
def generate_report(payload: dict): | ||
""" Sends request for report generating. | ||
Parameters | ||
---------- | ||
payload: | ||
Additional filtration info. | ||
Returns | ||
------- | ||
Report data generator. | ||
""" | ||
url = f"{API_URL}/generate_report/" | ||
|
||
request = requests.post(url, json=payload) | ||
return request.json() | ||
|
||
|
||
def generate_report_payload(tracker: Tracker) -> dict: | ||
""" Creates a filtration payload from report form slots. | ||
Parameters | ||
---------- | ||
tracker: | ||
The state of conversation. | ||
Returns | ||
------- | ||
Filtration payload. | ||
""" | ||
slots = tracker.current_slot_values() | ||
payload = dict() | ||
|
||
for slot in slots: | ||
if f"{slot}_selection" in slots: | ||
try: | ||
payload[slot] = loads(slots[f"{slot}_selection"]) | ||
except (JSONDecodeError, TypeError): | ||
payload[slot] = slots[f"{slot}_selection"] | ||
elif slot == "period": | ||
payload[slot] = loads(slots[slot]) | ||
|
||
return payload | ||
|
||
|
||
def request_values(field: str) -> dict: | ||
""" Query unique values by field. | ||
Parameters | ||
---------- | ||
field: | ||
Issue field. | ||
Returns | ||
------- | ||
Unique values. | ||
""" | ||
params = {"field": field} | ||
url = f"{API_URL}/request_values/" | ||
|
||
request = requests.get(url, params=params) | ||
return request.json() | ||
|
||
|
||
def check_issues() -> bool: | ||
url = f"{API_URL}/issues_checker/" | ||
|
||
request = requests.get(url) | ||
|
||
return request.json().get("issues_loaded") |
Oops, something went wrong.