diff --git a/.env-dev.template b/.env-dev.template index 4db7c5e2..2de0cf25 100644 --- a/.env-dev.template +++ b/.env-dev.template @@ -22,4 +22,5 @@ AZURE_TRANSLATION_RESOURCE_LOCATION= STORAGE_ACCOUNT_URL= STORAGE_ACCOUNT_KEY= STORAGE_AUDIOFILES_CONTAINER= -ENCRYPTION_KEY= \ No newline at end of file +ENCRYPTION_KEY= +WA_API_HOST= \ No newline at end of file diff --git a/docs/references/dependencies.md b/docs/references/dependencies.md new file mode 100644 index 00000000..a47303c0 --- /dev/null +++ b/docs/references/dependencies.md @@ -0,0 +1,21 @@ +--- +layout: default +title: Dependencies +--- + +### Azure Blob Storage Account +JB Manager depends on [Azure Blob Storage](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction) for the persistent storage of input and output audio files through which user interacts with the bot. + +You can either use an existing Azure storage account or [create a new azure storage account](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal). After procurement of a storage account, [create a new container](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-portal#create-a-container) in that storage account which will be used to store the audio files. + +### Bhasini Translation API +JB Manager depends on Bhasini translation and speech processing functionalites for processing messages (both text and audio) in Indic languages. To access Bhasini API, register at [Bhashini](https://bhashini.gov.in/ulca/user/register). + +### Azure Speech and Translation API +JB Manager depends on Azure speech and translation API for processesing non-Indic languages as well as fallback to Bhasini API. +1. [Create a Speech resource](https://portal.azure.com/#create/Microsoft.CognitiveServicesSpeechServices) in the Azure portal. +2. Create a Translation resource in the Azure portal. +2. Procure the Speech and Translation resource key and region. After your resource is deployed, select Go to resource to view and manage keys. For more information about Azure AI services resources, see [Get the keys for your resource](https://learn.microsoft.com/en-in/azure/ai-services/multi-service-resource?pivots=azportal#get-the-keys-for-your-resource). + +### Pinnacle Whatsapp API +For conversation with Bot, currently JB Manager depends on whatsapp access provided by [Pinnacle](https://www.pinnacle.in/whatsapp-business-api). diff --git a/docs/tutorials/Quickstart.md b/docs/tutorials/Quickstart.md index 22cda674..d27130f0 100644 --- a/docs/tutorials/Quickstart.md +++ b/docs/tutorials/Quickstart.md @@ -1,25 +1,13 @@ -# Quickstart Guide +--- +layout: default +title: Quickstart +--- ## Prerequisite 1. Docker - Ensure that your system has docker engine installed and running. For installation, please refer to [docker engine installation instruction](https://docs.docker.com/engine/install/). 2. Docker Compose - Ensure docker compose is enabled along with docker engine. Please refer to [docker compose installation instruction](https://docs.docker.com/compose/install/). +3. NGROK -## Dependencies -### Azure Blob Storage Account -JB Manager depends on [Azure Blob Storage](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction) for the persistent storage of input and output audio files through which user interacts with the bot. - -You can either use an existing Azure storage account or [create a new azure storage account](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal). After procurement of a storage account, [create a new container](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-portal#create-a-container) in that storage account which will be used to store the audio files. - -### Bhasini Translation API -JB Manager depends on Bhasini translation and speech processing functionalites for processing messages (both text and audio) in Indic languages. To access Bhasini API, register at [Bhashini](https://bhashini.gov.in/ulca/user/register). - -### Azure Speech and Translation API -JB Manager depends on Azure speech and translation API for processesing non-Indic languages as well as fallback to Bhasini API. -1. [Create a Speech resource](https://portal.azure.com/#create/Microsoft.CognitiveServicesSpeechServices) in the Azure portal. -2. Create a Translation resource in the Azure portal. -2. Procure the Speech and Translation resource key and region. After your resource is deployed, select Go to resource to view and manage keys. For more information about Azure AI services resources, see [Get the keys for your resource](https://learn.microsoft.com/en-in/azure/ai-services/multi-service-resource?pivots=azportal#get-the-keys-for-your-resource). -### Pinnacle Whatsapp API -For conversation with Bot, currently JB Manager depends on whatsapp access provided by [Pinnacle](https://www.pinnacle.in/whatsapp-business-api). ## Running JB Manager 1. Clone and Change the directory to the project root. @@ -27,9 +15,8 @@ For conversation with Bot, currently JB Manager depends on whatsapp access provi ```bash $ cp .env-dev.template .env-dev ``` -3. Enter the values of `BHASHINI_USER_ID`,`BHASHINI_API_KEY`,`BHASHINI_PIPELINE_ID`,`AZURE_SPEECH_KEY`,`AZURE_SPEECH_REGION`,`AZURE_TRANSLATION_KEY`,`AZURE_TRANSLATION_RESOURCE_LOCATION`,`STORAGE_ACCOUNT_URL`, `STORAGE_ACCOUNT_KEY`, `STORAGE_AUDIOFILES_CONTAINER` -`WA_API_HOST` procured from the prerequisite step. -4. Generate an Encryption key using the following command +1. Update the values missing fields in the `.env-dev` file. +2. Generate an Encryption key using the following command ```bash $ dd if=/dev/urandom bs=32 count=1 2>/dev/null | openssl base64 ``` @@ -60,3 +47,16 @@ $ bash scripts/run.sh --stage api channel language flow frontend ``` ## Bot Installation and Go Live + +1. Go to [JB Manager UI](https://localhost:4173) +2. Click on install new bot and provide the required data to create your bot. The detailed information about the fields are given below: + 1. **Name [Mandatory]** is the name of the bot. + 2. **Code [Mandatory]** is the fsm.py file python code. Copy the contents of [python file](car_wash.py) and paste it. + 3. **Requirements [Optional if no specialised pacakge is used in code]** is the required packages name with their versions as we put them usually in requirements.txt or pyproject.toml dependencies. For the above example, we don't have any external dependencies. + 4. **index_urls [Optional]** is for custom and private packages links to download them from (This is for the case you use a library that your team has developed and internally published). + 5. **version [Mandatory]** - version of the bot. Put `1.0.0`. + 6. **required_credentials [Mandatory]** - + +3. * Once the bot is created, click on the **settings (⚙) icon** to enter the given credentials values and click save to save the credentials values. +4. Then click on the **play (▶️) icon** to activate the bot by providing the whatsapp bot number and whatsapp api key. +5. Once the above steps are completed, the bot status will be changed from **inactive to active.** \ No newline at end of file diff --git a/docs/tutorials/car_wash.py b/docs/tutorials/car_wash.py new file mode 100644 index 00000000..12248be1 --- /dev/null +++ b/docs/tutorials/car_wash.py @@ -0,0 +1,616 @@ +from datetime import datetime +import json +from os import error +import re +from typing import Any, Dict, Type + +from jb_manager_bot import ( + AbstractFSM, + FSMOutput, + MessageData, + MessageType, + OptionsListType, + Status, +) +from jb_manager_bot.parsers import OptionParser +from jb_manager_bot.parsers.utils import LLMManager + + +class CarWashDealerFSM(AbstractFSM): + """ + This is the FSM class for the Car Wash Dealer project. + """ + + states = [ + "zero", + "select_language", + "welcome_message_display", + "service_selection_display", + "service_selection_input", + "service_selection_logic", + "service_selection_fail_display", + "appointment_query_display", + "date_input", + "process_date_logic", + "get_time_of_day_display", + "time_of_day_input", + "time_of_day_logic", + "appointment_date_fail_display", + "appointment_time_fail_display", + "check_availability_plugin", + "plugin_fail_display", + "check_booking_status_logic", + "booking_confirmation_display", + "alternative_appointment_display", + "further_assistance_display", + "further_assistance_input", + "process_further_assistance_logic", + "further_assistance_fail_display", + "conclusion_display", + "end", + ] + transitions = [ + {"source": "zero", "dest": "select_language", "trigger": "next"}, + { + "source": "select_language", + "dest": "welcome_message_display", + "trigger": "next", + }, + { + "source": "welcome_message_display", + "dest": "service_selection_display", + "trigger": "next", + }, + { + "source": "service_selection_display", + "dest": "service_selection_input", + "trigger": "next", + }, + { + "source": "service_selection_input", + "dest": "service_selection_logic", + "trigger": "next", + }, + { + "source": "service_selection_logic", + "dest": "appointment_query_display", + "trigger": "next", + "conditions": "is_valid_service", + }, + { + "source": "service_selection_logic", + "dest": "service_selection_fail_display", + "trigger": "next", + }, + { + "source": "service_selection_fail_display", + "dest": "service_selection_display", + "trigger": "next", + }, + { + "source": "appointment_query_display", + "dest": "date_input", + "trigger": "next", + }, + { + "source": "date_input", + "dest": "process_date_logic", + "trigger": "next", + }, + { + "source": "process_date_logic", + "dest": "get_time_of_day_display", + "trigger": "next", + "conditions": "is_valid_date", + }, + { + "source": "process_date_logic", + "dest": "appointment_date_fail_display", + "trigger": "next", + }, + { + "source": "appointment_query_display", + "dest": "appointment_date_fail_display", + "trigger": "next", + }, + { + "source": "appointment_date_fail_display", + "dest": "appointment_query_display", + "trigger": "next", + }, + { + "source": "get_time_of_day_display", + "dest": "time_of_day_input", + "trigger": "next", + }, + { + "source": "time_of_day_input", + "dest": "time_of_day_logic", + "trigger": "next", + }, + { + "source": "time_of_day_logic", + "dest": "check_availability_plugin", + "trigger": "next", + "conditions": "is_valid_time", + }, + { + "source": "time_of_day_logic", + "dest": "appointment_time_fail_display", + "trigger": "next", + }, + { + "source": "appointment_time_fail_display", + "dest": "get_time_of_day_display", + "trigger": "next", + }, + { + "source": "check_availability_plugin", + "dest": "check_booking_status_logic", + "trigger": "next", + "conditions": "is_error_code_200", + }, + { + "source": "check_availability_plugin", + "dest": "plugin_fail_display", + "trigger": "next", + "conditions": "is_error_code_400", + }, + { + "source": "check_availability_plugin", + "dest": "plugin_fail_display", + "trigger": "next", + "conditions": "is_error_code_500", + }, + { + "source": "check_availability_plugin", + "dest": "plugin_fail_display", + "trigger": "next", + "conditions": "is_error_code_404", + }, + { + "source": "plugin_fail_display", + "dest": "service_selection_display", + "trigger": "next", + }, + { + "source": "check_booking_status_logic", + "dest": "booking_confirmation_display", + "trigger": "booking_confirmed", + "conditions": "is_booking_confirmed", + }, + { + "source": "check_booking_status_logic", + "dest": "alternative_appointment_display", + "trigger": "booking_not_confirmed", + }, + { + "source": "alternative_appointment_display", + "dest": "appointment_query_display", + "trigger": "next", + }, + { + "source": "booking_confirmation_display", + "dest": "further_assistance_display", + "trigger": "next", + }, + { + "source": "further_assistance_display", + "dest": "further_assistance_input", + "trigger": "next", + }, + { + "source": "further_assistance_input", + "dest": "process_further_assistance_logic", + "trigger": "next", + }, + { + "source": "process_further_assistance_logic", + "dest": "service_selection_display", + "trigger": "next", + "conditions": "needs_further_assistance", + }, + { + "source": "process_further_assistance_logic", + "dest": "conclusion_display", + "trigger": "next", + }, + { + "source": "further_assistance_fail_display", + "dest": "further_assistance_display", + "trigger": "next", + }, + { + "source": "conclusion_display", + "dest": "end", + "trigger": "next", + }, + ] + conditions = { + "is_valid_service", + "is_valid_date", + "is_valid_time", + "is_booking_confirmed", + "needs_further_assistance", + } + output_variables = set() + + def __init__(self, send_message: callable, credentials: Dict[str, Any] = None): + + if credentials is None: + credentials = {} + + self.credentials = {} + + self.credentials["AZURE_OPENAI_API_KEY"] = credentials.get( + "AZURE_OPENAI_API_KEY" + ) + if not self.credentials["AZURE_OPENAI_API_KEY"]: + raise ValueError("Missing credential: AZURE_OPENAI_API_KEY") + self.credentials["AZURE_OPENAI_API_VERSION"] = credentials.get( + "AZURE_OPENAI_API_VERSION" + ) + if not self.credentials["AZURE_OPENAI_API_VERSION"]: + raise ValueError("Missing credential: AZURE_OPENAI_API_VERSION") + self.credentials["AZURE_OPENAI_API_ENDPOINT"] = credentials.get( + "AZURE_OPENAI_API_ENDPOINT" + ) + if not self.credentials["AZURE_OPENAI_API_ENDPOINT"]: + raise ValueError("Missing credential: AZURE_OPENAI_API_ENDPOINT") + + self.plugins: Dict[str, AbstractFSM] = {} + super().__init__(send_message=send_message) + + def standard_ask_again(self, message=None): + self.status = Status.WAIT_FOR_ME + if message is None: + message = ( + "Sorry, I did not understand your question. Can you tell me again?" + ) + self.send_message(FSMOutput(message_data=MessageData(body=message))) + self.status = Status.MOVE_FORWARD + + def on_enter_select_language(self): + self.status = Status.WAIT_FOR_ME + self.send_message( + FSMOutput( + message_data=MessageData( + body="Please select your preferred language.\nबंधु से संपर्क करने के लिए धन्यवाद!\nकृपया अपनी भाषा चुनें।" + ), + type=MessageType.TEXT, + dialog="language", + dest="channel", + ) + ) + self.status = Status.WAIT_FOR_USER_INPUT + + def on_enter_welcome_message_display(self): + self.status = Status.WAIT_FOR_ME + self.send_message( + FSMOutput( + message_data=MessageData( + body="Hello! Welcome to Car Wash Dealer. How can I assist you today?" + ), + type=MessageType.TEXT, + dest="channel", + ) + ) + self.status = Status.MOVE_FORWARD + + def on_enter_service_selection_display(self): + self.status = Status.WAIT_FOR_ME + message = "Would you like to buy a car, service your car, test drive, buy accessories or parts, or get a warranty and protection plan?" + + options = [ + OptionsListType(id="1", title="Buy a Car"), + OptionsListType(id="2", title="Service Car"), + OptionsListType(id="3", title="Test Drive"), + OptionsListType(id="4", title="Buy Accessories or Parts"), + OptionsListType(id="5", title="Warranty and Protection Plan"), + ] + self.send_message( + FSMOutput( + type=MessageType.INTERACTIVE, + message_data=MessageData(body=message), + options_list=options, + menu_selector="Service Select", + menu_title="Service Select", + ) + ) + self.status = Status.MOVE_FORWARD + + def on_enter_service_selection_input(self): + self.status = Status.WAIT_FOR_ME + self.status = Status.WAIT_FOR_USER_INPUT + + def on_enter_service_selection_logic(self): + self.status = Status.WAIT_FOR_ME + options = [ + OptionsListType(id="1", title="Buy a Car"), + OptionsListType(id="2", title="Service Car"), + OptionsListType(id="3", title="Test Drive"), + OptionsListType(id="4", title="Buy Accessories or Parts"), + OptionsListType(id="5", title="Warranty and Protection Plan"), + ] + task = "The user is asked to select a service from the options." + + result = OptionParser.parse( + task, + options, + self.current_input, + azure_openai_api_key=self.credentials["AZURE_OPENAI_API_KEY"], + azure_openai_api_version=self.credentials["AZURE_OPENAI_API_VERSION"], + azure_endpoint=self.credentials["AZURE_OPENAI_API_ENDPOINT"], + ) + self.variables["service_id"] = result + self.status = Status.MOVE_FORWARD + + def on_enter_service_selection_fail_display(self): + self.standard_ask_again( + message="Sorry, I didn't get that. Please select one of the following options: Buy a Car, Service Car, Test Drive, Buy Accessories or Parts, or Warranty and Protection Plan." + ) + + def on_enter_appointment_query_display(self): + self.status = Status.WAIT_FOR_ME + message = "Great choice! When would you like to book the appointment? Please provide the date you would be interested YYYY-MM-DD?" + self.send_message(FSMOutput(message_data=MessageData(body=message))) + self.status = Status.MOVE_FORWARD + + def on_enter_date_input(self): + self.status = Status.WAIT_FOR_ME + self.status = Status.WAIT_FOR_USER_INPUT + + def on_enter_process_date_logic(self): + self.status = Status.WAIT_FOR_ME + result = LLMManager.llm( + messages=[ + LLMManager.sm( + "The user provides a date, convert it into a format of YYYY-MM-DD. If the date provided is wrong and could not be decided return None. Based on the user's input, return the output in json format. {'appointment_date': }" + ), + LLMManager.um(self.current_input), + ], + azure_openai_api_key=self.credentials["AZURE_OPENAI_API_KEY"], + azure_openai_api_version=self.credentials["AZURE_OPENAI_API_VERSION"], + azure_endpoint=self.credentials["AZURE_OPENAI_API_ENDPOINT"], + response_format={"type": "json_object"}, + model="gpt4", + ) + result = json.loads(result) + self.variables["appointment_date"] = result["appointment_date"] + self.status = Status.MOVE_FORWARD + + def on_enter_appointment_date_fail_display(self): + self.status = Status.WAIT_FOR_ME + self.standard_ask_again( + message="Sorry, I didn't get that. Please provide the date in the format YYYY-MM-DD." + ) + self.status = Status.MOVE_FORWARD + + def on_enter_appointment_time_fail_display(self): + self.status = Status.WAIT_FOR_ME + self.standard_ask_again( + message="Sorry, I didn't get that. Please select one of the following options: Morning, Afternoon, or Evening." + ) + self.status = Status.MOVE_FORWARD + + def on_enter_get_time_of_day_display(self): + self.status = Status.WAIT_FOR_ME + message = "What time of day would you prefer? Morning, afternoon, or evening?" + slots = [ + OptionsListType(id="1", title="Morning"), + OptionsListType(id="2", title="Afternoon"), + OptionsListType(id="3", title="Evening"), + ] + self.send_message( + FSMOutput( + type=MessageType.INTERACTIVE, + message_data=MessageData(body=message), + options_list=slots, + ) + ) + self.status = Status.MOVE_FORWARD + + def on_enter_time_of_day_input(self): + self.status = Status.WAIT_FOR_ME + self.status = Status.WAIT_FOR_USER_INPUT + + def on_enter_time_of_day_logic(self): + self.status = Status.WAIT_FOR_ME + valid_times = ["morning", "afternoon", "evening"] + slots = [ + OptionsListType(id="1", title="Morning"), + OptionsListType(id="2", title="Afternoon"), + OptionsListType(id="3", title="Evening"), + ] + task = "The user is asked to select a time of day from the options." + result = OptionParser.parse( + task, + slots, + self.current_input, + azure_openai_api_key=self.credentials["AZURE_OPENAI_API_KEY"], + azure_openai_api_version=self.credentials["AZURE_OPENAI_API_VERSION"], + azure_endpoint=self.credentials["AZURE_OPENAI_API_ENDPOINT"], + ) + self.variables["appointment_id"] = result + self.status = Status.MOVE_FORWARD + + def availability_plugin(self): + return { + "error_code": 400, + "booking_status": "confirmed", + "appointment_image": "https://d27jswm5an3efw.cloudfront.net/app/uploads/2019/08/image-url-3.jpg", + } + + def on_enter_check_availability_plugin(self): + self.status = Status.WAIT_FOR_ME + self.send_message( + FSMOutput( + type=MessageType.TEXT, + message_data=MessageData( + body="Checking availability for the requested date and time..." + ), + ) + ) + + response = self.availability_plugin() + self.variables["booking_status"] = response["booking_status"] + self.variables["appointment_image"] = response["appointment_image"] + self.variables["error_code"] = response["error_code"] + self.status = Status.MOVE_FORWARD + + def on_enter_check_booking_status_logic(self): + self.status = Status.WAIT_FOR_ME + if self.variables["booking_status"] == "confirmed": + self.trigger("booking_confirmed") + else: + self.trigger("booking_not_confirmed") + + def on_enter_booking_confirmation_display(self): + self.status = Status.WAIT_FOR_ME + message = f"Your appointment has been successfully booked for {self.variables['service_selection']} on {self.variables['appointment_date']} at {self.variables['time_in_hr']}." + self.send_message( + FSMOutput( + type=MessageType.IMAGE, + message_data=MessageData(body=message), + media_url=self.variables["appointment_image"], + ) + ) + self.status = Status.MOVE_FORWARD + + def on_enter_alternative_appointment_display(self): + self.status = Status.WAIT_FOR_ME + message = "The requested date and time are not available. Please provide another date and time for your appointment." + self.send_message(FSMOutput(message_data=MessageData(body=message))) + self.status = Status.WAIT_FOR_USER_INPUT + + def on_enter_further_assistance_display(self): + self.status = Status.WAIT_FOR_ME + message = "Is there anything else I can help you with today?" + options = [ + OptionsListType(id="1", title="Yes"), + OptionsListType(id="2", title="No"), + ] + self.send_message( + FSMOutput( + type=MessageType.INTERACTIVE, + message_data=MessageData(body=message), + options_list=options, + ) + ) + self.status = Status.MOVE_FORWARD + + def on_enter_further_assistance_input(self): + self.status = Status.WAIT_FOR_ME + self.status = Status.WAIT_FOR_USER_INPUT + + def on_enter_process_further_assistance_logic(self): + self.status = Status.WAIT_FOR_ME + options = [ + OptionsListType(id="1", title="Yes"), + OptionsListType(id="2", title="No"), + ] + result = OptionParser.parse( + "The user provides a response to the 'Is there anything else I can help you with today?'.", + options, + self.current_input, + azure_openai_api_key=self.credentials["AZURE_OPENAI_API_KEY"], + azure_openai_api_version=self.credentials["AZURE_OPENAI_API_VERSION"], + azure_endpoint=self.credentials["AZURE_OPENAI_API_ENDPOINT"], + ) + if result == "1": + self.variables["further_assistance"] = "yes" + else: + self.variables["further_assistance"] = "no" + self.status = Status.MOVE_FORWARD + + def on_enter_further_assistance_fail_display(self): + self.standard_ask_again( + message="Sorry, I didn't get that. Please select one of the following options: Yes or No." + ) + + def on_enter_conclusion_display(self): + self.status = Status.WAIT_FOR_ME + message = "Thank you for choosing Car Wash Dealer. Have a great day!" + self.send_message(FSMOutput(message_data=MessageData(body=message))) + self.status = Status.MOVE_FORWARD + + def is_valid_service(self): + valid_services = [ + "Buy a Car", + "Service Car", + "Test Drive", + "Buy Accessories or Parts", + "Warranty and Protection Plan", + ] + if ( + self.variables["service_id"] + and self.variables["service_id"].isnumeric() + and int(self.variables["service_id"]) <= 5 + ): + valid_services = [service.title() for service in valid_services] + index = int(self.variables["service_id"]) - 1 + self.variables["service_selection"] = valid_services[index] + self.variables["service_selection"] = ( + self.variables["service_selection"].strip().title() + ) + return self.variables["service_selection"] in valid_services + return False + + def is_valid_date(self): + if self.variables["appointment_date"]: + try: + datetime.strptime(self.variables["appointment_date"], "%Y-%m-%d") + return True + except ValueError: + return False + return False + + def is_valid_time(self): + if ( + self.variables["appointment_id"] + and self.variables["appointment_id"].isnumeric() + and int(self.variables["appointment_id"]) <= 3 + ): + valid_times = ["morning", "afternoon", "evening"] + self.variables["appointment_time"] = valid_times[ + int(self.variables["appointment_id"]) - 1 + ] + + if self.variables["appointment_time"] in valid_times: + self.variables["time_in_hr"] = { + "morning": "09:00:00", + "afternoon": "13:00:00", + "evening": "17:00:00", + }[self.variables["appointment_time"]] + return True + return False + return False + + def on_enter_plugin_fail_display(self): + self.status = Status.WAIT_FOR_ME + self.send_message( + FSMOutput( + message_data=MessageData( + body="Sorry, I am unable to process your request at the moment, error while plugin call. Please try again later." + ), + ) + ) + self.status = Status.MOVE_FORWARD + + def is_booking_confirmed(self): + return self.variables["booking_status"] == "confirmed" + + def needs_further_assistance(self): + return self.variables["further_assistance"] == "yes" + + def is_error_code_200(self): + return self.variables["error_code"] == 200 + + def is_error_code_400(self): + return self.variables["error_code"] == 400 + + def is_error_code_500(self): + return self.variables["error_code"] == 500 + + def is_error_code_404(self): + return self.variables["error_code"] == 404