Exemple #1
0
class WorkoutController:
    def __init__(self):
        self.db = DbManager()

    def get_workout_by_name(self, name):
        return self.db.select_workout_by_name(name)

    def check_if_user_exist(self, user_id):
        if len(self.db.get_last_user_workouts(user_id)) > 0:
            return True
        else:
            # TODO: Save user in DB
            return False

    def save_user_workout(self, user_id, workout_id, intensity_rating,
                          daily_form):
        self.db.save_user_workout(user_id, workout_id, intensity_rating,
                                  daily_form)

    def get_workout_by_user(self, intensity, duration, body_part, user_id):
        """
        Get a workout depending on the parameters the user told alexa
        :param intensity:
        :param duration:
        :param body_part:
        :param user_id:
        :return:
        """

        'get all workouts from db that match the selected intensity value'
        workouts = self.db.select_workouts_by_user_parameters(intensity)
        workouts_body_part_match = []
        workouts_duration_match = []

        if workouts.__len__() == 0:
            'there is no such workout'
            return []

        if workouts.__len__() > 1:
            'get the last user workout from db'
            last_user_workout = self.db.get_last_user_workout(user_id)

            index = 0
            while index < workouts.__len__():
                workout = workouts[index]
                workout_removed = False

                'exclude the last done workout from match list'
                if workout["workout"]["id"] == last_user_workout["workout_id"]:
                    workout_removed = True
                else:
                    'exclude such workouts that do not match the selected body part'
                    if workout["workout"]["body_part"] != body_part:
                        workout_removed = True
                    else:
                        workouts_body_part_match.append(workout)

                    'exclude such workouts that do not match the selected duration'
                    if workout["workout"]["duration"] != duration:
                        workout_removed = True
                    else:
                        workouts_duration_match.append(workout)

                if workout_removed:
                    workouts.remove(workout)
                    index -= 1

                index += 1

            'if the workouts list does still contain workouts then they exactly match the given parameters' \
                'select a workout at random to return'
            if workouts.__len__() > 0:
                random.shuffle(workouts)
                return workouts[0]

            'prioritize matching body parts over matching durations' \
                'select a workout at random to return'
            if workouts_body_part_match.__len__() > 0:
                random.shuffle(workouts_body_part_match)
                return workouts_body_part_match[0]

            if workouts_duration_match.__len__() > 0:
                random.shuffle(workouts_duration_match)
                return workouts_duration_match[0]

            'fall back option if no workout was found in db that matches any of the given parameters' \
                'return the last done workout again'
            return last_user_workout
        else:
            'return the only workout with this parameters'
            return workouts[0]

    def get_workout_by_alexa(self, user_id, todays_form):
        """
        IN PROGRESS
        This function is used if alexa should choose a workout.
        In this case alexa takes the previous done workouts into account
        and tries to select a good workout for the user

        Alexa takes following parameters into account:
        - The last workout intensity
        - The last workout body part
        - The last workout intensity rating
        - The last workout fitness rating
        - The overall development of the user
        :return: the workout selected
        """

        last_user_workout = self.db.get_last_user_workout(user_id)

        if last_user_workout is not None:
            """
            Calculate best workout
            """
            low_duration = 7
            high_duration = 14

            selected_duration = 7

            last_workout_data = self.db.select_workout_by_id(
                last_user_workout['workout_id'])['workout'][0]
            last_rated_intensity = last_user_workout['intensity_rating']
            last_workout_total_intensity = last_workout_data['intensity']
            last_workout_duration = last_workout_data['duration']

            new_workout_intensity = self.calculate_workout_intensity(
                user_id, last_workout_total_intensity, last_rated_intensity,
                todays_form)

            selected_intensity = new_workout_intensity

            selected_body_part = self.calculate_workout_body_part(user_id)

            # define intensity and duration by calculated intensity
            if last_workout_total_intensity < new_workout_intensity and last_workout_duration == low_duration:
                selected_intensity = last_workout_total_intensity
                selected_duration = high_duration

            if last_workout_total_intensity < new_workout_intensity and last_workout_duration == high_duration:
                selected_intensity = new_workout_intensity
                selected_duration = low_duration

            if last_workout_total_intensity > new_workout_intensity and last_workout_duration == high_duration:
                selected_intensity = last_workout_total_intensity
                selected_duration = low_duration

            if last_workout_total_intensity < new_workout_intensity and last_workout_duration == low_duration:
                selected_intensity = new_workout_intensity
                selected_duration = high_duration

            return self.get_workout_by_user(selected_intensity,
                                            selected_duration,
                                            selected_body_part, user_id)
        else:
            """
            The user has no last workouts - Therefore select a basic beginner workout
            """
            workouts = self.db.select_workout_by_intensity_and_bodypart(1, 0)

            if workouts.__len__() > 0:
                return workouts[0]
            else:
                return []

    def calculate_workout_body_part(self, user_id):
        last_user_workouts = self.db.get_last_user_workouts(user_id)

        if last_user_workouts.__len__() > 1:
            recent_workout = last_user_workouts[0]
            previous_workout = last_user_workouts[1]

            today = pendulum.now()
            week_start = today.start_of('week')

            tz = pendulum.timezone('Europe/Paris')
            previous_workout_date = pendulum.from_timestamp(
                previous_workout["workout_date"])
            previous_workout_date = tz.convert(previous_workout_date)

            if week_start <= previous_workout_date:
                recent_workout_body_part = self.db.select_workout_by_id(
                    recent_workout['workout_id'])['workout'][0]["body_part"]
                previous_workout_body_part = self.db.select_workout_by_id(
                    previous_workout['workout_id'])['workout'][0]["body_part"]

                body_parts_array = numpy.arange(4)

                body_parts_array = body_parts_array[
                    body_parts_array != recent_workout_body_part]

                body_parts_array = body_parts_array[
                    body_parts_array != previous_workout_body_part]

                if body_parts_array.__len__() == 3:
                    return random.choice(body_parts_array)

                if body_parts_array.__len__() == 2:
                    if recent_workout_body_part != 0 and previous_workout_body_part != 0:
                        return body_parts_array[body_parts_array != 0][0]
                    else:
                        return random.choice(body_parts_array)
            else:
                return 0
        else:
            return 0

    def calculate_workout_intensity(
        self,
        user_id,
        last_workout_total_intensity,
        last_workout_intensity_rating,
        todays_form,
    ):
        """
        Based on the given parameters, this function calculates the intensity of the next workout to select
        :param user_id
        :param last_workout_total_intensity:
        :param last_workout_intensity_rating:
        :param todays_form:
        :return:
        """
        fitness_median = self.calculate_fitness_median(user_id)
        last_intense_delta = last_workout_total_intensity - last_workout_intensity_rating
        form_delta = todays_form - fitness_median

        min_intensity = 1
        max_intensity = 5

        intensity_to_return = min_intensity

        if -2 < last_intense_delta < 2:
            if -2 < form_delta < 2:
                if last_intense_delta == -1:
                    if form_delta > 0:
                        intensity_to_return = last_workout_total_intensity
                    else:
                        intensity_to_return = last_workout_total_intensity - 1
                if last_intense_delta == 0:
                    intensity_to_return = last_workout_total_intensity
                if last_intense_delta == 1:
                    if form_delta < 0:
                        intensity_to_return = last_workout_total_intensity
                    else:
                        intensity_to_return = last_workout_total_intensity + 1
            if form_delta < -1:
                intensity_to_return = last_workout_total_intensity - 1
            if form_delta > 1:
                intensity_to_return = last_workout_total_intensity + 1
        else:
            if last_intense_delta < -1:
                intensity_to_return = last_workout_total_intensity - 1
            if last_intense_delta > 1:
                intensity_to_return = last_workout_total_intensity + 1

        if intensity_to_return < min_intensity:
            intensity_to_return = min_intensity

        if intensity_to_return > max_intensity:
            intensity_to_return = max_intensity

        return intensity_to_return

    def calculate_fitness_median(self, user_id):
        """
        Calculates the median of the last fitness forms of the user
        :return:
        """
        fitness_array = self.db.get_user_fitness_ratings_array(user_id)

        if fitness_array:
            return numpy.round(numpy.median(fitness_array))

        return 0

    # chooses a random sentence from bunch of templates. State maps to the templates of the yaml
    @staticmethod
    def get_speech(state):

        with codecs.open('speechCollection.yaml', 'r',
                         encoding='utf-8') as stream:
            doc = yaml.load(stream)
            speech_list = doc[state]

            random.shuffle(speech_list)
            alexa_speaks = speech_list[0]
            try:
                alexa_speaks = alexa_speaks.encode('utf-8')
            except AttributeError:
                print alexa_speaks
                alexa_speaks = speech_list[0]

        return alexa_speaks

    @staticmethod
    def analyze_emotion_by_text(untranslated_text):
        """
        The function translates german text inside the ibm cloud and then sends them to the
        ibm cloud watson emotion analyzer - after that the result is checked
        :param untranslated_text: contains the german text recognised by alexa
        :return: Number in range 1 to 5
        """

        language_translator = LanguageTranslatorV3(
            version='2019-06-25',
            iam_apikey=
            'XXX-XXX-XXX',  # replace here api key IBM Language Translator
            url='https://gateway-fra.watsonplatform.net/language-translator/api'
        )
        translated_text = language_translator.translate(
            text=untranslated_text, model_id='de-en').get_result()
        print(json.dumps(translated_text, indent=2,
                         ensure_ascii=False)).encode('utf8')

        tone_analyzer = ToneAnalyzerV3(
            version='2019-06-25',
            iam_apikey=
            'XXX-XXX-XXX',  # replace here api key IBM Watson Tone Analyzer
            url='https://gateway-fra.watsonplatform.net/tone-analyzer/api',
        )

        translated_text = yaml.load(json.dumps(
            translated_text))  # decodes unicode dictionary. only cuz python2.7
        translated_text = translated_text['translations'][0]['translation']

        tone_analysis = tone_analyzer.tone(
            {
                'text': translated_text
            }, content_type='application/json').get_result()
        print(json.dumps(tone_analysis, indent=2)).encode('utf8')

        return WorkoutController._get_emotion_level_based_on_json(
            tone_analysis)

    @staticmethod
    def _get_emotion_level_based_on_json(tone_analysis):
        """
        Chooses the level of confidence (1-5) for the workout based on the emotions.
        :param tone_analysis: analysis of the emotion as json (dict)
        :return:
        """

        # if the analyzer could not figure it out map to normal mood
        emotion_dict = tone_analysis["document_tone"]["tones"]
        if len(emotion_dict) < 1:
            return 3
        # when there is only one emotion recognized
        elif len(emotion_dict) < 2:
            if emotion_dict[0]['tone_name'] == 'Joy':
                return 5

            elif emotion_dict[0]['tone_name'] == 'Anger':
                return 4

            elif emotion_dict[0]['tone_name'] == 'Confident':
                if emotion_dict[0]['score'] > 0.9:
                    return 4
                else:
                    return 3

            elif emotion_dict[0]['tone_name'] == 'Sadness':
                return 1

            elif emotion_dict[0]['tone_name'] == 'Tentative':
                return 3

            elif emotion_dict[0]['tone_name'] == 'Analytical':
                return 2

            else:
                return 3

        # there are more than two emotion recognised
        else:
            # find the two most highest emotions
            list_of_emotions = []
            for emotion in emotion_dict:
                list_of_emotions.append(
                    (emotion['tone_name'], emotion['score']))
            list_of_emotions.sort(key=lambda x: x[1], reverse=True)
            print(list_of_emotions)

            if list_of_emotions[0][0] == 'Joy':
                return 5

            elif list_of_emotions[0][0] == 'Anger':
                return 4

            elif list_of_emotions[0][0] == 'Confident':

                if list_of_emotions[1][0] == 'Anger':
                    return 4
                elif list_of_emotions[1][0] == 'Sadness':
                    return 1
                elif list_of_emotions[1][0] == 'Analytical':
                    return 4
                elif list_of_emotions[1][0] == 'Joy':
                    return 4
                elif list_of_emotions[1][0] == 'Fear':
                    return 2
                elif list_of_emotions[1][0] == 'Tentative':
                    return 3
                else:
                    return 3

            elif list_of_emotions[0][0] == 'Sadness':

                if list_of_emotions[1][0] == 'Joy':
                    return 2
                elif list_of_emotions[1][0] == 'Anger':
                    return 3
                elif list_of_emotions[1][0] == 'Confident':
                    return 2
                else:
                    return 1

            elif list_of_emotions[0][0] == 'Tentative':
                if list_of_emotions[1][0] == 'Joy':
                    return 4
                elif list_of_emotions[1][0] == 'Analytical':
                    return 3
                elif list_of_emotions[1][0] == 'Sadness':
                    return 2
                elif list_of_emotions[1][0] == 'Fear':
                    return 2
                else:
                    return 3

            elif list_of_emotions[0][0] == 'Analytical':
                if list_of_emotions[1][0] == 'Joy':
                    return 4
                elif list_of_emotions[1][0] == 'Sadness':
                    return 2
                elif list_of_emotions[1][0] == 'Fear':
                    return 2
                elif list_of_emotions[1][0] == 'Confident':
                    return 4
                else:
                    return 3

            else:
                return 3

    @staticmethod
    def check_context_in_text(spoken_text, list1, list2):
        """
        This is a fallback method if something wents wrong with wit.ai
        Checks the context of the user, given to lists with possible answers which could be inside.
        E.g. Alexa: "Which exercise do you want? A premade or a specifc?
             User : "******"yours","alexa","premade"]
             Second list would be ["mine","specific","not premade"]
             The list with the most occurrences will be the context.
        :param spoken_text: spoken words by user
        :param list1: possible words which should occur in list
        :param list2: possible words which should occur in list
        :return: The list with the most occurrences (context).
        """

        context1 = 0
        context2 = 0

        if any(word in spoken_text for word in list1):
            context1 = +1

        if any(word in spoken_text for word in list2):
            context2 = +2

        print(context1)

        if context1 > context2:
            return list1
        elif context2 > context1:
            return list2
        else:
            return []

    @staticmethod
    def check_context_wit_ai(spoken_text):
        """
        Sends the spoken text to wit.ai and recognizes the context/intent of it and passes a json back.
        :param spoken_text: german spoken text by the user
        :return: context as string
        """

        client = Wit('XXX')  # replace here API key wit ai
        response = client.message(spoken_text)
        # return found intent
        print response
        try:
            answer = response['entities'].keys()[0]
        except IndexError:
            return "not_found"

        return answer

    @staticmethod
    def get_feedback_out_of_context(spoken_text):

        emotion = WorkoutController.analyze_emotion_by_text(spoken_text)
        feeling = WorkoutController.check_context_wit_ai(spoken_text)

        result_of_feeling = emotion

        if result_of_feeling > 5:
            result_of_feeling = 5
        elif result_of_feeling < 1:
            result_of_feeling = 1

        result_of_feeling = int(round(result_of_feeling))

        return result_of_feeling