Esempio n. 1
0
def get_training(training_id) -> (dict, int):
    """
    Endpoint to get information about a training by its identifier.

    :param training_id: Training identifier
    :return: Dictionary with training information and 'OK' message, or
        a dictionary with an explanation and 404 HTTP return code if a training was not found, or
        an empty dictionary with 404 HTTP return code if access was denied.
    """
    if not check_access({'_id': ObjectId(training_id)}):
        return {}, 404
    training_db = TrainingsDBManager().get_training(training_id)
    if training_db is None:
        return {
            'message': 'No training with training_id = {}.'.format(training_id)
        }, 404
    return get_training_information(training_db)
Esempio n. 2
0
def get_presentation_record_file(presentation_record_file_id: str):
    """
    Endpoint to get a presentation record file by its identifier.

    :param presentation_record_file_id: Presentation record file identifier.
    :return: Presentation record file with the given identifier, or
            a dictionary with an explanation and 404 HTTP return code if a presentation record file was not found, or
            an empty dictionary with 404 HTTP return code if access was denied.
    """
    if not check_access(
        {'presentation_record_file_id': ObjectId(presentation_record_file_id)
         }):
        return {}, 404
    presentation_record_file = DBManager().get_file(
        presentation_record_file_id)
    if not presentation_record_file:
        return {
            'message':
            'No presentation record file with presentation_record_file_id = {}.'
            .format(presentation_record_file_id),
        }, 404
    logger.debug(
        'Got presentation record file with presentation_record_file_id = {}.'.
        format(presentation_record_file_id))
    as_attachment = safe_strtobool(request.args.get('as_attachment',
                                                    default=True),
                                   on_error=True)
    audiofile_len = presentation_record_file.length

    response = make_response(
        send_file(
            presentation_record_file,
            attachment_filename='{}.mp3'.format(presentation_record_file_id),
            as_attachment=as_attachment,
        ))

    response.headers['accept-ranges'] = "bytes"
    response.headers['Content-Length'] = audiofile_len
    response.headers['Content-Range'] = "bytes 0-{}/{}".format(
        audiofile_len, audiofile_len - 1)
    response.headers['content-type'] = presentation_record_file.content_type

    return response, 200
Esempio n. 3
0
def append_slide_switch_timestamp(training_id: str) -> (dict, int):
    """
    Endpoint to append a slide switch timestamp.

    :param training_id: Training identifier.
    :return: {'message': 'OK'}, or
        an empty dictionary with 404 HTTP return code if access was denied or training status is not NEW.
    """
    if not check_access({'_id': ObjectId(training_id)}):
        return {}, 404
    if not is_admin():
        training_db = TrainingsDBManager().get_training(training_id)
        if training_db.status != TrainingStatus.IN_PROGRESS:
            return {}, 404
    timestamp = request.args.get('timestamp', time.time(), float)
    TrainingsDBManager().append_timestamp(training_id, timestamp)
    logger.debug(
        'Slide switch: training_id = {}, timestamp = {}, time.time() = {}.'.
        format(training_id, timestamp, time.time()))
    return {'message': 'OK'}, 200
Esempio n. 4
0
def start_training_processing(training_id: str) -> (dict, int):
    """
    Endpoint to start training processing of a training by its identifier.

    :param training_id: Training identifier.
    :return: {'message': 'OK'}, or
        an empty dictionary with 404 HTTP return code if access was denied or training status is not NEW.
    """
    logger.info(f'start_training_processing. training_id = {training_id}')
    if not check_access({'_id': ObjectId(training_id)}):
        logger.info(
            f'start_training_processing. not access to training_id = {training_id}'
        )
        return {}, 404
    if not is_admin():
        training_db = TrainingsDBManager().get_training(training_id)
        if training_db.status != TrainingStatus.SENT_FOR_PREPARATION:
            logger.info(
                f"start_training_processing. user not admin AND training_db.status != TrainingStatus.IN_PROGRESS (it's {training_db.status})"
            )
            return {}, 404
    TrainingManager().add_training(training_id)
    return {'message': 'OK'}, 200
Esempio n. 5
0
def add_presentation_record(training_id: str) -> (dict, int):
    """
    Endpoint to add presentation record to a training by its identifier.

    :param training_id: Training identifier
    :return: {'message': 'OK'}, or
        an empty dictionary with 404 HTTP return code if access was denied, record duration is not convertible to float,
         or presentation record has already been added.

    TODO: check that presentation record is mp3
    TODO: check that duration is consistent
    """
    if not check_access({'_id': ObjectId(training_id)}):
        return {}, 404
    if 'presentationRecord' not in request.files:
        return {'message': 'No presentation record file.'}, 404
    presentation_record_file = request.files['presentationRecord']
    if 'presentationRecordDuration' not in request.form:
        return {'message': 'No presentation record duration.'}, 404
    presentation_record_duration = request.form.get(
        'presentationRecordDuration', default=None, type=float)
    if presentation_record_duration is None:
        return {}, 404
    if not is_admin():
        training_db = TrainingsDBManager().get_training(training_id)
        if training_db.presentation_record_file_id is not None:
            return {}, 404
    TrainingsDBManager().change_training_status_by_training_id(
        training_id, TrainingStatus.SENT_FOR_PREPARATION)
    presentation_record_file_id = DBManager().add_file(
        presentation_record_file)
    TrainingsDBManager().add_presentation_record(
        training_id,
        presentation_record_file_id,
        presentation_record_duration,
    )
    return {'message': 'OK'}, 200
Esempio n. 6
0
def get_presentation_file(presentation_file_id):
    """
    Endpoint to get a presentation file by presentation ID.

    :param presentation_file_id: Presentation file identifier
    :return: Presentation file with the given identifier, or
        a dictionary with an explanation and 404 HTTP return code if a presentation file or a preview was not found, or
        an empty dictionary with 404 HTTP return code if access was denied.
    """
    if not check_access({'presentation_file_id': presentation_file_id}):
        return {}, 404
    presentation_file_id = PresentationFilesDBManager().get_presentation_file(
        presentation_file_id)
    if presentation_file_id is None:
        return {
            'message':
            'No presentation file with presentation_file_id = {}.'.format(
                presentation_file_id)
        }, 404
    else:
        presentation_file_id = presentation_file_id.file_id
    presentation_file = DBManager().get_file(presentation_file_id)
    if presentation_file is None:
        return {
            'message':
            'No presentation binary file with presentation_file_id = {}.'.
            format(presentation_file_id)
        }, 404

    logger.debug('Got presentation file with presentation_file_id = {}'.format(
        presentation_file_id))
    as_attachment = safe_strtobool(request.args.get('as_attachment',
                                                    default=False),
                                   on_error=False)
    return send_file(presentation_file,
                     mimetype='application/pdf',
                     as_attachment=as_attachment), 200
Esempio n. 7
0
def get_remaining_processing_time_by_training_id(
        training_id: str) -> (dict, int):
    """
    Endpoint to get estimated time until the training with the provided training identifier will be processed.
    Estimation is calculated as a half of durations of records that should be processed before the training
        (including presentation record belongs to the training).

    :param training_id: Training identifier.
    :return: Dictionary with estimated processing time and 'OK' message, or
        a dictionary with an explanation and 404 HTTP return code if a training was not found, or
        an empty dictionary with 404 HTTP return code if access was denied.
    """
    if not check_access({'_id': ObjectId(training_id)}):
        return {}, 404
    logger.debug(
        'Estimating processing time of a training with training_id = {}.'.
        format(training_id))
    current_training_db = TrainingsDBManager().get_training(training_id)
    if not current_training_db:
        return {
            'message': 'No training with training_id = {}.'.format(training_id)
        }, 404
    current_training_status = current_training_db.status
    if TrainingStatus.is_terminal(current_training_status):
        logger.debug(
            'Current training status is {} and is terminal, training_id = {}.'.
            format(current_training_status, training_id))
        return {'processing_time_remaining': 0, 'message': 'OK'}, 200
    time_estimation = 0
    trainings_with_recognizing_audio_status = \
        TrainingsDBManager().get_trainings_filtered({'audio_status': AudioStatus.RECOGNIZING})
    for training in trainings_with_recognizing_audio_status:
        time_since_audio_status_last_update = datetime.now().timestamp(
        ) - training.audio_status_last_update.time
        estimated_remaining_recognition_time = \
            training.presentation_record_duration / 2 - time_since_audio_status_last_update
        message = 'Audio status is RECOGNIZING, training_id = {}, status last update = {}, {} seconds ago, '\
                  'presentation record duration = {}.\nEstimated remaining recognition time = {}.'\
            .format(training.pk, training.audio_status_last_update, time_since_audio_status_last_update,
                    training.presentation_record_duration, estimated_remaining_recognition_time)
        if estimated_remaining_recognition_time < 0:
            message += ' Setting to 0.'
        logger.debug(message)
        time_estimation += max(0, estimated_remaining_recognition_time)
    current_presentation_record_file_id = current_training_db.presentation_record_file_id
    current_presentation_record_file_generation_time = current_presentation_record_file_id.generation_time if current_presentation_record_file_id else None
    if current_presentation_record_file_generation_time:
        # if training doesn't have presentation_record_file_id -> skip this
        trainings_with_audio_status_before_recognizing = TrainingsDBManager(
        ).get_trainings_filtered(filters={
            '$or': [{
                'audio_status': {
                    '$in': [AudioStatus.NEW, AudioStatus.SENT_FOR_RECOGNITION]
                }
            }]
        }, )
        for training in trainings_with_audio_status_before_recognizing:
            if not training.presentation_record_file_id or (
                    not current_presentation_record_file_generation_time):
                continue
            presentation_record_file_generation_time = training.presentation_record_file_id.generation_time
            training_id = training.pk
            try:
                time_estimation_add = training.presentation_record_duration / 2
            except (AttributeError, TypeError):
                continue
            if presentation_record_file_generation_time > current_presentation_record_file_generation_time:
                continue
            logger.debug(
                'Presentation record file generation time for a training with training_id = {} is {}. '
                'It is earlier than or equals to generation time for the current training with training_id = {} '
                'that is {}. Adding {} seconds.'.format(
                    training_id,
                    presentation_record_file_generation_time,
                    training_id,
                    current_presentation_record_file_generation_time,
                    time_estimation_add,
                ))
            time_estimation += time_estimation_add
    trainings_with_sent_for_processing_or_processing_status = TrainingsDBManager(
    ).get_trainings_filtered(filters={
        '$or': [{
            'status': {
                '$in': [
                    TrainingStatus.PREPARED,
                    TrainingStatus.SENT_FOR_PROCESSING,
                    TrainingStatus.PROCESSING
                ]
            }
        }]
    }, )
    if current_training_status not in \
            [TrainingStatus.NEW, TrainingStatus.IN_PROGRESS, TrainingStatus.SENT_FOR_PREPARATION, TrainingStatus.PREPARING]:
        current_recognized_audio_generation_time = current_training_db.recognized_audio_id.generation_time
    else:
        current_recognized_audio_generation_time = None
    for training in trainings_with_sent_for_processing_or_processing_status:
        recognized_audio_generation_time = training.recognized_audio_id.generation_time
        if not current_recognized_audio_generation_time or \
                recognized_audio_generation_time > current_recognized_audio_generation_time:
            continue
        time_estimation_add = 20
        logger.debug(
            'Current audio status is {}, training_id = {}. Adding {} seconds.'.
            format(training.status, training_id, time_estimation_add))
        time_estimation += time_estimation_add
    if time_estimation == 0:
        time_estimation = 20
    return {
        'processing_time_remaining': round(time_estimation),
        'message': 'OK'
    }, 200
Esempio n. 8
0
def get_training_statistics(training_id: str) -> (dict, int):
    """
    Endpoint to get statistics of a training by its identifier

    :param training_id: Training identifier
    :return: Dictionary with statistics of the training with the given identifier, or
        a dictionary with an explanation and 404 HTTP return code if something went wrong, or
        an empty dictionary with 404 HTTP return code if the file was not found or access was denied.
    """
    if not check_access({'_id': ObjectId(training_id)}):
        return {}, 404
    training_db = TrainingsDBManager().get_training(training_id)
    presentation_file_id = training_db.presentation_file_id
    presentation_file_name = DBManager().get_file_name(presentation_file_id)
    if presentation_file_name is None:
        return {
            'message':
            'No presentation file with presentation_file_id = {}.'.format(
                presentation_file_id)
        }, 404
    presentation_record_file_id = training_db.presentation_record_file_id
    training_status = training_db.status
    audio_status = training_db.audio_status
    presentation_status = training_db.presentation_status
    slides_time = []
    if audio_status == AudioStatus.PROCESSED:
        # here we need to process audio_slides
        audio = Audio.from_json_file(DBManager().get_file(
            training_db.audio_id))
        slides_time = proccess_training_slides_info(audio)
    feedback = training_db.feedback
    criteria_pack_id = training_db.criteria_pack_id
    feedback_evaluator_id = training_db.feedback_evaluator_id
    remaining_processing_time_estimation, remaining_processing_time_estimation_code = \
        get_remaining_processing_time_by_training_id(training_id)
    if remaining_processing_time_estimation['message'] != 'OK':
        return remaining_processing_time_estimation, remaining_processing_time_estimation_code
    return {
        'message':
        'OK',
        'presentation_file_id':
        str(presentation_file_id),
        'presentation_file_name':
        presentation_file_name,
        'presentation_record_file_id':
        str(presentation_record_file_id),
        'feedback':
        feedback,
        'training_status':
        training_status,
        'audio_status':
        audio_status,
        'presentation_status':
        presentation_status,
        'slides_time':
        slides_time,
        'remaining_processing_time_estimation':
        remaining_processing_time_estimation['processing_time_remaining'],
        'criteria_pack_id':
        criteria_pack_id,
        'feedback_evaluator_id':
        feedback_evaluator_id,
    }, 200
Esempio n. 9
0
def view_training_statistics(training_id: str):
    """
    Route to show page with statistics of a training by its identifier

    :param training_id: Training identifier
    :return: Page with statistics of the training with the given identifier, or
        a dictionary with an explanation and 404 HTTP return code if something went wrong, or
        an empty dictionary with 404 HTTP return code if the file was not found or access was denied.
    """
    if not check_access({'_id': ObjectId(training_id)}):
        return {}, 404
    training_statistics, training_statistics_status_code = get_training_statistics(training_id)
    if training_statistics.get('message') != 'OK':
        return training_statistics, training_statistics_status_code
    criteria_pack_db = CriterionPackDBManager().get_criterion_pack_by_name(training_statistics['criteria_pack_id'])
    feedback = training_statistics['feedback']
    feedback_evaluator_id = training_statistics['feedback_evaluator_id']
    feedback_evaluator = FeedbackEvaluatorFactory().get_feedback_evaluator(feedback_evaluator_id)(criteria_pack_db.criterion_weights)
    criteria_results = feedback.get('criteria_results', {})
    if 'score' in feedback:
        feedback_str = '{} = {}'.format(t("Оценка за тренировку"),'{:.2f}'.format(feedback.get('score')))
        results_as_sum_str = feedback_evaluator.get_result_as_sum_str(criteria_results)
        if results_as_sum_str:
            feedback_str += ' = {}'.format(results_as_sum_str)
    else:
        feedback_str = t("Тренировка обрабатывается. Обновите страницу.")
    if criteria_results is not None:
        criteria_results_str = '\n'.join('{} = {}{}'.format(
            name,
            '{:.2f}'.format(result.get('result')),
            '' if not result.get('verdict', '')  else (', ' + result.get('verdict')),
        ) for (name, result) in criteria_results.items())
    else:
        criteria_results_str = ''
    criteria_results_str = '<br>'.join((criteria_results_str.replace('\n', '<br>'), criteria_pack_db.feedback))
    if 'verdict' in feedback:
        verdict_str = feedback.get('verdict').replace('\n', '\\n')
    else:
        verdict_str = ''
    training_status = training_statistics['training_status']
    training_status_str = TrainingStatus.russian.get(training_status, '')
    if training_status_str:
        training_status_str = '{}: {}'.format(t("Статус"), t(training_status_str))
    audio_status = training_statistics['audio_status']
    audio_status_str = AudioStatus.russian.get(audio_status, '')
    if audio_status_str:
        audio_status_str = '{}: {}'.format(t("Статус"), t(audio_status_str))
    presentation_status = training_statistics['presentation_status']
    presentation_status_str = PresentationStatus.russian.get(presentation_status, '')
    if presentation_status_str:
        presentation_status_str = '{}: {}'.format(t("Статус"), t(presentation_status_str))
    remaining_processing_time_estimation = training_statistics['remaining_processing_time_estimation']
    if remaining_processing_time_estimation and remaining_processing_time_estimation > 0:
        remaining_processing_time_estimation_str = '{}: {} с.'.format(t("Ожидаемое время обработки"),
            time.strftime("%M:%S", time.gmtime(remaining_processing_time_estimation)),
        )
    else:
        remaining_processing_time_estimation_str = ''
    return render_template(
        'training/statistics.html',
        title='{}: {}'.format(t("Статистика тренировки с ID"), training_id),
        training_id=training_id,
        presentation_file_id=training_statistics['presentation_file_id'],
        presentation_file_name=training_statistics['presentation_file_name'],
        presentation_record_file_id=training_statistics['presentation_record_file_id'],
        feedback=feedback_str,
        verdict=verdict_str,
        training_status=training_status_str,
        audio_status=audio_status_str,
        presentation_status=presentation_status_str,
        remaining_processing_time_estimation=remaining_processing_time_estimation_str,
        criteria_results=criteria_results_str.replace('\n', '\\n').replace('\'', '').replace('"', ''),
        slides_time=training_statistics['slides_time'],
    ), 200