class StudentStageTransition(db.Model):

    __tablename__ = 'stage_transitions'

    id = db.Column(db.Integer, primary_key=True)
    from_stage = db.Column(db.String(100), nullable=False)
    to_stage = db.Column(db.String(100), nullable=False)
    notes = db.Column(db.String(1000))
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    student_id = db.Column(db.Integer, db.ForeignKey('students.id'))
    student = db.relationship("Student")
class StudentContact(db.Model):

    __tablename__ = 'student_contacts'

    id = db.Column(db.Integer, primary_key=True)
    contact = db.Column(db.String(10))
    main_contact = db.Column(db.Boolean, default=False)
    student_id = db.Column(db.Integer, db.ForeignKey('students.id'))
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    incoming_calls = db.relationship('IncomingCalls',
                                     backref='contact',
                                     cascade='all, delete-orphan',
                                     lazy='dynamic')
    outgoing_calls = db.relationship('OutgoingCalls',
                                     backref='contact',
                                     cascade='all, delete-orphan',
                                     lazy='dynamic')
    outgoing_messages = db.relationship('OutgoingSMS',
                                        backref='contact',
                                        cascade='all, delete-orphan',
                                        lazy='dynamic')

    def send_sms(self, message, sms_type):
        """
            For sending the message to number associtated with this instance using exotel api.

            Params:
                `message` : Contains the message that need to be sent str required
                `sms_type` : Sending SMS Type

            Usage: student_contact.send_sms(message)

        """
        exotel.sms(app.config.get("EXOTEL_SMS_NUM"), self.contact, message)

        # recording the outgoing sms in chanaya
        outgoing_sms = OutgoingSMS(contact_id=self.id,
                                   type=sms_type,
                                   text=message)
        db.session.add(outgoing_sms)
        db.session.commit()

    @staticmethod
    def create(contact, student_id, main_contact=False):
        """
            Function is used for creating a new student_contact record for the student_id.

            Params:
                `contact` : '7896121314' Student mobile number
                `student_id`: '21' Student id
                `main_contact`: True(default is False) True if we can call on this number to connect with the student else False.
        """

        student_contact = StudentContact(contact=contact,
                                         student_id=student_id,
                                         main_contact=main_contact)
        db.session.add(student_contact)
        db.session.commit()
class OutgoingSMS(db.Model):

    __tablename__ = 'outgoing_sms'

    id = db.Column(db.Integer, primary_key=True)
    contact_id = db.Column(db.Integer, db.ForeignKey('student_contacts.id'))
    type = db.Column(db.Enum(app.config['OUTGOING_SMS_TYPE']), nullable=False)
    text = db.Column(db.String(1000), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
Exemplo n.º 4
0
class QuestionAttempts(db.Model):

    __tablename__ = 'attempts'

    id = db.Column(db.Integer, primary_key=True)
    enrolment_key_id = db.Column(db.Integer,
                                 db.ForeignKey('enrolment_keys.id'),
                                 nullable=False)
    question_id = db.Column(db.Integer,
                            db.ForeignKey('questions.id'),
                            nullable=False)
    question = db.relationship('Questions')
    selected_option_id = db.Column(
        db.Integer, db.ForeignKey('question_options.id'))  #to store mcq answer
    answer = db.Column(db.String(10))  #to store integer value
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    @staticmethod
    def create_attempts(questions_attempts, enrollment):
        """
            Create the answer attempt made for each enrollment key generated.

            Params:
                    `enrollment` = EnrolmentKey instance,
                    `question_attempted`: [
                        {
                            'question_id' : 23,
                            'selected_option_id' : 19,
                        },
                        {
                            'question_id' : 23,
                            'answer': '216'
                        }
                    ]
        """
        # recording the answer
        for question_attempt in questions_attempts:
            # each attempts for test are for the single enrollment key
            question_attempt['enrolment_key_id'] = enrollment.id
            if not question_attempt.get('selected_option_id'):
                question_attempt['selected_option_id'] = None
            attempt = QuestionAttempts(**question_attempt)
            db.session.add(attempt)
        db.session.commit()
class Student(db.Model):

    __tablename__ = 'students'

    id = db.Column(db.Integer, primary_key=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    # personal details fields
    stage = db.Column(db.String(100), nullable=False)
    name = db.Column(db.String(200))
    gender = db.Column(db.Enum(app.config['GENDER']))
    dob = db.Column(db.Date)

    #extra detail fields
    caste = db.Column(db.Enum(app.config['CASTE']))
    religion = db.Column(db.Enum(app.config['RELIGION']))
    monthly_family_income = db.Column(db.Integer)
    total_family_member = db.Column(db.Integer)
    family_member_income_detail = db.Column(db.Text)

    contacts = db.relationship('StudentContact', backref='student', cascade='all, delete-orphan', lazy='dynamic')
    enrollment_keys = db.relationship('EnrolmentKey', backref='student', cascade='all, delete-orphan', lazy='dynamic')

    @staticmethod
    def create(stage, **kwargs):
        """
            This function create the student object with list of contact or single contact or the
            main_contact where we have to call them
            it requires **kwargs

            Params:
                `mobile` : str  (the number from which the person called on the helpline)
                `contact_list`: list of str (list of all the number of the student)
                `main_contact` : str (the number to which we can connect with the student)

            USAGE:
                Student.create(stage, **kwargs)

            Returns:    student object.
                        student_contact object if the called was from the helpline.

        """
        mobile=kwargs.get('mobile', None)
        contacts_list=kwargs.get('contacts_list', None)
        main_contact=kwargs.get('main_contact', None)

        student = Student(stage=app.config['STAGES'][stage])

        db.session.add(student)
        db.session.commit()


        # adding the number to which we can connect to the student
        if main_contact:
            student_contact = StudentContact(contact=mobile, main_contact=True, student_id=student.id)
            db.session.add(student_contact)
            db.session.commit()

        # adding the list of the contact
        if contacts_list:
            for contact in main_contacts:
                contact = StudentContact(contact=mobile, student_id=student.id)
                db.session.add(contact)
                db.session.commit()
        # if the call is from helpline create a record in student contact
        if mobile:
            call_from_number = StudentContact(contact=mobile, student_id=student.id)
            db.session.add(call_from_number)
            db.session.commit()

            # return the student object and number called at helpline
            return student, call_from_number

        # if there was no called on helpline just send the student object
        return student


    @staticmethod
    def offline_student_record(stage, student_data, main_contact, alternative_contact, set):
        """
            Function helps to add student data who have given the test offline.
            it creates a student instance and then update it's data with contact infomation
            and create a enrollment key for the student.

            Params:
                `stage`: 'PRIVILEGE AND VERIFICATION CALL',
                `student_data` : {
                    contains the data which need to be added to the Student table
                    'name':'Amar Kumar Sinha',
                    'dob': datetime(1997, 9, 18)
                    'gender': 'MALE',
                    'religion': 'Hindu'
                }

                `main_contact` : The number on which we can contact the student
                `alternative_contact` : Alternative contact to which we can call to reach student
                `set` : A QuestionSet intance for the student.
        """

        student, call_from = Student.create(stage, main_contact=main_contact, mobile=alternative_contact)

        student.update_data(student_data)
        student_id = student.id

        enrollment = EnrolmentKey.generate_key(student_id)
        enrollment.question_set_id = set.id

        enrollment.start_test()
        enrollment.end_test()

        return student, enrollment


    @staticmethod
    def generate_enrolment_key(mobile, from_helpline):
        """
            This function helps to create a a record of a new student in
            the database were the from_helpine varibale helps to track was
            the call from helpline or was manually created and send the enrollment
            key to the mobile number

            Note: if the key was generated from_helpline record also record the incoming calls.

            USAGE :
                Student.generate_enrolment_key(mobile, from_helpline)

            Params:
                `mobile` : String required
                `from_helpline` : Boolean required

        """

        # create a new student record in the platform with this mobile number
        student, call_from_number = Student.create(stage='EKG', mobile=mobile)
         # recording the api call if it was from the helpine
        if from_helpline:
            IncomingCalls.create(student_contact=call_from_number,call_type=app.config['INCOMING_CALL_TYPE'].ekg)
        # sending sms to each and everynumber of the student
        message = student.send_enrolment_key(from_helpline)

        return message

    def update_data(self, student_data, mobiles=[]):
        """
        Update the student's data.

        Params:
        `student_data`: Should contain the fields of student instance which needs to be updated
                        in dictionary format.
                        for example: {'name': 'Amar Kumar Sinha', 'gender': gender.male #enum}
        `mobiles`: List of strings with student mobile numbers to be updated. Example:
                    ['8130378953', '8130378965']. If they exist as contacts associated with the
                    given student no new contacts will be created, otherwise new ones will be
                    created.
        """

        fields = ('stage','name','dob','monthly_family_income','total_family_member','family_member_income_detail')
        enum_fields = {
            'caste':'CASTE',
            'religion':'RELIGION',
            'gender':'GENDER'
        }
        # update the attributes given by the `student_data` dict
        for key, value in student_data.items():
            if key in fields or key in enum_fields.keys():
                # converting the value for enums type fields
                if key in enum_fields.keys():
                    enum_class_name = enum_fields[key]
                    enum_value = student_data[key]
                    value = app.config[enum_class_name](enum_value)

                # adding new value to the attributes
                setattr(self, key, value)

        db.session.add(self)

        # get all the associated student contacts
        contacts = StudentContact.query.filter_by(student_id=self.id).all()
        contacts = [contact.contact for contact in contacts]
        for mobile in mobiles:
            # if the given mobile doesn't exist as one of the student contacts
            if not mobile in contacts:
                contact = StudentContact(contact=mobile, student_id=self.id)
                db.session.add(contact)

        db.session.commit()



    def send_enrolment_key(self, from_helpline):
        """
            Method is used to send valid enrollment key to the student if exist
            else it will generate a new enrollment key and send it to the user.

            USAGE:
                instance.send_enrollment_key(from_helpline)

            Params:
                `from_helpline` : Boolean required

            Return a dictionary which is structured below
                {
                    'generate': False, # True if Generate a new enrollment key else False
                    'sent':True, # True if the new Generated Key is been sent to all the Phone Number
                    'enrollment_key': 'ASD456' # Newly Generated Enrollment Key
                }
        """

        student_id = self.id

        # getting the latest enrollment key from the database
        enrollment = EnrolmentKey.query.filter_by(student_id=student_id).order_by(EnrolmentKey.created_at.desc()).first()

        #no enrollment key exist then create a new key and send it in the case of rqc and intrested
        if not enrollment:
            enrollment = EnrolmentKey.generate_key(student_id)
            #send the enrollment key message to the student contact
            message = {
                'generate':True,
                'sent':True,
                'enrollment_key':enrollment.key
            }
        #check if the enrollment key is valid or not
        elif enrollment.is_valid():
            message = {
                'generate': False,
                'sent': True,
                'enrollment_key':enrollment.key
            }
        # if the key is not valid then generating a new valid key for the student
        else:
            enrollment = EnrolmentKey.generate_key(student_id)
            message = {
                'generate': True,
                'sent': True,
                'enrollment_key':enrollment.key
            }

        print(enrollment.key)

        # getting the test that we have to send the student
        enrollment_message = app.config.get("TEST_ENROLL_MSG").format(test_url=enrollment.key)

        sms_type=app.config['OUTGOING_SMS_TYPE'].enrolment_key_gen

        self.send_sms_to_all_numbers(enrollment_message, sms_type)

        return message

    def send_sms_to_all_numbers(self, message, sms_type):
        """
            Helps to send sms to all the numbers attached to the student record.
            Params
                `message` : Contains the message that needs to be sent to the student.
                `sms_type` : Type of the sms defined in enums OUTGOING_SMS_TYPE in config.
        """

        contacts =  self.contacts.all()
        for contact in contacts:
            contact.send_sms(message, sms_type)

    def change_stage(self, to_stage, notes=None):
        """
            Helps to keep track of student stages in which it has been changed and
            also send sms when reached to specific stages defined in OUTGOING_SMS
            in config.

            Params:
                `to_stage`: New student stage.
                `notes`: (default is None) Notes for the stage change.
        """

        new_stage = app.config['STAGES'][to_stage]
        student_stage_transition = StudentStageTransition(from_stage=self.stage, to_stage=new_stage, student=self, notes=notes)
        self.stage = new_stage
        db.session.add(student_stage_transition)
        db.session.add(self)

        # Sending the messages for some specific stages
        if to_stage in app.config['OUTGOING_SMS'].keys():
            outgoing_sms = app.config['OUTGOING_SMS'][to_stage]

            message = outgoing_sms['message']
            sms_type = app.config['OUTGOING_SMS_TYPE'](outgoing_sms['sms_type'])

            self.send_sms_to_all_numbers(message, sms_type)

        db.session.commit()
Exemplo n.º 6
0
class EnrolmentKey(db.Model):

    __tablename__ = 'enrolment_keys'

    id = db.Column(db.Integer, primary_key=True)
    key = db.Column(db.String(6), unique=True)
    test_start_time = db.Column(db.DateTime, nullable=True)
    test_end_time = db.Column(db.DateTime, nullable=True)
    student_id = db.Column(db.Integer, db.ForeignKey('students.id'))
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    question_set_id = db.Column(db.Integer, db.ForeignKey('sets.id'))
    score = db.Column(db.Integer, nullable=True)

    attempts = db.relationship('QuestionAttempts',
                               backref='enrollment',
                               cascade='all, delete-orphan',
                               lazy='dynamic')

    question_set = db.relationship("QuestionSet",
                                   back_populates="enrolment_key")

    @staticmethod
    def generate_key(student_id):
        """
            The method helps to generate a unique enrollment key for the student_id provided
            and add it to student record.
            Params:
                `student_id`: int
            Return enrollment (object associated to the student_id)
        """
        # generating a new enrollment key
        ALPHABETS, NUMBERS = string.ascii_uppercase, string.digits
        enrollment_key = "".join([
            random.choice(ALPHABETS) for x in range(3)
        ]) + "".join([random.choice(NUMBERS) for x in range(3)])
        # checking if the enrollment key already exist or not
        while EnrolmentKey.query.filter_by(key=enrollment_key).first():
            enrollment_key = "".join([
                random.choice(ALPHABETS) for x in range(3)
            ]) + "".join([random.choice(NUMBERS) for x in range(3)])
        # record the  enrollment key in the database
        enrollment = EnrolmentKey(student_id=student_id, key=enrollment_key)
        db.session.add(enrollment)
        db.session.commit()

        return enrollment

    def calculate_test_score(self):
        """
            Test Score Calulcation.

            The method calculate marks for all the questions attempted by each enrollment key(Student).
            The marks realted to each question are in CONFIG file based on question difficulty level.

            Update the marks to the score column in the enrollment.
        """

        attempts = self.attempts.all()
        # get marks config
        marks_config = app.config['QUESTION_CONFIG']['marks_config']

        score = 0
        # iterate over each question
        for attempt in attempts:
            question = attempt.question
            is_correct = False
            # for mcq check if the id which is select is correct = True or not
            if question.type.value == 'MCQ':
                selected_option_id = attempt.selected_option_id
                # for integer_answer strip both attempt answer and answer in the db and check if both are equal or not
                option = QuestionOptions.query.get(selected_option_id)
                if option.correct:
                    is_correct = True
            else:
                option = question.options.first()
                correct_answer = option.en_text.strip()
                student_answer = attempt.answer.strip()

                if correct_answer == student_answer:
                    is_correct = True

            # if the flag is true include the score in the score variable
            if is_correct:
                question_difficulty = question.difficulty.value
                mark = marks_config[question_difficulty]
                score += mark
        print(score)

        self.score = score
        db.session.add(self)
        db.session.commit()

        if self.score < app.config['MINIMUM_PASSING_SCORE']:
            outgoing_sms = app.config['OUTGOING_SMS']['ETF']
        else:
            outgoing_sms = app.config['OUTGOING_SMS']['ETP']

        message = outgoing_sms['message']
        sms_type = app.config['OUTGOING_SMS_TYPE'](outgoing_sms['sms_type'])
        student = self.student

        student.send_sms_to_all_numbers(message, sms_type)

    def get_question_set(self):
        """
            Generate a question set corresponding to this enrolment key and return
            the question set instanceself.

            Note: Also runs the start_test method on the instance.
        """
        self.start_test()
        question_set, questions = QuestionSet.create_new_set()
        self.question_set_id = question_set.id
        db.session.add(self)
        db.session.commit()
        return question_set, questions

    def start_test(self):
        """
            Marks the `test_start_time` parameter of the enrolment key.
            Also marks the `test_end_time` as `test_start_time` + the time allowed for the test.
        """
        current_datetime = datetime.now()
        self.test_start_time = current_datetime

    def end_test(self):
        """
            This records the end time of the test in `test_end_time` attribute.
        """
        self.test_end_time = datetime.now()
        db.session.add(self)
        db.session.commit()

    def is_valid(self):
        """
            The method checks if the key is been used or not
            if it is not used then the key is valid

            No params
            Usage:
                enrollment.is_valid()
            Return Boolean value
        """
        current_datetime = datetime.now()
        # if the test hasn't started of the key it is valid
        if not self.test_start_time:
            return True
        expired_datetime = self.test_start_time + timedelta(
            seconds=app.config['TEST_DURATION'])
        # if the test has end for the enrollment key then it is not valid
        if expired_datetime and expired_datetime <= current_datetime:
            return False
            #if the test is ongoing then it is valid
        elif expired_datetime > current_datetime:
            return True

    def is_test_ended(self):
        """
            Checks if the enrollment mkey has been used to give the test or not.
            No Params
            Usage:
                enrollment.is_test_ended()
            Returns True if the test has ended else False.
        """
        if self.test_end_time:
            return True
        return False

    def in_use(self):
        """
            Check if the enrollment key is already in used or not.
            No Params
            Usage:
                enrollment.in_use()
            Return Boolean(True when it is being used else False)

        """
        current_datetime = datetime.now()

        # if it is not used
        if not self.test_start_time:
            return False
        expired_datetime = self.test_start_time + timedelta(
            seconds=app.config['TEST_DURATION'])
        # on being used
        if expired_datetime > current_datetime:
            return True

        return False
Exemplo n.º 7
0
class QuestionSet(db.Model):

    __tablename__ = 'sets'

    id = db.Column(db.Integer, primary_key=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    partner_name = db.Column(db.String(200))
    question_pdf_url = db.Column(db.String(200))
    answer_pdf_url = db.Column(db.String(200))
    set_name = db.Column(db.String(5))
    _question_ids = db.Column(db.String(200))

    enrolment_key = db.relationship("EnrolmentKey",
                                    back_populates="question_set")

    def get_question_ids(self):
        return [int(i) for i in self._question_ids.split(',')]

    def set_question_ids(self, ids):
        self._question_ids = ','.join([str(i) for i in ids])

    def get_questions(self):
        """
            Get a list of all the questions associated with this question set.
            Returns a list of instances of Questions.
        """
        # get the related questions
        ids = self.get_question_ids()
        questions = Questions.query.filter(Questions.id.in_(ids)).all()
        # order the questions as the order of the ids list
        # the _in caluse f***s up the order
        questions_map = {q.id: q for q in questions}
        questions = [questions_map[id] for id in ids]
        return questions

    @staticmethod
    def _generate_random_question_paper():
        """
            Generates a list of 18 questions as per the requirements of the config file.

            Returns a list of question instances.
        """
        questions = Questions.query.all()

        # arrange the question according to the topic and difficulty
        topics = app.config['QUESTION_CONFIG']['topic']
        questions_dict = {}
        for question in questions:
            topic = question.topic.value
            difficulty = question.difficulty.value
            if topic in topics.keys():
                if not questions_dict.get(topic):
                    questions_dict[topic] = {}
                    questions_dict[topic][difficulty] = []
                elif not questions_dict[topic].get(difficulty):
                    questions_dict[topic][difficulty] = []
                questions_dict[topic][difficulty].append(question)

        # Select the question randomly according to topic and difficulty
        # Number of questions to be selected are defined in the CONFIG file.
        main_questions_list = []
        for topic in topics:
            for difficulty in topics[topic]:
                question_topic = questions_dict.get(topic)
                if not question_topic:
                    continue
                question_list = question_topic.get(difficulty)
                if not question_list:
                    continue
                # shuffle the list in place
                random.shuffle(question_list)
                # figure out how many questions of that topic & difficulty level are required
                required_question_num = topics[topic][difficulty]
                # pick the number of questions required as per the config file
                main_questions_list += question_list[:required_question_num]

        # finally shuffle the list again while returning
        random.shuffle(main_questions_list)
        return main_questions_list

    @staticmethod
    def create_new_set(partner_name=None, set_name=None):
        """
            Generates a new set of questions as per the logic defined in the config file.
            The config file mentions how many questions of which topic and difficulty level
            need to be included in a test.

            Params:
                `partner_name`: the partner for whom this is being generated
                            (defaults to None when the set is created for a student)


            Return:
                `questiom_set`: QuestionSet instance.
                `questions`:  [ <Questions 1>,<Questions 18>,<Questions 21>,<Questions 11>,<Questions 13>,<Questions 51>]
                              Total 18 questions in each set.
        """
        questions = QuestionSet._generate_random_question_paper()
        ids = [question.id for question in questions]

        # Create a new question set
        question_set = QuestionSet(partner_name=partner_name,
                                   set_name=set_name)
        question_set.set_question_ids(ids)
        db.session.add(question_set)
        db.session.commit()

        return question_set, questions