Beispiel #1
0
def schoolyear(request):
    path_parts = request.path.lstrip('/').split('/')
    if len(path_parts) > 3:
        program_url = '/'.join(path_parts[1:3])
        if Program.objects.filter(url=program_url).count() == 1:
            program = Program.objects.get(url=program_url)
            return {'schoolyear': ESPUser.program_schoolyear(program)}
    return {'schoolyear': ESPUser.current_schoolyear()}
def schoolyear(request):
    path_parts = request.path.lstrip('/').split('/')
    if len(path_parts) > 3:
        program_url = '/'.join(path_parts[1:3])
        if Program.objects.filter(url=program_url).count() == 1:
            program = Program.objects.get(url=program_url)
            return {'schoolyear': ESPUser.program_schoolyear(program)}
    return {'schoolyear': ESPUser.current_schoolyear()}
Beispiel #3
0
    def initialize(self):
        """ Gather all of the information needed to run the lottery assignment.
            This includes:
            -   Students' interest (priority and interested bits)
            -   Class schedules and capacities
            -   Timeslots (incl. lunch periods for each day)
        """

        self.enroll_orig = numpy.zeros(
            (self.num_students, self.num_sections, self.num_timeslots),
            dtype=numpy.bool)
        self.enroll_final = numpy.zeros(
            (self.num_students, self.num_sections, self.num_timeslots),
            dtype=numpy.bool)
        self.request = numpy.zeros(
            (self.num_students, self.num_sections, self.num_timeslots),
            dtype=numpy.bool)
        self.waitlist = [
            numpy.zeros(
                (self.num_students, self.num_sections, self.num_timeslots),
                dtype=numpy.bool) for i in range(self.priority_limit + 1)
        ]
        self.section_schedules = numpy.zeros(
            (self.num_sections, self.num_timeslots), dtype=numpy.bool)
        self.section_capacities = numpy.zeros((self.num_sections, ),
                                              dtype=numpy.int32)
        self.section_scores = numpy.zeros((self.num_sections, ),
                                          dtype=numpy.int32)
        self.same_subject = numpy.zeros((self.num_sections, self.num_sections),
                                        dtype=numpy.bool)
        self.section_conflict = numpy.zeros(
            (self.num_sections, self.num_sections), dtype=numpy.bool
        )  # is this a section that takes place in the same timeblock

        #   Get student, section, timeslot IDs and prepare lookup table
        (self.student_ids,
         self.student_indices) = self.get_ids_and_indices(self.students)
        (self.section_ids,
         self.section_indices) = self.get_ids_and_indices(self.sections)
        (self.timeslot_ids,
         self.timeslot_indices) = self.get_ids_and_indices(self.timeslots)
        self.parent_classes = numpy.array(
            self.sections.values_list('parent_class__id', flat=True))

        self.student_not_checked_in = numpy.zeros((self.num_students, ),
                                                  dtype=numpy.bool)
        self.student_not_checked_in[self.student_indices[
            self.students_not_checked_in]] = True

        #   Get IDs of timeslots allocated to lunch by day
        #   (note: requires that this is constant across days)
        self.lunch_schedule = numpy.zeros((self.num_timeslots, ))
        lunch_timeslots = Event.objects.filter(
            meeting_times__parent_class__parent_program=self.program,
            meeting_times__parent_class__category__category='Lunch').order_by(
                'start').distinct()
        #   Note: this code should not be necessary once lunch-constraints branch is merged (provides Program.dates())
        dates = []
        for ts in self.timeslots:
            ts_day = date(ts.start.year, ts.start.month, ts.start.day)
            if ts_day not in dates:
                dates.append(ts_day)
        lunch_by_day = [[] for x in dates]
        ts_count = 0
        for ts in lunch_timeslots:
            d = date(ts.start.year, ts.start.month, ts.start.day)
            lunch_by_day[dates.index(d)].append(ts.id)
            self.lunch_schedule[self.timeslot_indices[ts.id]] = True

        for i in range(len(lunch_by_day)):
            if len(lunch_by_day[i]) > ts_count:
                ts_count = len(lunch_by_day[i])
        self.lunch_timeslots = numpy.zeros((len(lunch_by_day), ts_count),
                                           dtype=numpy.int32)
        for i in range(len(lunch_by_day)):
            self.lunch_timeslots[i, :len(lunch_by_day[i])] = numpy.array(
                lunch_by_day[i])

        #   Populate old enrollment matrix
        enroll_regs = StudentRegistration.objects.filter(
            self.Q_EN).values_list('user__id', 'section__id',
                                   'section__meeting_times__id').distinct()
        era = numpy.array(enroll_regs, dtype=numpy.uint32)
        try:
            self.enroll_orig[self.student_indices[era[:, 0]],
                             self.section_indices[era[:, 1]],
                             self.timeslot_indices[era[:, 2]]] = True
        except IndexError:
            pass

        self.student_not_checked_in[numpy.transpose(
            numpy.nonzero(True - self.enroll_orig.any(axis=(1, 2))))] = True

        #   Populate old enrollment matrix
        request_regs = StudentRegistration.objects.filter(
            self.Q_REQ).values_list('user__id', 'section__id',
                                    'section__meeting_times__id').distinct()
        rra = numpy.array(request_regs, dtype=numpy.uint32)
        try:
            self.request[self.student_indices[rra[:, 0]],
                         self.section_indices[rra[:, 1]],
                         self.timeslot_indices[rra[:, 2]]] = True
        except IndexError:
            pass

        #   Populate waitlist matrix
        waitlist_regs = [
            StudentRegistration.objects.filter(self.Q_WAIT[i]).values_list(
                'user__id', 'section__id',
                'section__meeting_times__id').distinct()
            for i in range(self.priority_limit + 1)
        ]
        wra = [
            numpy.array(waitlist_regs[i], dtype=numpy.uint32)
            for i in range(self.priority_limit + 1)
        ]
        self.waitlist[0][:, :, :] = True
        for i in range(1, self.priority_limit + 1):
            try:
                self.waitlist[i][self.student_indices[wra[i][:, 0]],
                                 self.section_indices[wra[i][:, 1]],
                                 self.timeslot_indices[wra[i][:, 2]]] = True
                self.waitlist[0][self.student_indices[wra[i][:, 0]],
                                 self.section_indices[wra[i][:, 1]],
                                 self.timeslot_indices[wra[i][:, 2]]] = False
            except IndexError:
                pass

        #   Populate section schedule
        section_times = numpy.array(
            self.sections.values_list('id', 'meeting_times__id'))
        self.section_schedules[self.section_indices[section_times[:, 0]],
                               self.timeslot_indices[section_times[:,
                                                                   1]]] = True

        #   Populate section overlap matrix
        for i in range(self.num_sections):
            group_ids = numpy.nonzero(
                self.parent_classes == self.parent_classes[i])[0]
            self.same_subject[numpy.meshgrid(group_ids, group_ids)] = True

            sec_times = numpy.transpose(
                numpy.nonzero(self.section_schedules[i, :]))
            for [
                    ts_ind,
            ] in sec_times:
                self.section_conflict[numpy.transpose(
                    numpy.nonzero(self.section_schedules[:, ts_ind])),
                                      i] = True
                self.section_conflict[
                    i,
                    numpy.transpose(
                        numpy.nonzero(self.section_schedules[:,
                                                             ts_ind]))] = True
            self.section_conflict[i, i] = False

        #   Populate section grade limits
        self.section_grade_min = numpy.array(self.sections.values_list(
            'parent_class__grade_min', flat=True),
                                             dtype=numpy.uint32)
        self.section_grade_max = numpy.array(self.sections.values_list(
            'parent_class__grade_max', flat=True),
                                             dtype=numpy.uint32)

        #   Populate student grades; grade will be assumed to be 0 if not entered on profile
        self.student_grades = numpy.zeros((self.num_students, ))
        gradyear_pairs = numpy.array(RegistrationProfile.objects.filter(
            user__id__in=list(self.student_ids),
            most_recent_profile=True,
            student_info__graduation_year__isnull=False).values_list(
                'user__id', 'student_info__graduation_year'),
                                     dtype=numpy.uint32)
        self.student_grades[self.student_indices[
            gradyear_pairs[:, 0]]] = 12 + ESPUser.program_schoolyear(
                self.program) - gradyear_pairs[:, 1]

        #   Find section capacities (TODO: convert to single query)
        for sec in self.sections:
            sec_ind = self.section_indices[sec.id]
            self.section_capacities[sec_ind] = sec.capacity - sec.num_students(
            )
            self.section_capacities[sec_ind] += numpy.count_nonzero(
                (self.enroll_orig[:, sec_ind,
                                  self.section_schedules[sec_ind, :]].any(
                                      axis=1) *
                 self.request[:, :, self.section_schedules[sec_ind, :]].any(
                     axis=(1, 2))))  # number enrolled but want to switch out
            self.enroll_final[numpy.transpose(
                numpy.nonzero(
                    (self.enroll_orig[:, sec_ind,
                                      self.section_schedules[sec_ind, :]].any(
                                          axis=1) *
                     (True - self.request[:, :, self.section_schedules[
                         sec_ind, :]].any(axis=(1, 2)))))), sec_ind,
                              self.section_schedules[sec_ind, :]] = True
            self.section_scores[sec_ind] = -self.section_capacities[sec_ind]
            self.section_scores[sec_ind] += numpy.count_nonzero(
                self.request[:, sec_ind, :].any(
                    axis=1))  # number who want to switch in
        self.section_capacities_orig = numpy.copy(self.section_capacities)
        self.section_scores_orig = numpy.copy(self.section_scores)
        self.enroll_final_orig = numpy.copy(self.enroll_final)
        self.clear_assignments()
    def onsite_create(self, request, tl, one, two, module, extra, prog):
        if request.method == 'POST':
            form = OnSiteRegForm(request.POST)

            if form.is_valid():
                new_data = form.cleaned_data
                username = ESPUser.get_unused_username(new_data['first_name'],
                                                       new_data['last_name'])
                new_user = ESPUser.objects.create_user(
                    username=username,
                    first_name=new_data['first_name'],
                    last_name=new_data['last_name'],
                    email=new_data['email'])

                self.student = new_user

                regProf = RegistrationProfile.getLastForProgram(
                    new_user, self.program)
                contact_user = ContactInfo(first_name=new_user.first_name,
                                           last_name=new_user.last_name,
                                           e_mail=new_user.email,
                                           user=new_user)
                contact_user.save()
                regProf.contact_user = contact_user

                student_info = StudentInfo(
                    user=new_user,
                    graduation_year=ESPUser.YOGFromGrade(
                        new_data['grade'],
                        ESPUser.program_schoolyear(self.program)))

                try:
                    if isinstance(new_data['k12school'], K12School):
                        student_info.k12school = new_data['k12school']
                    else:
                        if isinstance(new_data['k12school'], int):
                            student_info.k12school = K12School.objects.get(
                                id=int(new_data['k12school']))
                        else:
                            student_info.k12school = K12School.objects.filter(
                                name__icontains=new_data['k12school'])[0]
                except:
                    student_info.k12school = None
                student_info.school = new_data[
                    'school'] if not student_info.k12school else student_info.k12school.name

                student_info.save()
                regProf.student_info = student_info

                regProf.save()

                if new_data['paid']:
                    self.createBit('paid')
                    self.updatePaid(True)
                else:
                    self.updatePaid(False)

                self.createBit('Attended')

                if new_data['medical']:
                    self.createBit('Med')

                if new_data['liability']:
                    self.createBit('Liab')

                self.createBit('OnSite')

                new_user.groups.add(Group.objects.get(name="Student"))

                new_user.recoverPassword()

                return render_to_response(
                    self.baseDir() + 'reg_success.html', request, {
                        'student':
                        new_user,
                        'retUrl':
                        '/onsite/%s/classchange_grid?student_id=%s' %
                        (self.program.getUrlBase(), new_user.id)
                    })

        else:
            form = OnSiteRegForm()

        return render_to_response(self.baseDir() + 'reg_info.html', request,
                                  {'form': form})
Beispiel #5
0
    def stats(prog):
        # Create a dictionary to assemble the output
        dictOut = {"stats": []}

        classes = prog.classes().select_related()
        vitals = {'id': 'vitals'}

        class_num_list = []
        class_num_list.append(
            ("Total # of Classes", classes.distinct().count()))
        class_num_list.append(
            ("Total # of Class Sections",
             prog.sections().select_related().distinct().count()))
        class_num_list.append(
            ("Total # of Lunch Classes",
             classes.filter(category__category="Lunch").filter(
                 status=10).distinct().count()))
        class_num_list.append(
            ("Total # of Classes <span style='color: #00C;'>Unreviewed</span>",
             classes.filter(status=0).distinct().count()))
        class_num_list.append(
            ("Total # of Classes <span style='color: #0C0;'>Accepted</span>",
             classes.filter(status=10).distinct().count()))
        class_num_list.append(
            ("Total # of Classes <span style='color: #C00;'>Rejected</span>",
             classes.filter(status=-10).distinct().count()))
        class_num_list.append(
            ("Total # of Classes <span style='color: #990;'>Cancelled</span>",
             classes.filter(status=-20).distinct().count()))
        for ft in ClassFlagType.get_flag_types(prog):
            class_num_list.append(
                ('Total # of Classes with the "%s" flag' % ft.name,
                 classes.filter(flags__flag_type=ft).distinct().count()))
        vitals['classnum'] = class_num_list

        #   Display pretty labels for teacher and student numbers
        teacher_labels_dict = {}
        for module in prog.getModules():
            teacher_labels_dict.update(module.teacherDesc())
        vitals['teachernum'] = []

        ## Ew, queries in a for loop...
        ## Not much to be done about it, though;
        ## the loop is iterating over a list of independent queries and running each.
        teachers = prog.teachers()
        for key in teachers.keys():
            if key in teacher_labels_dict:
                vitals['teachernum'].append((
                    teacher_labels_dict[key],  ## Unfortunately,
                    teachers[key].filter(is_active=True).distinct().count()))
            else:
                vitals['teachernum'].append(
                    (key,
                     teachers[key].filter(is_active=True).distinct().count()))

        student_labels_dict = {}
        for module in prog.getModules():
            student_labels_dict.update(module.studentDesc())
        vitals['studentnum'] = []

        ## Ew, another set of queries in a for loop...
        ## Same justification, though.
        students = prog.students()
        for key in students.keys():
            if key in student_labels_dict:
                vitals['studentnum'].append(
                    (student_labels_dict[key],
                     students[key].filter(is_active=True).distinct().count()))
            else:
                vitals['studentnum'].append(
                    (key,
                     students[key].filter(is_active=True).distinct().count()))

        timeslots = prog.getTimeSlots()
        vitals['timeslots'] = []

        shours = 0.0
        chours = 0.0
        crhours = 0.0
        ## Write this as a 'for' loop because PostgreSQL can't do it in
        ## one go without a subquery or duplicated logic, and Django
        ## doesn't have enough power to expose either approach directly.
        ## At least there aren't any queries in the for loop...
        ## (In MySQL, this could I believe be done with a minimally-painful extra() clause.)
        ## Also, since we're iterating over a big data set, use .values()
        ## minimize the number of objects that we're creating.
        ## One dict and two Decimals per row, as opposed to
        ## an Object per field and all kinds of stuff...
        for cls in prog.classes().exclude(category__category='Lunch').annotate(
                num_sections=Count('sections'),
                subject_duration=Sum('sections__duration'),
                subject_students=Sum('sections__enrolled_students')).values(
                    'num_sections', 'subject_duration', 'subject_students',
                    'class_size_max'):
            if cls['subject_duration']:
                chours += float(cls['subject_duration'])
                shours += float(cls['subject_duration']) * (float(
                    cls['class_size_max']) if cls['class_size_max'] else 0)
                crhours += float(cls['subject_duration']) * float(
                    cls['subject_students']) / float(cls['num_sections'])
        vitals["hournum"] = []
        vitals["hournum"].append(("Total # of Class-Hours", chours))
        vitals["hournum"].append(
            ("Total # of Class-Student-Hours (capacity)", shours))
        vitals["hournum"].append(
            ("Total # of Class-Student-Hours (registered)", crhours))

        ## Prefetch enough data that get_meeting_times() and num_students() don't have to hit the db
        curclasses = ClassSection.prefetch_catalog_data(
            ClassSection.objects.filter(
                parent_class__parent_program=prog).select_related(
                    'parent_class', 'parent_class__category'))

        ## Is it really faster to do this logic in Python?
        ## It'd be even faster to just write a raw SQL query to do it.
        ## But this is probably good enough.
        timeslot_dict = defaultdict(list)
        timeslot_set = set(timeslots)
        for section in curclasses:
            for timeslot in set.intersection(timeslot_set,
                                             section.get_meeting_times()):
                timeslot_dict[timeslot].append(section)

        for timeslot in timeslots:
            curTimeslot = {'slotname': timeslot.short_description}

            curTimeslot['classcount'] = len(timeslot_dict[timeslot])

            def student_count(clslist):
                lst = [0] + [
                    x.num_students()
                    for x in clslist if x.category.category != 'Lunch'
                ]
                return reduce(operator.add, lst)

            def student_max_count(clslist):
                lst = [0] + [
                    x.capacity
                    for x in clslist if x.category.category != 'Lunch'
                ]
                return reduce(operator.add, lst)

            curTimeslot['studentcount'] = {
                'count': student_count(timeslot_dict[timeslot]),
                'max_count': student_max_count(timeslot_dict[timeslot])
            }

            vitals['timeslots'].append(curTimeslot)

        dictOut["stats"].append(vitals)

        shirt_data = {
            "id": "shirtnum"
        }
        adminvitals_shirt = prog.getShirtInfo()
        shirt_data["sizes"] = adminvitals_shirt['shirt_sizes']
        shirt_data["types"] = adminvitals_shirt['shirt_types']
        shirt_data["data"] = adminvitals_shirt['shirts']
        dictOut["stats"].append(shirt_data)

        Q_categories = Q(program=prog)
        crmi = prog.classregmoduleinfo
        if crmi.open_class_registration:
            Q_categories |= Q(pk=prog.open_class_category.pk)
        #   Introduce a separate query to get valid categories, since the single query seemed to introduce duplicates
        program_categories = ClassCategories.objects.filter(
            Q_categories).distinct().values_list('id', flat=True)
        annotated_categories = ClassCategories.objects.filter(
            cls__parent_program=prog, cls__status__gte=0).annotate(
                num_subjects=Count('cls', distinct=True),
                num_sections=Count('cls__sections'),
                num_class_hours=Sum('cls__sections__duration')).order_by(
                    '-num_subjects').values('id', 'num_sections',
                                            'num_subjects', 'num_class_hours',
                                            'category').distinct()
        #   Convert Decimal values to float for serialization
        for i in range(len(annotated_categories)):
            annotated_categories[i]['num_class_hours'] = float(
                annotated_categories[i]['num_class_hours'])
        dictOut["stats"].append({
            "id":
            "categories",
            "data":
            filter(lambda x: x['id'] in program_categories,
                   annotated_categories)
        })

        ## Calculate the grade data:
        grades = [i for i in range(prog.grade_min, prog.grade_max + 1)]
        # We can't perfectly trust most_recent_profile, but it's good enough for stats
        students_grades = students['enrolled'].filter(
            registrationprofile__most_recent_profile=True)
        students_grades = students_grades.values_list(
            'registrationprofile__student_info__graduation_year')
        students_grades = students_grades.annotate(Count('id', distinct=True))
        grades_dict = {result[0]: result[1] for result in students_grades}
        grades_results = []
        for g in grades:
            year = ESPUser.YOGFromGrade(g, ESPUser.program_schoolyear(prog))
            grade_classes = classes.filter(status__gte=0,
                                           grade_min__lte=g,
                                           grade_max__gte=g)
            grade_sections = prog.sections().filter(
                status__gte=0, parent_class__in=grade_classes)
            grades_results.append({
                'grade':
                g,
                'num_subjects':
                grade_classes.count(),
                'num_sections':
                grade_sections.count(),
                'num_students':
                grades_dict[year] if year in grades_dict else 0
            })
        dictOut["stats"].append({"id": "grades", "data": grades_results})

        #   Add SplashInfo statistics if our program has them
        splashinfo_data = {}
        splashinfo_modules = filter(lambda x: isinstance(x, SplashInfoModule),
                                    prog.getModules('learn'))
        if len(splashinfo_modules) > 0:
            splashinfo_module = splashinfo_modules[0]
            tag_data = Tag.getProgramTag('splashinfo_choices', prog)
            if tag_data:
                splashinfo_choices = json.loads(tag_data)
            else:
                splashinfo_choices = {
                    'lunchsat': SplashInfoForm.default_choices,
                    'lunchsun': SplashInfoForm.default_choices
                }
            for key in splashinfo_choices:
                counts = {}
                for item in splashinfo_choices[key]:
                    filter_kwargs = {'program': prog}
                    filter_kwargs[key] = item[0]
                    counts[item[1]] = SplashInfo.objects.filter(
                        **filter_kwargs).distinct().count()
                splashinfo_data[key] = counts
            splashinfo_data['siblings'] = {
                'yes':
                SplashInfo.objects.filter(
                    program=prog, siblingdiscount=True).distinct().count(),
                'no':
                SplashInfo.objects.filter(program=prog).exclude(
                    siblingdiscount=True).distinct().count()
            }
            dictOut["stats"].append({
                "id": "splashinfo",
                "data": splashinfo_data
            })

        #   Add accounting stats
        pac = ProgramAccountingController(prog)
        (num_payments, total_payment) = pac.payments_summary()
        accounting_data = {
            'num_payments': num_payments,
            # We need to convert to a float in order for json to serialize it.
            # Since we're not doing any computation client-side with these
            # numbers, this doesn't cause accuracy issues.  If the
            # total_payment is None, just coerce it to zero for display
            # purposes.
            'total_payments': float(total_payment or 0),
        }
        dictOut["stats"].append({"id": "accounting", "data": accounting_data})

        return dictOut
    def initialize(self):
        """ Gather all of the information needed to run the lottery assignment.
            This includes:
            -   Students' interest (priority and interested bits)
            -   Class schedules and capacities
            -   Timeslots (incl. lunch periods for each day)
        """

        self.enroll_orig = numpy.zeros((self.num_students, self.num_sections, self.num_timeslots), dtype=numpy.bool)
        self.enroll_final = numpy.zeros((self.num_students, self.num_sections, self.num_timeslots), dtype=numpy.bool)
        self.request = numpy.zeros((self.num_students, self.num_sections, self.num_timeslots), dtype=numpy.bool)
        self.waitlist = [numpy.zeros((self.num_students, self.num_sections, self.num_timeslots), dtype=numpy.bool) for i in range(self.priority_limit+1)]
        self.section_schedules = numpy.zeros((self.num_sections, self.num_timeslots), dtype=numpy.bool)
        self.section_capacities = numpy.zeros((self.num_sections,), dtype=numpy.int32)
        self.section_scores = numpy.zeros((self.num_sections,), dtype=numpy.int32)
        self.same_subject = numpy.zeros((self.num_sections, self.num_sections), dtype=numpy.bool)
        self.section_conflict = numpy.zeros((self.num_sections, self.num_sections), dtype=numpy.bool) # is this a section that takes place in the same timeblock

        #   Get student, section, timeslot IDs and prepare lookup table
        (self.student_ids, self.student_indices) = self.get_ids_and_indices(self.students)
        (self.section_ids, self.section_indices) = self.get_ids_and_indices(self.sections)
        (self.timeslot_ids, self.timeslot_indices) = self.get_ids_and_indices(self.timeslots)
        self.parent_classes = numpy.array(self.sections.values_list('parent_class__id', flat=True))

        self.student_not_checked_in = numpy.zeros((self.num_students,), dtype=numpy.bool)
        self.student_not_checked_in[self.student_indices[self.students_not_checked_in]] = True

        #   Get IDs of timeslots allocated to lunch by day
        #   (note: requires that this is constant across days)
        self.lunch_schedule = numpy.zeros((self.num_timeslots,))
        lunch_timeslots = Event.objects.filter(meeting_times__parent_class__parent_program=self.program, meeting_times__parent_class__category__category='Lunch').order_by('start').distinct()
        #   Note: this code should not be necessary once lunch-constraints branch is merged (provides Program.dates())
        dates = []
        for ts in self.timeslots:
            ts_day = date(ts.start.year, ts.start.month, ts.start.day)
            if ts_day not in dates:
                dates.append(ts_day)
        lunch_by_day = [[] for x in dates]
        ts_count = 0
        for ts in lunch_timeslots:
            d = date(ts.start.year, ts.start.month, ts.start.day)
            lunch_by_day[dates.index(d)].append(ts.id)
            self.lunch_schedule[self.timeslot_indices[ts.id]] = True

        for i in range(len(lunch_by_day)):
            if len(lunch_by_day[i]) > ts_count:
                ts_count = len(lunch_by_day[i])
        self.lunch_timeslots = numpy.zeros((len(lunch_by_day), ts_count), dtype=numpy.int32)
        for i in range(len(lunch_by_day)):
            self.lunch_timeslots[i, :len(lunch_by_day[i])] = numpy.array(lunch_by_day[i])

        #   Populate old enrollment matrix
        enroll_regs = StudentRegistration.objects.filter(self.Q_EN).values_list('user__id', 'section__id', 'section__meeting_times__id').distinct()
        era = numpy.array(enroll_regs, dtype=numpy.uint32)
        try:
            self.enroll_orig[self.student_indices[era[:, 0]], self.section_indices[era[:, 1]], self.timeslot_indices[era[:, 2]]] = True
        except IndexError:
            pass

        self.student_not_checked_in[numpy.transpose(numpy.nonzero(True-self.enroll_orig.any(axis=(1,2))))] = True

        #   Populate old enrollment matrix
        request_regs = StudentRegistration.objects.filter(self.Q_REQ).values_list('user__id', 'section__id', 'section__meeting_times__id').distinct()
        rra = numpy.array(request_regs, dtype=numpy.uint32)
        try:
            self.request[self.student_indices[rra[:, 0]], self.section_indices[rra[:, 1]], self.timeslot_indices[rra[:, 2]]] = True
        except IndexError:
            pass

        #   Populate waitlist matrix
        waitlist_regs = [StudentRegistration.objects.filter(self.Q_WAIT[i]).values_list('user__id', 'section__id', 'section__meeting_times__id').distinct() for i in range(self.priority_limit+1)]
        wra = [numpy.array(waitlist_regs[i], dtype=numpy.uint32) for i in range(self.priority_limit+1)]
        self.waitlist[0][:,:,:] = True
        for i in range(1, self.priority_limit+1):
            try:
                self.waitlist[i][self.student_indices[wra[i][:, 0]], self.section_indices[wra[i][:, 1]], self.timeslot_indices[wra[i][:, 2]]] = True
                self.waitlist[0][self.student_indices[wra[i][:, 0]], self.section_indices[wra[i][:, 1]], self.timeslot_indices[wra[i][:, 2]]] = False
            except IndexError:
                pass

        #   Populate section schedule
        section_times = numpy.array(self.sections.values_list('id', 'meeting_times__id'))
        self.section_schedules[self.section_indices[section_times[:, 0]], self.timeslot_indices[section_times[:, 1]]] = True

        #   Populate section overlap matrix
        for i in range(self.num_sections):
            group_ids = numpy.nonzero(self.parent_classes == self.parent_classes[i])[0]
            self.same_subject[numpy.meshgrid(group_ids, group_ids)] = True

            sec_times = numpy.transpose(numpy.nonzero(self.section_schedules[i, :]))
            for [ts_ind,] in sec_times:
                self.section_conflict[numpy.transpose(numpy.nonzero(self.section_schedules[:, ts_ind])), i] = True
                self.section_conflict[i, numpy.transpose(numpy.nonzero(self.section_schedules[:, ts_ind]))] = True
            self.section_conflict[i, i] = False

        #   Populate section grade limits
        self.section_grade_min = numpy.array(self.sections.values_list('parent_class__grade_min', flat=True), dtype=numpy.uint32)
        self.section_grade_max = numpy.array(self.sections.values_list('parent_class__grade_max', flat=True), dtype=numpy.uint32)

        #   Populate student grades; grade will be assumed to be 0 if not entered on profile
        self.student_grades = numpy.zeros((self.num_students,))
        gradyear_pairs = numpy.array(RegistrationProfile.objects.filter(user__id__in=list(self.student_ids), most_recent_profile=True, student_info__graduation_year__isnull=False).values_list('user__id', 'student_info__graduation_year'), dtype=numpy.uint32)
        self.student_grades[self.student_indices[gradyear_pairs[:, 0]]] = 12 + ESPUser.program_schoolyear(self.program) - gradyear_pairs[:, 1]

        #   Find section capacities (TODO: convert to single query)
        for sec in self.sections:
            sec_ind = self.section_indices[sec.id]
            self.section_capacities[sec_ind] = sec.capacity - sec.num_students()
            self.section_capacities[sec_ind] += numpy.count_nonzero((self.enroll_orig[:, sec_ind, self.section_schedules[sec_ind,:]].any(axis=1) * self.request[:,:,self.section_schedules[sec_ind,:]].any(axis=(1,2)))) # number enrolled but want to switch out
            self.enroll_final[numpy.transpose(numpy.nonzero((self.enroll_orig[:, sec_ind, self.section_schedules[sec_ind,:]].any(axis=1) * (True - self.request[:,:,self.section_schedules[sec_ind,:]].any(axis=(1,2)))))), sec_ind, self.section_schedules[sec_ind,:]] = True
            self.section_scores[sec_ind] = -self.section_capacities[sec_ind]
            self.section_scores[sec_ind] += numpy.count_nonzero(self.request[:, sec_ind, :].any(axis=1)) # number who want to switch in
        self.section_capacities_orig = numpy.copy(self.section_capacities)
        self.section_scores_orig = numpy.copy(self.section_scores)
        self.enroll_final_orig = numpy.copy(self.enroll_final)
        self.clear_assignments()
Beispiel #7
0
    def initialize(self):
        """ Gather all of the information needed to run the lottery assignment.
            This includes:
            -   Students' interest (priority and interested bits)
            -   Class schedules and capacities
            -   Timeslots (incl. lunch periods for each day)
        """

        self.interest = numpy.zeros((self.num_students, self.num_sections), dtype=numpy.bool)
        self.priority = [numpy.zeros((self.num_students, self.num_sections), dtype=numpy.bool) for i in range(self.effective_priority_limit+1)]
        self.ranks = 10*numpy.ones((self.num_students, self.num_sections), dtype=numpy.int32)
        self.section_schedules = numpy.zeros((self.num_sections, self.num_timeslots), dtype=numpy.bool)
        self.section_start_schedules = numpy.zeros((self.num_sections, self.num_timeslots), dtype=numpy.bool)
        self.section_capacities = numpy.zeros((self.num_sections,), dtype=numpy.uint32)
        self.section_overlap = numpy.zeros((self.num_sections, self.num_sections), dtype=numpy.bool)

        # One array to keep track of the utility of each student
        # (defined as hours of interested class + 1.5*hours of priority classes)
        # and the other arrary to keep track of student weigths (defined as # of classes signed up for)
        self.student_utility_weights = numpy.zeros((self.num_students, ), dtype=numpy.float)
        self.student_utilities = numpy.zeros((self.num_students, ), dtype=numpy.float)

        #   Get student, section, timeslot IDs and prepare lookup table
        (self.student_ids, self.student_indices) = self.get_ids_and_indices(self.lotteried_students)
        (self.section_ids, self.section_indices) = self.get_ids_and_indices(self.sections)
        (self.timeslot_ids, self.timeslot_indices) = self.get_ids_and_indices(self.timeslots)
        self.parent_classes = numpy.array(self.sections.values_list('parent_class__id', flat=True))

        #   Get IDs of timeslots allocated to lunch by day
        #   (note: requires that this is constant across days)
        self.lunch_schedule = numpy.zeros((self.num_timeslots,))
        lunch_timeslots = Event.objects.filter(meeting_times__parent_class__parent_program=self.program, meeting_times__parent_class__category__category='Lunch').order_by('start').distinct()
        #   Note: this code should not be necessary once lunch-constraints branch is merged (provides Program.dates())
        dates = []
        for ts in self.timeslots:
            ts_day = date(ts.start.year, ts.start.month, ts.start.day)
            if ts_day not in dates:
                dates.append(ts_day)
        lunch_by_day = [[] for x in dates]
        ts_count = 0
        for ts in lunch_timeslots:
            d = date(ts.start.year, ts.start.month, ts.start.day)
            lunch_by_day[dates.index(d)].append(ts.id)
            self.lunch_schedule[self.timeslot_indices[ts.id]] = True
        self.lunch_timeslots = numpy.array(lunch_by_day)

        #   Populate interest matrix; this uses both the StudentRegistrations (which apply to a particular section) and StudentSubjectIntegests (which apply to all sections of the class).  If one does not exist, ignore it.  Be careful to only return SRs and SSIs for accepted sections of accepted classes; this might matter for SSIs where only some sections of the class are accepted.
        interest_regs_sr = StudentRegistration.valid_objects().filter(section__parent_class__parent_program=self.program, section__status__gt=0, section__parent_class__status__gt=0, section__registration_status=0, section__meeting_times__isnull=False, relationship__name='Interested').values_list('user__id', 'section__id').distinct()
        interest_regs_ssi = StudentSubjectInterest.valid_objects().filter(subject__parent_program=self.program, subject__status__gt=0, subject__sections__status__gt=0, subject__sections__registration_status=0, subject__sections__meeting_times__isnull=False).values_list('user__id', 'subject__sections__id').distinct()
        self.put_prefs_in_array(interest_regs_sr, self.interest)
        self.put_prefs_in_array(interest_regs_ssi, self.interest)

        #   Populate priority matrix
        priority_regs = [StudentRegistration.valid_objects().filter(section__parent_class__parent_program=self.program, relationship__name='Priority/%s'%i).values_list('user__id', 'section__id').distinct() for i in range(self.real_priority_limit+1)]
        if self.grade_range_exceptions:
            priority_regs.append(StudentRegistration.valid_objects().filter(section__parent_class__parent_program=self.program, relationship__name='GradeRangeException').values_list('user__id', 'section__id').distinct())
        for i in range(1,self.effective_priority_limit+1):
            self.put_prefs_in_array(priority_regs[i], self.priority[i])
        if self.options['use_student_apps']:
            for i in range(1,self.effective_priority_limit+1):
                for (student_id,section_id) in priority_regs[i]:
                    self.ranks[self.student_indices[student_id],self.section_indices[section_id]] = ESPUser.getRankInClass(student_id,self.parent_classes[self.section_indices[section_id]])
            for (student_id,section_id) in interest_regs_sr + interest_regs_ssi:
                self.ranks[self.student_indices[student_id],self.section_indices[section_id]] = ESPUser.getRankInClass(student_id,self.parent_classes[self.section_indices[section_id]])


        #   Set student utility weights. Counts number of classes that students selected. Used only for computing the overall_utility stat
        self.student_utility_weights = numpy.sum(self.interest.astype(float), 1) + sum([numpy.sum(self.priority[i].astype(float), 1) for i in range(1,self.effective_priority_limit+1)])

        #   Populate section schedule
        section_times = numpy.array(self.sections.values_list('id', 'meeting_times__id'))
        start_times = numpy.array(self.sections.annotate(start_time=Min('meeting_times')).values_list('id','start_time'))
        self.section_schedules[self.section_indices[section_times[:, 0]], self.timeslot_indices[section_times[:, 1]]] = True
        self.section_start_schedules[self.section_indices[start_times[:, 0]], self.timeslot_indices[start_times[:, 1]]] = True

        #   Populate section overlap matrix
        for i in range(self.num_sections):
            group_ids = numpy.nonzero(self.parent_classes == self.parent_classes[i])[0]
            self.section_overlap[numpy.meshgrid(group_ids, group_ids)] = True

        #   Populate section grade limits
        self.section_grade_min = numpy.array(self.sections.values_list('parent_class__grade_min', flat=True), dtype=numpy.uint32)
        self.section_grade_max = numpy.array(self.sections.values_list('parent_class__grade_max', flat=True), dtype=numpy.uint32)

        #   Populate student grades; grade will be assumed to be 0 if not entered on profile
        self.student_grades = numpy.zeros((self.num_students,))
        gradyear_pairs = numpy.array(RegistrationProfile.objects.filter(user__id__in=list(self.student_ids), most_recent_profile=True, student_info__graduation_year__isnull=False).values_list('user__id', 'student_info__graduation_year'), dtype=numpy.uint32)
        self.student_grades[self.student_indices[gradyear_pairs[:, 0]]] = 12 + ESPUser.program_schoolyear(self.program) - gradyear_pairs[:, 1]

        #   Find section capacities (TODO: convert to single query)
        for sec in self.sections:
            self.section_capacities[self.section_indices[sec.id]] = sec.capacity

        # Populate section lengths (hours)
        self.section_lengths = numpy.array([x.nonzero()[0].size for x in self.section_schedules])

        if self.options['fill_low_priorities']:
            #   Compute who has a priority when.  Includes lower priorities, since this is used for places where we check not clobbering priorities.
            self.has_priority = [numpy.zeros((self.num_students, self.num_timeslots), dtype=numpy.bool) for i in range(self.effective_priority_limit+1)]
            for i in range(1,self.effective_priority_limit+1):
                priority_at_least_i = reduce(operator.or_,[self.priority[j] for j in range(i,self.effective_priority_limit+1)])
                numpy.dot(priority_at_least_i,self.section_schedules,out=self.has_priority[i])

            self.sections_at_same_time = numpy.dot(self.section_schedules, numpy.transpose(self.section_schedules))

            #   And the same, overlappingly.
            self.has_overlapping_priority = [numpy.zeros((self.num_students, self.num_timeslots), dtype=numpy.bool) for i in range(self.effective_priority_limit+1)]
            for i in range(1,self.effective_priority_limit+1):
                priority_at_least_i = reduce(operator.or_,[self.priority[j] for j in range(i,self.effective_priority_limit+1)])
                numpy.dot(numpy.dot(priority_at_least_i,self.sections_at_same_time),self.section_schedules,out=self.has_overlapping_priority[i])

            #   Fill in preferences for students who haven't ranked them.  In particular, if a student has ranked some level of class in a timeblock (i.e. they plan to be at Splash that timeblock), but has not ranked any priority/n or lower-priority classes overlapping it, add a random class from their interesteds.

            for i in range(1,self.real_priority_limit+1): #Use self.real_priority_limit since we don't want to give people free grade range exceptions!
                should_fill = numpy.transpose(numpy.nonzero(self.has_priority[1]&~self.has_overlapping_priority[i]))
                if len(should_fill):
                    for student, timeslot in should_fill:
                        # student is interested, and class starts in this timeslot, and class does not overlap any lower or equal priorities
                        possible_classes = numpy.nonzero(self.interest[student] & self.section_start_schedules[:,timeslot] & ~numpy.dot(self.section_schedules, numpy.transpose(self.has_priority[i][student])))[0]
                        if len(possible_classes):
                            choice = numpy.random.choice(possible_classes)
                            self.priority[i][student,choice]=True
    def onsite_create(self, request, tl, one, two, module, extra, prog):
        if request.method == 'POST':
            form = OnSiteRegForm(request.POST)

            if form.is_valid():
                new_data = form.cleaned_data
                username = ESPUser.get_unused_username(new_data['first_name'], new_data['last_name'])
                new_user = ESPUser.objects.create_user(username = username,
                                first_name = new_data['first_name'],
                                last_name  = new_data['last_name'],
                                email      = new_data['email'])

                self.student = new_user

                regProf = RegistrationProfile.getLastForProgram(new_user,
                                                                self.program)
                contact_user = ContactInfo(first_name = new_user.first_name,
                                           last_name  = new_user.last_name,
                                           e_mail     = new_user.email,
                                           user       = new_user)
                contact_user.save()
                regProf.contact_user = contact_user

                student_info = StudentInfo(user = new_user, graduation_year = ESPUser.YOGFromGrade(new_data['grade'], ESPUser.program_schoolyear(self.program)))

                try:
                    if isinstance(new_data['k12school'], K12School):
                        student_info.k12school = new_data['k12school']
                    else:
                        if isinstance(new_data['k12school'], int):
                            student_info.k12school = K12School.objects.get(id=int(new_data['k12school']))
                        else:
                            student_info.k12school = K12School.objects.filter(name__icontains=new_data['k12school'])[0]
                except:
                    student_info.k12school = None
                student_info.school = new_data['school'] if not student_info.k12school else student_info.k12school.name

                student_info.save()
                regProf.student_info = student_info

                regProf.save()

                if new_data['paid']:
                    self.createBit('paid')
                    self.updatePaid(True)
                else:
                    self.updatePaid(False)

                self.createBit('Attended')

                if new_data['medical']:
                    self.createBit('Med')

                if new_data['liability']:
                    self.createBit('Liab')

                self.createBit('OnSite')


                new_user.groups.add(Group.objects.get(name="Student"))

                new_user.recoverPassword()

                return render_to_response(self.baseDir()+'reg_success.html', request, {
                    'student': new_user,
                    'retUrl': '/onsite/%s/classchange_grid?student_id=%s' % (self.program.getUrlBase(), new_user.id)
                    })

        else:
            form = OnSiteRegForm()

        return render_to_response(self.baseDir()+'reg_info.html', request, {'form':form})
    def stats(prog):
        # Create a dictionary to assemble the output
        dictOut = { "stats": [] }

        classes = prog.classes().select_related()
        vitals = {'id': 'vitals'}

        class_num_list = []
        class_num_list.append(("Total # of Classes", classes.distinct().count()))
        class_num_list.append(("Total # of Class Sections", prog.sections().select_related().distinct().count()))
        class_num_list.append(("Total # of Lunch Classes", classes.filter(category__category = "Lunch").filter(status=10).distinct().count()))
        class_num_list.append(("Total # of Classes <span style='color: #00C;'>Unreviewed</span>", classes.filter(status=0).distinct().count()))
        class_num_list.append(("Total # of Classes <span style='color: #0C0;'>Accepted</span>", classes.filter(status=10).distinct().count()))
        class_num_list.append(("Total # of Classes <span style='color: #C00;'>Rejected</span>", classes.filter(status=-10).distinct().count()))
        class_num_list.append(("Total # of Classes <span style='color: #990;'>Cancelled</span>", classes.filter(status=-20).distinct().count()))
        for ft in ClassFlagType.get_flag_types(prog):
            class_num_list.append(('Total # of Classes with the "%s" flag' % ft.name, classes.filter(flags__flag_type=ft).distinct().count()))
        vitals['classnum'] = class_num_list

        #   Display pretty labels for teacher and student numbers
        teacher_labels_dict = {}
        for module in prog.getModules():
            teacher_labels_dict.update(module.teacherDesc())
        vitals['teachernum'] = []

        ## Ew, queries in a for loop...
        ## Not much to be done about it, though;
        ## the loop is iterating over a list of independent queries and running each.
        teachers = prog.teachers()
        for key in teachers.keys():
            if key in teacher_labels_dict:
                vitals['teachernum'].append((teacher_labels_dict[key],         ## Unfortunately,
teachers[key].filter(is_active = True).distinct().count()))
            else:
                vitals['teachernum'].append((key, teachers[key].filter(is_active = True).distinct().count()))

        student_labels_dict = {}
        for module in prog.getModules():
            student_labels_dict.update(module.studentDesc())
        vitals['studentnum'] = []

        ## Ew, another set of queries in a for loop...
        ## Same justification, though.
        students = prog.students()
        for key in students.keys():
            if key in student_labels_dict:
                vitals['studentnum'].append((student_labels_dict[key], students[key].filter(is_active = True).distinct().count()))
            else:
                vitals['studentnum'].append((key, students[key].filter(is_active = True).distinct().count()))

        timeslots = prog.getTimeSlots()
        vitals['timeslots'] = []


        shours = 0.0
        chours = 0.0
        crhours = 0.0
        ## Write this as a 'for' loop because PostgreSQL can't do it in
        ## one go without a subquery or duplicated logic, and Django
        ## doesn't have enough power to expose either approach directly.
        ## At least there aren't any queries in the for loop...
        ## (In MySQL, this could I believe be done with a minimally-painful extra() clause.)
        ## Also, since we're iterating over a big data set, use .values()
        ## minimize the number of objects that we're creating.
        ## One dict and two Decimals per row, as opposed to
        ## an Object per field and all kinds of stuff...
        for cls in prog.classes().exclude(category__category='Lunch').annotate(num_sections=Count('sections'), subject_duration=Sum('sections__duration'), subject_students=Sum('sections__enrolled_students')).values('num_sections', 'subject_duration', 'subject_students', 'class_size_max'):
            if cls['subject_duration']:
                chours += float(cls['subject_duration'])
                shours += float(cls['subject_duration']) * (float(cls['class_size_max']) if cls['class_size_max'] else 0)
                crhours += float(cls['subject_duration']) * float(cls['subject_students']) / float(cls['num_sections'])
        vitals["hournum"] = []
        vitals["hournum"].append(("Total # of Class-Hours", chours))
        vitals["hournum"].append(("Total # of Class-Student-Hours (capacity)", shours))
        vitals["hournum"].append(("Total # of Class-Student-Hours (registered)", crhours))


        ## Prefetch enough data that get_meeting_times() and num_students() don't have to hit the db
        curclasses = ClassSection.prefetch_catalog_data(
            ClassSection.objects
            .filter(parent_class__parent_program=prog)
            .select_related('parent_class', 'parent_class__category'))

        ## Is it really faster to do this logic in Python?
        ## It'd be even faster to just write a raw SQL query to do it.
        ## But this is probably good enough.
        timeslot_dict = defaultdict(list)
        timeslot_set = set(timeslots)
        for section in curclasses:
            for timeslot in set.intersection(timeslot_set, section.get_meeting_times()):
                timeslot_dict[timeslot].append(section)

        for timeslot in timeslots:
            curTimeslot = {'slotname': timeslot.short_description}

            curTimeslot['classcount'] = len(timeslot_dict[timeslot])

            def student_count(clslist):
                lst = [0] + [x.num_students() for x in clslist if x.category.category != 'Lunch']
                return reduce(operator.add, lst)

            def student_max_count(clslist):
                lst = [0] + [x.capacity for x in clslist if x.category.category != 'Lunch']
                return reduce(operator.add, lst)

            curTimeslot['studentcount'] = {
                'count': student_count(timeslot_dict[timeslot]),
                'max_count': student_max_count(timeslot_dict[timeslot])
                }

            vitals['timeslots'].append(curTimeslot)

        dictOut["stats"].append(vitals)

        shirt_data = {"id": "shirtnum"};
        adminvitals_shirt = prog.getShirtInfo()
        shirt_data["sizes"] = adminvitals_shirt['shirt_sizes'];
        shirt_data["types"] = adminvitals_shirt['shirt_types'];
        shirt_data["data"] = adminvitals_shirt['shirts'];
        dictOut["stats"].append(shirt_data);

        Q_categories = Q(program=prog)
        crmi = prog.classregmoduleinfo
        if crmi.open_class_registration:
            Q_categories |= Q(pk=prog.open_class_category.pk)
        #   Introduce a separate query to get valid categories, since the single query seemed to introduce duplicates
        program_categories = ClassCategories.objects.filter(Q_categories).distinct().values_list('id', flat=True)
        annotated_categories = ClassCategories.objects.filter(cls__parent_program=prog, cls__status__gte=0).annotate(num_subjects=Count('cls', distinct=True), num_sections=Count('cls__sections'), num_class_hours=Sum('cls__sections__duration')).order_by('-num_subjects').values('id', 'num_sections', 'num_subjects', 'num_class_hours', 'category').distinct()
        #   Convert Decimal values to float for serialization
        for i in range(len(annotated_categories)):
            annotated_categories[i]['num_class_hours'] = float(annotated_categories[i]['num_class_hours'])
        dictOut["stats"].append({"id": "categories", "data": filter(lambda x: x['id'] in program_categories, annotated_categories)})

        ## Calculate the grade data:
        grades = [i for i in range(prog.grade_min, prog.grade_max+1)]
        # We can't perfectly trust most_recent_profile, but it's good enough for stats
        students_grades = students['enrolled'].filter(registrationprofile__most_recent_profile=True)
        students_grades = students_grades.values_list('registrationprofile__student_info__graduation_year')
        students_grades = students_grades.annotate(Count('id', distinct=True))
        grades_dict = {result[0]: result[1] for result in students_grades}
        grades_results = []
        for g in grades:
            year = ESPUser.YOGFromGrade(g, ESPUser.program_schoolyear(prog))
            grade_classes = classes.filter(status__gte=0, grade_min__lte=g, grade_max__gte=g)
            grade_sections = prog.sections().filter(status__gte=0, parent_class__in=grade_classes)
            grades_results.append({'grade': g, 'num_subjects': grade_classes.count(),
                                   'num_sections': grade_sections.count(),
                                   'num_students': grades_dict[year] if year in grades_dict else 0})
        dictOut["stats"].append({"id": "grades", "data": grades_results})

        #   Add SplashInfo statistics if our program has them
        splashinfo_data = {}
        splashinfo_modules = filter(lambda x: isinstance(x, SplashInfoModule), prog.getModules('learn'))
        if len(splashinfo_modules) > 0:
            splashinfo_module = splashinfo_modules[0]
            tag_data = Tag.getProgramTag('splashinfo_choices', prog)
            if tag_data:
                splashinfo_choices = json.loads(tag_data)
            else:
                splashinfo_choices = {'lunchsat': SplashInfoForm.default_choices, 'lunchsun': SplashInfoForm.default_choices}
            for key in splashinfo_choices:
                counts = {}
                for item in splashinfo_choices[key]:
                    filter_kwargs = {'program': prog}
                    filter_kwargs[key] = item[0]
                    counts[item[1]] = SplashInfo.objects.filter(**filter_kwargs).distinct().count()
                splashinfo_data[key] = counts
            splashinfo_data['siblings'] = {
                'yes': SplashInfo.objects.filter(program=prog, siblingdiscount=True).distinct().count(),
                'no':  SplashInfo.objects.filter(program=prog).exclude(siblingdiscount=True).distinct().count()
            }
            dictOut["stats"].append({"id": "splashinfo", "data": splashinfo_data})

        #   Add accounting stats
        pac = ProgramAccountingController(prog)
        (num_payments, total_payment) = pac.payments_summary()
        accounting_data = {
            'num_payments': num_payments,
            # We need to convert to a float in order for json to serialize it.
            # Since we're not doing any computation client-side with these
            # numbers, this doesn't cause accuracy issues.  If the
            # total_payment is None, just coerce it to zero for display
            # purposes.
            'total_payments': float(total_payment or 0),
        }
        dictOut["stats"].append({"id": "accounting", "data": accounting_data})

        return dictOut