Ejemplo n.º 1
0
    def get(self, *args, **kwargs):
        parser = reqparse.RequestParser()
        parser.add_argument('batch_id', type=int)
        parser.add_argument('batch_type', type=str, choices=app.config['BATCH_TYPE'].keys())
        parser.add_argument('target_year', type=int)
        parser.add_argument('target_exam', type=str, choices=app.config['TARGET_EXAMS'].keys())
        parser.add_argument('branches', type=comma_separated_ints_type)
        parser.add_argument('query', type=str)
        parser.add_argument('offset', type=int, default=0)
        parser.add_argument('limit', type=int, default=20)
        args = parser.parse_args()
        
        if args['branches'] is not None:
            args['branches'] = map(str, args['branches'])

        if args['batch_id'] is not None:
            batch = Batch.get(args['batch_id'])
            if args['batch_type'] is not None and batch.type != args['batch_type']:
                return {'students': []}
            if args['target_year'] is not None and batch.target_year != args['target_year']:
                return {'students': []}
            if args['target_exam'] is not None and batch.target_exam != args['target_exam']:
                return {'students': []}
            student_ids = [sb.student_id for sb in StudentBatches.query.filter(StudentBatches.batch_id == batch.id,
                                                                               StudentBatches.left_at == None).all()]
        else:
            batches = {b.id: b for b in Batch.get_filtered(type=args['batch_type'], target_year=args['target_year'],
                                                           target_exam=args['target_exam'], institute_id=kwargs['user'].id, branches=args['branches'])}
            student_ids = list({sb.student_id for sb in StudentBatches.query.filter(StudentBatches.batch_id.in_(batches.keys()),
                                                                               StudentBatches.left_at == None).all()})

        # for pagination
        if args['query'] is not None:
            students = Student.query.filter(Student.id.in_(student_ids), Student.name.ilike('%' + args['query'] + '%')).all()
            total = len(students)
            student_ids = [s.id for s in students][args['offset']:args['offset']+args['limit']]
        else:
            total = len(student_ids)
            student_ids = sorted(student_ids)[args['offset']:args['offset']+args['limit']]
            students = Student.query.filter(Student.id.in_(student_ids)).all()
        student_batches = {}
        # get all students whose id is present `student_ids` and have not left the batch and all their batches too
        for sb in StudentBatches.query.filter(StudentBatches.student_id.in_(student_ids), StudentBatches.left_at == None).all():
            if sb.student_id not in student_batches:
                student_batches[sb.student_id] = [sb.batch_id]
            else:
                student_batches[sb.student_id].append(sb.batch_id)

        # need to make one more batch query because i need all the batches that any student in the result set is part of
        batches = {b.id: b for b in Batch.get_filtered(include_ids=list(itertools.chain(*student_batches.values())))}
        for student in students:
            student.batches = [{'id': b_id, 'name': batches[b_id].name} for b_id in student_batches.get(student.id, [])]
        return {'students': students, 'total': total}
Ejemplo n.º 2
0
    def post(self, *args, **kwargs):
        parser = reqparse.RequestParser()
        parser.add_argument('batch_ids',
                            type=comma_separated_ints_type,
                            required=True)
        args = parser.parse_args()
        mock_test = MockTest.query.get(kwargs['id'])
        if mock_test is None:
            raise InvalidMockTestId
        batches = {
            b.id: b
            for b in Batch.get_filtered(institute_id=kwargs['user'].id)
        }
        new_batch_ids = args['batch_ids'][:]
        for p in PushedMockTest.query.filter(
                PushedMockTest.mock_test_id == mock_test.id,
                PushedMockTest.batch_id.in_(batches.keys())).all():
            if p.batch_id in args['batch_ids']:
                new_batch_ids.remove(p.batch_id)

        for batch_id in new_batch_ids:
            p = PushedMockTest(mock_test_id=mock_test.id,
                               batch_id=batch_id,
                               pushed_at=datetime.datetime.utcnow())
            db.session.add(p)
        db.session.commit()
        return {'error': False}
    def get(self, *args, **kwargs):
        parser = reqparse.RequestParser()
        parser.add_argument('difficulty', type=str, choices=app.config['MOCK_TEST_DIFFICULTY_LEVEL'].keys())
        parser.add_argument('batches_pushed_to', type=comma_separated_ints_type)
        parser.add_argument('offset', type=int, default=0)
        parser.add_argument('limit', type=int, default=20)
        args = parser.parse_args()
        total = 0
        if args['batches_pushed_to'] is not None:
            pushed_mock_test_ids = {}
            for p in PushedMockTest.query.filter(PushedMockTest.batch_id.in_(args['batches_pushed_to'])).all():
                if p.mock_test_id not in pushed_mock_test_ids:
                    pushed_mock_test_ids[p.mock_test_id] = [p.batch_id]
                else:
                    pushed_mock_test_ids[p.mock_test_id].append(p.batch_id)
            exprs = []
            exprs.append(MockTest.id.in_(pushed_mock_test_ids.keys()))
            if args['difficulty'] is not None:
                exprs.append(MockTest.difficulty == args['difficulty'])
            mock_tests = MockTest.query.filter(*exprs).offset(args['offset']).limit(args['limit'])
            total = MockTest.query.filter(*exprs).count()
            batches = {b.id: b for b in Batch.get_filtered(include_ids=args['batches_pushed_to'])}
        else:
            exprs = []
            exprs.append(MockTest.for_institutes == True)
            exprs.append(MockTest.is_locked == True)
            if args['difficulty'] is not None:
                exprs.append(MockTest.difficulty == args['difficulty'])
            mock_tests = MockTest.query.filter(*exprs).offset(args['offset']).limit(args['limit'])
            batches = {b.id: b for b in Batch.get_filtered(institute_id=kwargs['user'].id)}
            total = MockTest.query.filter(*exprs).count()
            pushed_mock_test_ids = {}
            for p in PushedMockTest.query.filter(PushedMockTest.mock_test_id.in_([m.id for m in mock_tests]),
                                                 PushedMockTest.batch_id.in_(batches.keys())).all():
                if p.mock_test_id not in pushed_mock_test_ids:
                    pushed_mock_test_ids[p.mock_test_id] = [p.batch_id]
                else:
                    pushed_mock_test_ids[p.mock_test_id].append(p.batch_id)
        res = []
        for mock_test in mock_tests:
            mock_test.batches_pushed_to = [{'id': b_id, 'name': batches[b_id].name, 'class': batches[b_id].clazz} for
                                           b_id in pushed_mock_test_ids.get(mock_test.id, [])]
            res.append(mock_test)

        return {'mock_tests': res, 'total': total}
Ejemplo n.º 4
0
 def delete(self, *args, **kwargs):
     sbs = StudentBatches.query.filter(
         StudentBatches.batch_id == kwargs['id'],
         StudentBatches.left_at == None).count()
     if sbs > 0:
         raise BatchNotEmpty
     batch = BatchModel.get(kwargs['id'])
     batch.status = 0
     db.session.commit()
     return {'error': False}
    def get(self, *args, **kwargs):
        parser = reqparse.RequestParser()
        parser.add_argument('profile', type=int, choices=[0, 1], default=0)
        args = parser.parse_args()
        student = Student.get(kwargs['id'])
        batch_ids = [
            sb.batch_id for sb in StudentBatches.query.filter(
                StudentBatches.student_id == student.id, StudentBatches.left_at
                == None).all()
        ]
        batches = Batch.get_filtered(include_ids=batch_ids,
                                     institute_id=kwargs['user'].id)
        student.batches = [{'id': b.id, 'name': b.name} for b in batches]
        if args['profile'] == 1:
            pushed_mock_test_ids = {
                p.id: p.mock_test_id
                for p in PushedMockTest.query.filter(
                    PushedMockTest.batch_id.in_([b.id
                                                 for b in batches])).all()
            }
            mock_tests = {
                m.id: m
                for m in MockTest.query.filter(
                    MockTest.id.in_(pushed_mock_test_ids.values()))
            }
            amts = AttemptedMockTest.query.filter(
                AttemptedMockTest.student_id == student.id,
                AttemptedMockTest.pushed_mock_test_id.in_(
                    pushed_mock_test_ids.keys())).all()
            attempted_mock_test_ids = {amt.mock_test_id for amt in amts}
            student.mock_tests = []
            seen_mock_test_ids = set()
            for pushed_id, mock_test_id in pushed_mock_test_ids.items():
                mt = {
                    'id': mock_test_id,
                    'name': mock_tests[mock_test_id].name,
                    'attempted': False
                }
                for amt in amts:
                    if pushed_id == amt.pushed_mock_test_id:
                        mt['attempted'] = True
                        break

                # if current mock test is not attempted
                if mt['attempted'] is False:
                    # a similar attempted mock test exists then dont push this entry into the result
                    if mock_test_id in attempted_mock_test_ids:
                        continue
                    # if similar mock test has already been pushed into the result
                    if mock_test_id in seen_mock_test_ids:
                        continue
                student.mock_tests.append(mt)
                seen_mock_test_ids.add(mock_test_id)
        return {'student': student}
Ejemplo n.º 6
0
    def post(self, *args, **kwargs):
        parser = reqparse.RequestParser()
        parser.add_argument('name', type=str, required=True)
        parser.add_argument('email', type=str, required=True)
        parser.add_argument('password', type=str, required=True)
        parser.add_argument('mobile_no', type=str, required=True)
        parser.add_argument('city', type=str)
        parser.add_argument('area', type=str)
        parser.add_argument('pin', type=str)
        parser.add_argument('school', type=str)
        parser.add_argument('ntse_score', type=float)
        parser.add_argument('roll_no', type=str, required=True)
        parser.add_argument('father_name', type=str)
        parser.add_argument('father_mobile_no', type=str)
        parser.add_argument('father_email', type=str)
        parser.add_argument('batch_ids', type=comma_separated_ints_type, required=True)
        args = parser.parse_args()
        student = Student.create(name=args['name'], email=args['email'], password=md5(args['password']).hexdigest(),
                                 mobile_no=args['mobile_no'], city=args['city'], area=args['area'], pin=args['pin'],
                                 school=args['school'], ntse_score=args['ntse_score'], roll_no=args['roll_no'],
                                 father_name=args['father_name'], father_mobile_no=args['father_mobile_no'],
                                 father_email=args['father_email'], registered_from='institute')
        target_exams = set()
        target_year = None
        batches = Batch.get_filtered(include_ids=args['batch_ids'])

        engineering_exams = {'1', '2', '3'}
        medical_exams = {'4', '5'}
        for batch in batches:
            if batch.target_exam in engineering_exams:
                target_exams.update(engineering_exams)
            if batch.target_exam in medical_exams:
                target_exams.update(medical_exams)
            if target_year is None or target_year > batch.target_year:
                target_year = batch.target_year
            sb = StudentBatches(batch_id=batch.id, student_id=student.id, joined_at=datetime.datetime.utcnow())
            db.session.add(sb)

        branches = []
        if len(target_exams.intersection(engineering_exams)) > 0:
            branches.append('1')
        if len(target_exams.intersection(medical_exams)) > 0:
            branches.append('2')

        student.target_year = target_year
        student.target_exams = list(target_exams)
        student.branches = branches
        db.session.commit()
        student.batches = [{'id': b.id, 'name': b.name} for b in batches]
        return {'student': student}
Ejemplo n.º 7
0
    def put(self, *args, **kwargs):
        parser = reqparse.RequestParser()
        parser.add_argument('on_weekdays',
                            type=int,
                            required=True,
                            choices=[0, 1])
        parser.add_argument('on_weekends',
                            type=int,
                            required=True,
                            choices=[0, 1])
        parser.add_argument('clazz', type=str, choices=['11', '12'])
        parser.add_argument('target_year', type=int, required=True)
        parser.add_argument('target_exam',
                            type=str,
                            required=True,
                            choices=app.config['TARGET_EXAMS'].keys())
        parser.add_argument('type',
                            type=str,
                            required=True,
                            choices=app.config['BATCH_TYPE'].keys())
        parser.add_argument('other', type=str)
        parser.add_argument('batch_timings', type=BatchList.batch_timings)
        parser.add_argument('status', type=int)
        args = parser.parse_args()

        batch = BatchModel.get(kwargs['id'])
        if args['status'] is not None and args['status'] == 1:
            batch.status = 1
        else:
            batch.on_weekdays = bool(args['on_weekdays'])
            batch.on_weekends = bool(args['on_weekends'])
            if args['clazz'] is not None:
                batch.clazz = args['clazz']
            batch.target_year = args['target_year']
            batch.target_exam = args['target_exam']
            batch.type = args['type']
            if args['other'] is not None:
                batch.other = args['other']
            if args['batch_timings'] is not None:
                batch.batch_timings = args['batch_timings']

        db.session.commit()

        return {'batch': batch}
Ejemplo n.º 8
0
 def get(self, *args, **kwargs):
     mock_test = MockTest.query.get(kwargs['id'])
     if mock_test is None:
         raise InvalidMockTestId
     pushed_batch_ids = [
         p.batch_id for p in PushedMockTest.query.filter(
             PushedMockTest.mock_test_id == mock_test.id).all()
     ]
     batches = Batch.get_filtered(include_ids=pushed_batch_ids,
                                  institute_id=kwargs['user'].id)
     mock_test.batches_pushed_to = [{
         'id': b.id,
         'name': b.name,
         'class': b.clazz
     } for b in batches]
     question_ids = []
     for sid, data in json.loads(mock_test.question_ids).items():
         question_ids.extend(data['q_ids'])
     questions = Question.get_filtertered_list(
         include_question_ids=question_ids)['questions']
     return {'mock_test': mock_test, 'questions': questions}
Ejemplo n.º 9
0
    def post(self, *args, **kwargs):
        parser = reqparse.RequestParser()
        parser.add_argument('name', type=str, required=True)
        parser.add_argument('on_weekdays',
                            type=int,
                            required=True,
                            choices=[0, 1])
        parser.add_argument('on_weekends',
                            type=int,
                            required=True,
                            choices=[0, 1])
        parser.add_argument('clazz', type=str, choices=['11', '12'])
        parser.add_argument('target_year', type=int, required=True)
        parser.add_argument('target_exam',
                            type=str,
                            required=True,
                            choices=app.config['TARGET_EXAMS'].keys())
        parser.add_argument('type',
                            type=str,
                            required=True,
                            choices=app.config['BATCH_TYPE'].keys())
        parser.add_argument('other', type=str)
        parser.add_argument('batch_timings', type=self.__class__.batch_timings)
        args = parser.parse_args()

        batch = Batch.create(name=args['name'],
                             on_weekdays=bool(args['on_weekdays']),
                             on_weekends=bool(args['on_weekends']),
                             clazz=args['clazz'],
                             target_year=args['target_year'],
                             target_exam=args['target_exam'],
                             type=args['type'],
                             other=args['other'],
                             batch_timings=args['batch_timings'],
                             institute_id=kwargs['user'].id)
        return {'batch': batch}
Ejemplo n.º 10
0
    def get(self, *args, **kwargs):
        parser = reqparse.RequestParser()
        parser.add_argument('days', type=str, choices=['weekdays', 'weekends'])
        parser.add_argument('type',
                            type=str,
                            choices=app.config['BATCH_TYPE'].keys())
        parser.add_argument('target_year', type=int)
        parser.add_argument('target_exam',
                            type=str,
                            choices=app.config['TARGET_EXAMS'].keys())
        parser.add_argument('branch',
                            type=str,
                            choices=app.config['BATCH_FIELD'].keys())
        parser.add_argument('status', type=int, default=-1)
        args = parser.parse_args()

        if args['branch'] is not None:
            args['branches'] = [
                args['branch'],
            ]
        args.pop('branch', None)

        batches = Batch.get_filtered(institute_id=kwargs['user'].id, **args)
        return {'batches': batches}
Ejemplo n.º 11
0
 def get(self, *args, **kwargs):
     batch = BatchModel.get(kwargs['id'])
     return {'batch': batch}
Ejemplo n.º 12
0
    def get(self, *args, **kwargs):
        parser = reqparse.RequestParser()
        parser.add_argument('target_exams', type=comma_separated_ints_type)
        parser.add_argument('batches', type=comma_separated_ints_type)
        args = parser.parse_args()
        # if batches are provided
        if args['batches'] is not None:
            batch_ids = args['batches']
        # if batches are not provided but target exams are provided
        elif args['target_exams'] is not None:
            target_exams = map(str, args['target_exams'])
            batch_ids = [b.id for b in Batch.query.filter(Batch.target_exam.in_(target_exams), Batch.status == 1,
                                                          Batch.institute_id == kwargs['user'].id).all()]
        # if no filters are used then get all batches for this institute
        else:
            batch_ids = [b.id for b in Batch.get_filtered(institute_id=kwargs['user'].id)]

        students = {}
        for sb in StudentBatches.query.filter(StudentBatches.batch_id.in_(batch_ids)).all():
            student_id = sb.student_id
            batch_id = sb.batch_id
            if student_id not in students:
                students[student_id] = {}
            if batch_id not in students[student_id]:
                # not using dictionary for `joined_at` and `left_at` on purpose, saving memory
                students[student_id][batch_id] = (sb.joined_at, sb.left_at)

        # current_students, past_students = [], []
        # for student in students:
        #     (current_students, past_students)[student['left_at'] is not None].append(student)

        # Get all mock tests that were pushed to the batches with id in `batch_ids`
        pushed_mock_tests = PushedMockTest.query.filter(PushedMockTest.batch_id.in_(batch_ids)).all()

        exprs = [AttemptedMockTest.pushed_mock_test_id.in_([p.id for p in pushed_mock_tests])]

        # if len(current_students) > 0:
        #     exprs.append(AttemptedMockTest.student_id.in_([s['id'] for s in current_students]))
        # if len(past_students) > 0:
        #     for student in past_students:
        #         exprs.append(or_(AttemptedMockTest.student_id == student['id'], AttemptedMockTest.attempted_at < student['left_at']))

        attempted_mock_tests = AttemptedMockTest.query.filter(*exprs).all()

        mock_test_ids = list({a.mock_test_id for a in attempted_mock_tests})
        mock_tests = {m.id: m for m in MockTest.query.filter(MockTest.id.in_(mock_test_ids)).all()}

        question_ids = set()
        for mock_test in mock_tests.values():
            subjects = json.loads(mock_test.question_ids)
            for sid, data in subjects.items():
                question_ids.update(set(data['q_ids']))

        question_difficulty = {q.id: q.difficulty for q in Question.query.with_entities(Question.id, Question.difficulty).filter(Question.id.in_(list(question_ids)))}
        exam_weights = app.config['TARGET_EXAM_WEIGHT']

        performance = {}
        topics = {}
        attempted_test_count = {}
        for amt in attempted_mock_tests:
            mock_test = mock_tests[amt.mock_test_id]
            d = []
            subjects = json.loads(mock_test.question_ids)
            analysis = json.loads(amt.analysis)
            for sid, data in subjects.items():
                for q_id in data['q_ids']:
                    d.append(int(question_difficulty[q_id]))
            average_difficulty = sum(d)/float(len(d))
            p = (amt.score/analysis['maximum_marks'])*exam_weights[mock_tests[amt.mock_test_id].target_exam] * average_difficulty

            if amt.student_id in performance:
                performance[amt.student_id][amt.id] = p
            else:
                performance[amt.student_id] = {
                    amt.id: p
                }
            attempted_test_count[amt.student_id] = attempted_test_count.get(amt.student_id, 0) + 1
            # this set is used for keeping track of those topic ids whose subject id not known yet
            new_topic_ids = set()
            for topic_id, data in analysis['topics'].items():
                topic_id = int(topic_id)
                total = len(data['not_attempted']) + len(data['correct']) + len(data['incorrect'])
                correct = len(data['correct'])
                if topic_id in topics:
                    topics[topic_id]['total'] += total
                    topics[topic_id]['correct'] += correct
                else:
                    # topic has not been seen yet
                    new_topic_ids.add(topic_id)
                    topics[topic_id] = {
                        'total': total,
                        'correct': correct
                    }
            for sid, data in analysis['subjects'].items():
                sid = int(sid)
                subject_topic_ids = set(data['topic_ids'])
                # topic ids of the current subject whose subject id was not known
                found_topic_ids = subject_topic_ids.intersection(new_topic_ids)
                for tid in found_topic_ids:
                    topics[tid]['subject_id'] = sid
                new_topic_ids = new_topic_ids - found_topic_ids

        student_count = len(performance)
        top_student_count = int(student_count*(app.config['TOP_PERFORMERS_PERCENTAGE']/100))
        top_student_count = top_student_count if top_student_count > app.config['TOP_PERFORMERS_MIN_COUNT'] else app.config['TOP_PERFORMERS_MIN_COUNT']
        bottom_student_count = int(student_count*(app.config['BOTTOM_PERFORMERS_PERCENTAGE']/100))
        bottom_student_count = bottom_student_count if bottom_student_count > app.config['BOTTOM_PERFORMERS_MIN_COUNT'] else app.config['BOTTOM_PERFORMERS_MIN_COUNT']
        top_students = {}
        bottom_students = {}

        for student_id, data in performance.items():
            score = sum(data.values())/float(len(data.values()))

            if len(top_students) < top_student_count:
                top_students[student_id] = score
            else:
                min_score = min(top_students.values())
                if score > min_score:
                    k = None
                    for i, s in top_students.items():
                        if s == min_score:
                            k = i
                            break
                    if k is not None:
                        del top_students[k]
                        top_students[student_id] = score

            if len(bottom_students) < bottom_student_count:
                bottom_students[student_id] = score
            else:
                max_score = max(bottom_students.values())
                if score < max_score:
                    k = None
                    for i, s in bottom_students.items():
                        if s == max_score:
                            k = i
                            break
                    if k is not None:
                        del bottom_students[k]
                        bottom_students[student_id] = score

        # Students that appear both in top students and bottom students should be removed from bottom students
        for student_id in top_students:
            if student_id in bottom_students:
                del bottom_students[student_id]


        top_topic_count = app.config['TOP_TOPICS_COUNT']
        bottom_topic_count = app.config['BOTTOM_TOPICS_COUNT']
        top_topics = {}
        bottom_topics = {}
        top_topics_by_subjects = {}
        bottom_topics_by_subjects = {}
        subject_ids = set()

        for topic_id, data in topics.items():
            accuracy = (data['correct']*100.0)/data['total'] if data['total'] > 0 else 0
            subject_id = data['subject_id']
            subject_ids.add(subject_id)
            # Overall top topics

            # if number of top_topics found yet is less than required top topics then add topic to top_topics
            if len(top_topics) < top_topic_count:
                top_topics[topic_id] = accuracy
            # if number of top_topics found yet is more than or equal to required top_topics
            else:
                min_acc = min(top_topics.values())
                # if accuracy of current topic is more than minimum accuracy of any topic in top_topics
                if accuracy > min_acc:
                    k = None
                    # in the next loop the topic_id with minimum accuracy is found
                    for i, s in top_topics.items():
                        if s == min_acc:
                            k = i
                            break
                    # remove the topic_id with minimum accuracy
                    del top_topics[k]
                    # add current topic to top_topics
                    top_topics[topic_id] = accuracy

            # Subject wise top topics

            if subject_id not in top_topics_by_subjects or len(top_topics_by_subjects[subject_id]) < top_topic_count:
                if subject_id not in top_topics_by_subjects:
                    top_topics_by_subjects[subject_id] = {}
                top_topics_by_subjects[subject_id][topic_id] = accuracy
            else:
                min_acc = min(top_topics_by_subjects[subject_id].values())
                if accuracy > min_acc:
                    k = None
                    for i, s in top_topics_by_subjects[subject_id].items():
                        if s == min_acc:
                            k = i
                            break
                    del top_topics_by_subjects[subject_id][k]
                    top_topics_by_subjects[subject_id][topic_id] = accuracy

            # Overall bottom topics

            # if number of bottom_topics found yet is less than required bottom topics then add topic to bottom_topics
            if len(bottom_topics) < bottom_topic_count:
                bottom_topics[topic_id] = accuracy
            # if number of bottom_topics found yet is more than or equal to required bottom_topics
            else:
                max_acc = max(bottom_topics.values())
                # if accuracy of current topic is less than maximum accuracy of any topic in bottom_topics
                if accuracy < max_acc:
                    k = None
                    # in the next loop the topic_id with maximum accuracy is found
                    for i, s in bottom_topics.items():
                        if s == max_acc:
                            k = i
                            break
                    # remove the topic_id with maximum accuracy
                    del bottom_topics[k]
                    # add current topic to bottom_topics
                    bottom_topics[topic_id] = accuracy

            # Subject wise bottom topics

            if subject_id not in bottom_topics_by_subjects or len(bottom_topics_by_subjects[subject_id]) < bottom_topic_count:
                if subject_id not in bottom_topics_by_subjects:
                    bottom_topics_by_subjects[subject_id] = {}
                bottom_topics_by_subjects[subject_id][topic_id] = accuracy
            else:
                max_acc = max(bottom_topics_by_subjects[subject_id].values())
                if accuracy < max_acc:
                    k = None
                    for i, s in bottom_topics_by_subjects[subject_id].items():
                        if s == max_acc:
                            k = i
                            break
                    del bottom_topics_by_subjects[subject_id][k]
                    bottom_topics_by_subjects[subject_id][topic_id] = accuracy

        # Topics that appear both in top topics and bottom topics should be removed from bottom topics
        for topic_id in top_topics:
            if topic_id in bottom_topics:
                del bottom_topics[topic_id]

        # Topics that appear both in top topics for a subject and bottom topics of the same subject should be removed
        # from bottom topics of that subject
        for subject_id in top_topics_by_subjects:
            if subject_id in bottom_topics_by_subjects:
                for topic_id in top_topics_by_subjects[subject_id]:
                    if topic_id in bottom_topics_by_subjects[subject_id]:
                        del bottom_topics_by_subjects[subject_id][topic_id]

        student_objs = {s.id: s for s in Student.query.filter(Student.id.in_(top_students.keys()+bottom_students.keys()))}

        top_students_list = []
        bottom_students_list = []

        # Calculating attendance for top_students
        for student_id in top_students:
            total_pushed = 0
            seen_mock_test_ids = set()
            for batch_id, dates in students[student_id].items():
                # if current student
                if dates[1] is None:
                    for pmt in pushed_mock_tests:
                        # if mock test was pushed to this batch
                        if pmt.batch_id == batch_id:
                            if pmt.mock_test_id in seen_mock_test_ids:
                                continue
                            total_pushed += 1
                            seen_mock_test_ids.add(pmt.mock_test_id)
                # if past student
                else:
                    for pmt in pushed_mock_tests:
                        # if mock test was pushed to this batch before this student left the batch
                        if pmt.batch_id == batch_id and dates[0] < pmt.pushed_at < dates[1]:
                            if pmt.mock_test_id in seen_mock_test_ids:
                                continue
                            total_pushed += 1
                            seen_mock_test_ids.add(pmt.mock_test_id)
            student = {
                'id': student_id,
                'attendance': (attempted_test_count[student_id]*100.0)/total_pushed if total_pushed > 0 else 0,
                'name': student_objs[student_id].name,
                'score': top_students[student_id]
            }
            top_students_list.append(student)

        # Calculating attendance for bottom_students
        for student_id in bottom_students:
            total_pushed = 0
            for batch_id, dates in students[student_id].items():
                # if current student
                if dates[1] is None:
                    for pmt in pushed_mock_tests:
                        # if mock test was pushed to this batch
                        if pmt.batch_id == batch_id:
                            total_pushed += 1
                # if past student
                else:
                    for pmt in pushed_mock_tests:
                        # if mock test was pushed to this batch before this student left the batch
                        if pmt.batch_id == batch_id and dates[0] < pmt.pushed_at < dates[1]:
                            total_pushed += 1

            student = {
                'id': student_id,
                'attendance': (attempted_test_count[student_id]*100.0)/total_pushed if total_pushed > 0 else 0,
                'name': student_objs[student_id].name,
                'score': bottom_students[student_id]
            }
            bottom_students_list.append(student)

        required_topic_ids = set()
        for d in top_topics_by_subjects.values():
            required_topic_ids.update(set(d.keys()))
        for d in bottom_topics_by_subjects.values():
            required_topic_ids.update(set(d.keys()))

        node_objs = {n.id: n for n in Ontology.query.filter(or_(Ontology.id.in_(top_topics.keys()+bottom_topics.keys()),
                                                                Ontology.id.in_(list(subject_ids)),
                                                                Ontology.id.in_(list(required_topic_ids))))}

        top_topics_list = []
        bottom_topics_list = []

        for topic_id, accuracy in top_topics.items():
            topic_id = int(topic_id)
            subject_id = node_objs[topic_id].parent_path[0]
            subject_name = node_objs[subject_id].name
            topic = {
                'id': topic_id,
                'name': node_objs[topic_id].name,
                'accuracy': accuracy,
                'subject_id': subject_id,
                'subject_name': subject_name
            }
            top_topics_list.append(topic)

        for topic_id, accuracy in bottom_topics.items():
            topic_id = int(topic_id)
            subject_id = node_objs[topic_id].parent_path[0]
            subject_name = node_objs[subject_id].name
            topic = {
                'id': topic_id,
                'name': node_objs[topic_id].name,
                'accuracy': accuracy,
                'subject_id': subject_id,
                'subject_name': subject_name
            }
            bottom_topics_list.append(topic)

        for subject_id, data in top_topics_by_subjects.items():
            subject_name = node_objs[subject_id].name
            topics_list = tuple(data.items())
            top_topics_by_subjects[subject_id] = [{
                'id': tpl[0],
                'name': node_objs[tpl[0]].name,
                'accuracy': tpl[1],
                'subject_id': subject_id,
                'subject_name': subject_name
            } for tpl in topics_list]

        for subject_id, data in bottom_topics_by_subjects.items():
            subject_name = node_objs[subject_id].name
            topics_list = tuple(data.items())
            bottom_topics_by_subjects[subject_id] = [{
                'id': tpl[0],
                'name': node_objs[tpl[0]].name,
                'accuracy': tpl[1],
                'subject_id': subject_id,
                'subject_name': subject_name
            } for tpl in topics_list]

        return {
            'top_students': top_students_list,
            'bottom_students': bottom_students_list,
            'top_topics': top_topics_list,
            'bottom_topics': bottom_topics_list,
            'top_topics_by_subjects': top_topics_by_subjects,
            'bottom_topics_by_subjects': bottom_topics_by_subjects
        }
    def put(self, *args, **kwargs):
        parser = reqparse.RequestParser()
        parser.add_argument('name', type=str, required=True)
        parser.add_argument('password', type=str)
        parser.add_argument('mobile_no', type=str, required=True)
        parser.add_argument('city', type=str)
        parser.add_argument('area', type=str)
        parser.add_argument('pin', type=str)
        parser.add_argument('school', type=str)
        parser.add_argument('ntse_score', type=float)
        parser.add_argument('roll_no', type=str)
        parser.add_argument('father_name', type=str)
        parser.add_argument('father_mobile_no', type=str)
        parser.add_argument('father_email', type=str)
        parser.add_argument('batch_ids',
                            type=comma_separated_ints_type,
                            required=True)
        args = parser.parse_args()
        student = Student.get(kwargs['id'])
        if args['password'] is not None:
            student.password = md5(args['password']).hexdigest()
        student.name = args['name']
        student.mobile_no = args['mobile_no']
        student.city = args['city']
        student.area = args['area']
        student.pin = args['pin']
        student.school = args['school']
        student.ntse_score = args['ntse_score']
        student.roll_no = args['roll_no']
        student.father_name = args['father_name']
        student.father_mobile_no = args['father_mobile_no']
        student.father_email = args['father_email']
        unow = datetime.datetime.utcnow()

        # new batches that the student may join. ids from this will be removed in a loop later
        new_batch_ids = args['batch_ids'][:]

        for sb in StudentBatches.query.filter(
                StudentBatches.student_id == student.id).all():
            # if any old batch_id not in newly supplied batch ids then student will leave that batch
            if sb.batch_id not in args['batch_ids']:
                sb.left_at = unow
            # old batch_id also in newly supplied batch ids so remove this batch_id from new_batch_ids
            else:
                new_batch_ids.remove(sb.batch_id)
                # if student rejoining
                if sb.left_at is not None:
                    sb.left_at = None

        target_exams = set()
        target_year = None
        batches = Batch.get_filtered(include_ids=args['batch_ids'])
        engineering_exams = {'1', '2', '3'}
        medical_exams = {'4', '5'}
        for batch in batches:
            if batch.target_exam in engineering_exams:
                target_exams.update(engineering_exams)
            if batch.target_exam in medical_exams:
                target_exams.update(medical_exams)
            if target_year is None or target_year > batch.target_year:
                target_year = batch.target_year
            # if a new batch id encountered
            if batch.id in new_batch_ids:
                sb = StudentBatches(batch_id=batch.id,
                                    student_id=student.id,
                                    joined_at=datetime.datetime.utcnow())
                db.session.add(sb)

        branches = []
        if len(target_exams.intersection(engineering_exams)) > 0:
            branches.append('1')
        if len(target_exams.intersection(medical_exams)) > 0:
            branches.append('2')

        student.target_year = target_year
        student.target_exams = list(target_exams)
        student.branches = branches
        db.session.commit()
        student.batches = [{'id': b.id, 'name': b.name} for b in batches]
        return {'student': student}
        email = student_details['email'],
        password = md5(student_details['password']).hexdigest(),
        mobile_no = student_details['mobile_no'],
        city = student_details['city'],
        area = student_details['area'],
        # pin = student_details['pin'],
        school = student_details['school'],
        # ntse_score = student_details['ntse_score'],
        roll_no = student_details['roll_no'],
        father_name = student_details['father_name'],
        father_mobile_no = student_details['father_mobile_no'],
        # father_email = student_details['father_email'],
        registered_from = student_details['registered_from'],
        target_year = int(student_details['target_year'])
    )
    student.branches = [i for i in student_details['branches'].split(',')]
    student.target_exams = [int(i) for i in student_details['target_exams'].split(',')]
    batch_obj = all_batches[student_details['batch']]
    student_batch = StudentBatches(batch_id=batch_obj['id'], student_id=student.id, joined_at=datetime.datetime.utcnow())
    db.session.add(student_batch)
    db.session.commit()


_students = get_data_from_csv('/Users/rishabh/Downloads/student_data.csv')
students = [student for student in _students]
institute = Institute.get(4)
batches = Batch.get_filtered(institute_id=institute.id)
batches = {batch.name: batch.__dict__ for batch in batches}
# for student in students:
#     create_student(student, batches)
    def get_cumulative_analysis(student_id, institute_id=None):
        # get mock tests by student which have completed
        if institute_id is not None:
            batches = Batch.get_filtered(institute_id=institute_id)
            pushed_mock_tests = PushedMockTest.query.filter(
                PushedMockTest.batch_id.in_([b.id for b in batches]))
            attempted_mock_tests = AttemptedMockTest.query.filter(
                AttemptedMockTest.student_id == student_id,
                AttemptedMockTest.score != None,
                AttemptedMockTest.pushed_mock_test_id.in_(
                    [p.id for p in pushed_mock_tests])).all()
        else:
            attempted_mock_tests = AttemptedMockTest.query.filter(
                AttemptedMockTest.student_id == student_id,
                AttemptedMockTest.score != None).all()
        mock_test_ids = [amt.mock_test_id for amt in attempted_mock_tests]
        mock_tests = MockTest.query.filter(
            MockTest.id.in_(mock_test_ids)).all()
        question_ids = set()
        overall_correct_q_ids = set()
        overall_incorrect_q_ids = set()
        overall_not_attempted_q_ids = set()
        total_ideal_time = 0
        total_taken_time = 0

        for amt in attempted_mock_tests:
            analysis = json.loads(amt.analysis)
            subjects = analysis['subjects']
            for sid in subjects:
                overall_correct_q_ids.update(set(subjects[sid]['correct']))
                overall_incorrect_q_ids.update(set(subjects[sid]['incorrect']))
                overall_not_attempted_q_ids.update(
                    set(subjects[sid]['not_attempted']))

        question_ids.update(overall_correct_q_ids)
        question_ids.update(overall_incorrect_q_ids)
        question_ids.update(overall_not_attempted_q_ids)
        questions = {
            q.id: q
            for q in Question.get_filtertered_list(
                include_question_ids=list(question_ids))['questions']
        }
        overall_attempted_count = len(overall_incorrect_q_ids) + len(
            overall_correct_q_ids)
        accuracy = round(
            len(overall_correct_q_ids) * 100.0 /
            overall_attempted_count, 2) if overall_attempted_count > 0 else 0.0

        for amt in attempted_mock_tests:
            answers = json.loads(amt.answers)
            for q_id, answer in answers.items():
                q_id = int(q_id)
                # if attempted question
                if len(answer['options']) != 0 and q_id in questions:
                    total_ideal_time += questions[q_id].average_time
                    total_taken_time += answer['time']

        overall_speed = total_ideal_time - total_taken_time

        return {
            'attempted_mock_tests': attempted_mock_tests,
            'mock_tests': mock_tests,
            'questions': questions.values(),
            'accuracy': accuracy,
            'speed': overall_speed
        }