org.opentestsystem.delivery.testadmin.scheduling.SchedulerHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.opentestsystem.delivery.testadmin.scheduling.SchedulerHelper.java

Source

/*
Educational Online Test Delivery System Copyright (c) 2013 American Institutes for Research
    
Distributed under the AIR Open Source License, Version 1.0 See accompanying file AIR-License-1_0.txt or at
http://www.smarterapp.org/documents/American_Institutes_for_Research_Open_Source_Software_License.pdf
 */

package org.opentestsystem.delivery.testadmin.scheduling;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.opentestsystem.delivery.testadmin.domain.AccessibilityEquipment;
import org.opentestsystem.delivery.testadmin.domain.Affinity;
import org.opentestsystem.delivery.testadmin.domain.Affinity.AffinityType;
import org.opentestsystem.delivery.testadmin.domain.Facility;
import org.opentestsystem.delivery.testadmin.domain.FacilityAvailability;
import org.opentestsystem.delivery.testadmin.domain.Proctor;
import org.opentestsystem.delivery.testadmin.domain.ProctorRole;
import org.opentestsystem.delivery.testadmin.domain.schedule.Schedule;
import org.opentestsystem.delivery.testadmin.domain.schedule.ScheduleCreationInfo;
import org.opentestsystem.delivery.testadmin.domain.schedule.ScheduleTestStatus;
import org.opentestsystem.delivery.testadmin.domain.schedule.ScheduledSeat;
import org.opentestsystem.delivery.testadmin.domain.schedule.ScheduledStudent;
import org.opentestsystem.delivery.testadmin.domain.schedule.ScheduledTimeSlot;
import org.opentestsystem.delivery.testadmin.domain.schedule.SchedulerValidationError;
import org.opentestsystem.delivery.testadmin.domain.schedule.SchedulerValidationError.ErrorType;
import org.opentestsystem.delivery.testadmin.domain.search.AccessibilityEquipmentSearchRequest;
import org.opentestsystem.delivery.testadmin.persistence.ProctorRepository;
import org.opentestsystem.delivery.testadmin.persistence.ProctorRoleRepository;
import org.opentestsystem.delivery.testadmin.service.AccessibilityEquipmentService;
import org.opentestsystem.delivery.testadmin.service.FacilityAvailabilityService;
import org.opentestsystem.delivery.testadmin.service.FacilityService;
import org.opentestsystem.delivery.testreg.domain.Assessment;
import org.opentestsystem.delivery.testreg.domain.Assessment.TestWindow;
import org.opentestsystem.delivery.testreg.domain.EligibleStudent;
import org.opentestsystem.delivery.testreg.domain.FormatType;
import org.opentestsystem.delivery.testreg.domain.Sb11Entity;
import org.opentestsystem.delivery.testreg.domain.Student.GradeLevel;
import org.opentestsystem.delivery.testreg.domain.StudentGroup;
import org.opentestsystem.delivery.testreg.persistence.EligibleStudentRepository;
import org.opentestsystem.delivery.testreg.service.StudentGroupService;
import org.opentestsystem.delivery.testreg.service.TestRegPersister;
import org.opentestsystem.shared.search.domain.SearchResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * Helper class that contains most of the scheduling logic
 */
@Component
public class SchedulerHelper {

    private static final Logger LOGGER = LoggerFactory.getLogger(SchedulerHelper.class);

    @Autowired
    private FacilityService facilityService;

    @Autowired
    private FacilityAvailabilityService facilityAvailabilityService;

    @Autowired
    private EligibleStudentRepository eligStudentRepository;

    @Autowired
    private ProctorRoleRepository proctorRoleRepository;

    @Autowired
    private StudentGroupService studentGroupService;

    @Autowired
    private TestRegPersister testRegPersister;

    @Autowired
    private ProctorRepository proctorRepository;

    @Autowired
    private ScheduleTestStatusCreator scheduleTestStatusCreator;

    @Autowired
    private AccessibilityEquipmentService accessibilityEquipmentService;

    private static final String ACCESS_EQUIP_ERROR_TEMPLATE = "Student %1$s was not allocated to required equipment %2$s for assessment %3$s";
    private static final String NOT_SCHEDULED_FOR_ASSESSMENT_ERROR_TEMPLATE = "Student %1$s was not scheduled for assessment %2$s";
    private static final String NO_PROCTOR_ERROR_TEMPLATE = "No proctor found for the test slot that starts on %1$tD %1$tR and ends on %2$tD %2$tR";

    /**
     * Gets Facility and FacilityAvailability data for the given institution and merges them together using the
     * FacilityData class
     * 
     * @param institutionId
     * @return
     */
    public Map<String, FacilityData> findFacilityData(final String institutionId) {
        final Map<String, FacilityData> facilityDatas = new HashMap<String, FacilityData>();
        // get Facilities for Institution
        final List<Facility> facilities = this.facilityService.getFacilities(institutionId);

        for (final Facility facility : facilities) {
            final FacilityData facilityData = new FacilityData();
            facilityData.setFacilityId(facility.getId());
            facilityData.setFacility(facility);
            // get availabilities for facility and institution
            final List<FacilityAvailability> availabilities = this.facilityAvailabilityService
                    .getAvailabilities(facility.getId(), facility.getInstitutionIdentifier());
            facilityData.setAvailabilities(availabilities);
            facilityDatas.put(facility.getId(), facilityData);
        }

        return facilityDatas;
    }

    /**
     * Finds all Students associated to the given institution
     * 
     * @param institutionId
     * @return
     */
    public List<EligibleStudent> findStudents(final String institutionId) {

        // find all Students for institution

        return this.eligStudentRepository.findByStudentInstitutionEntityMongoId(institutionId);

    }

    /**
     * Interrogates a List of EligibleStudents to find a unique set of Assessments for all of the Students eligible.
     * 
     * @param eligibleStudents
     * @param startDate
     * @param endDate
     * @return
     */
    public List<Assessment> findAssessments(final List<EligibleStudent> eligibleStudents, final DateTime startDate,
            final DateTime endDate, final List<String> tenantIdsForAssessment) {

        // the assessments to schedule are the assessments with a test window at least partially within the schedule
        // dates
        // taken from the EligibleStudents

        // use a set to avoid duplicate Assessments

        // IMPORTANT!!!!!!!!!
        // This code will add one day to the end date of the schedule and each of the test window end dates
        // because the Joda Interval makes the end instant EXCLUSIVE
        // We need to ensure that the interval takes into account that the end dates are part of the interval.
        // Since all of our schedule dates and test windows use midnight as the time, an end date of 06-02-2014 00:00:00
        // will turn into 06-03-2014 00:00:00 and thus the entire day of 06-02 is now part of the interval
        // !!!!!!!!!!!!!!!!!!

        final Interval scheduleInterval = new Interval(startDate, endDate.plusDays(1));
        Interval testWindowInterval = null;
        final Set<Assessment> schedulableAssessments = new HashSet<Assessment>();
        for (final EligibleStudent eligStudent : eligibleStudents) {
            for (final Assessment assess : eligStudent.getAssessments()) {
                for (final TestWindow testWindow : assess.getTestWindow()) {
                    testWindowInterval = new Interval(testWindow.getBeginWindow(),
                            testWindow.getEndWindow().plusDays(1));

                    if (scheduleInterval.overlaps(testWindowInterval)
                            && tenantIdsForAssessment.contains(assess.getTenantId())) {
                        schedulableAssessments.add(assess);
                        break;
                    }
                }
            }
        }

        return new ArrayList<Assessment>(schedulableAssessments);
    }

    /**
     * Finds Proctors that are able to Proctor the List of Assessments using the Proctor Role Associations
     * 
     * @param assessments
     * @return
     */
    public List<Proctor> findProctors(final List<ProctorRole> proctorRoles, final String associatedEntityId) {
        // Create new query on ProctorRepository to find Proctors that have a user/role association with the roles
        // determined from previous step

        return this.proctorRepository.findByAssociatedRolesAndEntity(proctorRoles, associatedEntityId);
    }

    private List<ProctorRole> findProctorRoles(final List<Assessment> assessments) {
        // find all Proctors that can Proctor assessment types found
        // get unique assessment types from assessments List above

        final Set<String> uniqueTypes = new HashSet<String>();

        for (final Assessment assess : assessments) {
            uniqueTypes.add(assess.getType().toUpperCase());
        }

        // Create new query on ProctorRoleRepository to get the Roles that can administer the assessment types

        final List<ProctorRole> proctorRoles = this.proctorRoleRepository
                .findByAssessmentTypesIn(new ArrayList<String>(uniqueTypes));
        return proctorRoles;
    }

    /**
     * Iterate through the timeslots for this schedule to see if any of them have any affinities. If they have
     * affinities, these time slots must be scheduled first.
     * Be aware that the actual scheduling modifies the Schedule argument passed into this function
     * 
     * @param scheduled
     */
    public void allocateTimeslotAffinities(final Schedule scheduled, final List<Assessment> assessments,
            final Map<String, ScheduledStudent> studentsScheduled,
            final HashMultimap<String, Assessment> gradesToAssessments, final boolean reschedule) {

        for (final ScheduledTimeSlot timeslot : scheduled.getOrderedTimeSlots(reschedule)) {
            // for each timeslot, if there is an affinity,
            // check if affinity with strict rule is scheduled
            // if yes then skip this timeslot
            // else check if timeslot has any unscheduled seat
            // if yes then allocate timeslot for that affinity
            // else skip timeslot
            if (timeslot.hasAffinities()) {
                for (final Affinity affinity : timeslot.getAffinities()) {
                    if (!timeslot.isStrictAffinityScheduled() && timeslot.hasUnscheduledSeat()) {
                        allocateTimeslotAffinity(assessments, timeslot, studentsScheduled, scheduled.getId(),
                                affinity, gradesToAssessments);
                    }
                }
            }
        }
    }

    /**
     * Allocate students who are eligible for the assessment to the timeslot
     * 
     * @param scheduledTimeSlot
     * @param assessment
     * @param studentsScheduled
     */
    public boolean allocateToTimeslot(final ScheduledTimeSlot scheduledTimeSlot, final Assessment assessment,
            final Map<String, ScheduledStudent> studentsScheduled, final boolean priorityAllocation,
            final String scheduleId, final HashMultimap<String, Assessment> gradesToAssessments) {

        boolean studentAssigned = false;

        if (scheduledTimeSlot.hasUnscheduledSeat()) {
            // must check to see if this time slot has a strict affinity scheduled to it
            // if so, then we double check to see if the assessment passed in satisfies that strict affinity
            // if it does, then go ahead with scheduling, if it doesn't then we cannot schedule
            if (!scheduledTimeSlot.isStrictAffinityScheduled() || scheduledTimeSlot.isStrictAffinityScheduled()
                    && doesAssessmentFulfillAffinity(scheduledTimeSlot, assessment, gradesToAssessments)) {

                // get unscheduled seats with equipment
                final Set<ScheduledSeat> seatsWithEquipment = scheduledTimeSlot
                        .getUnscheduledSeatsWithAccessbilityEquip();

                // for each seat with equipment
                for (final ScheduledSeat nextSeatWithEquip : seatsWithEquipment) {

                    // does the seat have accessiblity equipment objects loaded yet?
                    // if not, load them
                    // if so, skip the load and continue
                    loadAccessibilityEquipmentObj(nextSeatWithEquip);

                    final List<ScheduledStudent> eligibleStudents = findStudentsEligibleForAssessmentAndEquipment(
                            studentsScheduled, assessment, nextSeatWithEquip.getAccessibilityEquipmentObjs());

                    // for each student
                    for (final ScheduledStudent studentToSchedule : eligibleStudents) {

                        // if not scheduled for assessment && able to use seat equipment?
                        if (!studentToSchedule.isStudentScheduled(assessment)
                                && studentToSchedule.canUseAccessibilityEquipment(
                                        nextSeatWithEquip.getAccessibilityEquipmentObjs(), assessment)) {
                            // then schedule for seat
                            studentAssigned = assignStudentToSeat(scheduledTimeSlot, assessment, nextSeatWithEquip,
                                    Lists.newArrayList(studentToSchedule), scheduleId);

                            if (studentAssigned) {
                                studentToSchedule.scheduledToEquipment(assessment,
                                        nextSeatWithEquip.getAccessibilityEquipmentObjs());
                            }

                            // break
                            break;
                        }

                    }

                } // end iteration of seats with equipment

                // we have scheduled any students that qualify for seats with equipment
                // now add any student to any seat

                // get all unscheduled seats
                final Set<ScheduledSeat> unscheduledSeats = scheduledTimeSlot.getAllUnscheduledSeats();

                // for each seat
                for (final ScheduledSeat nextSeat : unscheduledSeats) {

                    final List<ScheduledStudent> eligibleStudents = findStudentsEligibleForAssessment(
                            studentsScheduled, assessment);

                    // for each student
                    for (final ScheduledStudent studentToSchedule : eligibleStudents) {
                        // if student not scheduled for assessment
                        if (!studentToSchedule.isStudentScheduled(assessment)) {
                            // then schedule for seat
                            studentAssigned = assignStudentToSeat(scheduledTimeSlot, assessment, nextSeat,
                                    com.google.common.collect.Lists.newArrayList(studentToSchedule), scheduleId);
                            // break
                            break;
                        }
                    }
                }
            }
        }

        return studentAssigned;

    }

    /**
     * Allocate students to seats based on schedule-wide priority rules.
     * Be aware that the actual scheduling modifies the Schedule argument passed into this function
     * 
     * @param scheduled
     */
    public void allocateWithPriorities(final Schedule scheduled, final List<Assessment> assessments,
            final Map<String, ScheduledStudent> studentsScheduled,
            final HashMultimap<String, Assessment> gradesToAssessments,
            final HashMultimap<String, Assessment> studentGroupToAssessment, final boolean reschedule) {
        // iterate through affinities in the order
        // find assessments, but only schedule a single assessment at once
        if (scheduled.hasPriorityAllocationRules()) {
            for (final Affinity affinity : scheduled.getAffinities()) {
                final Collection<Assessment> affinityAssessments = filterAssessmentByAffinity(assessments,
                        affinity.getType(), affinity.getValue(), gradesToAssessments, studentGroupToAssessment);
                for (final Assessment assessmentToSchedule : affinityAssessments) {
                    allocateToScheduleAffinity(scheduled, studentsScheduled, assessmentToSchedule, affinity,
                            gradesToAssessments, reschedule);
                }
            }
        }
    }

    /**
     * Generic allocation algorithm used when no other affinities or priorities are in effect.
     * Be aware that the actual scheduling modifies the Schedule argument passed into this function
     * 
     * @param scheduled
     */
    public void allocateAll(final Schedule scheduled, final List<Assessment> assessments,
            final Map<String, ScheduledStudent> studentsScheduled,
            final HashMultimap<String, Assessment> gradesToAssessments, final boolean reschedule) {
        // look at the students that still need to be scheduled, do any of them require accommodations?
        // -- if so then determine the distinct assessments those students need to be scheduled for
        // ---- get next assessment
        // ------ get next time slot with empty seats
        // -------- are there still students with accommodations to be allocated?
        // ---------- if yes then, are there any empty seats with accessibility equipment?
        // ------------ if yes then run accessibility rules to see if equipment matches up with students that need
        // accommodations
        // -------------- does equipment match up?
        // ---------------- if yes, then allocate the student to the seat, get next seat and repeat
        // ---------------- if no, then get next seat and repeat
        // ------------ if no then get next time slot and repeat
        // ---------- if no, then are there other students for this assessment that need to be scheduled?
        // ------------ if yes then allocate students to any empty seats, loop back to students with accommodations
        // check
        // ------------ if no, then loop back to get next assessment
        // -- when no students are left to schedule, then done

        Map<String, ScheduledStudent> studentsToBeScheduled = null;

        // for assessment in assessments

        for (final Assessment curAssessment : assessments) {

            // guava filter to get ScheduledStudents that need to be scheduled for assessment

            studentsToBeScheduled = ImmutableMap
                    .copyOf(Maps.filterValues(studentsScheduled, new Predicate<ScheduledStudent>() {
                        @Override
                        public boolean apply(final ScheduledStudent schedStudent) {
                            return schedStudent.isEligibleForAssessment(curAssessment)
                                    && !schedStudent.isStudentScheduled(curAssessment);
                        }
                    }));

            // get ordered time slot itr
            final TreeSet<ScheduledTimeSlot> timeSlots = scheduled.getOrderedTimeSlots(reschedule);

            // for each time slot
            for (final ScheduledTimeSlot nextTimeSlot : timeSlots) {
                // if there are no students to schedule for assessment anymore, then break here
                if (allStudentsScheduled(studentsToBeScheduled)) {
                    break;
                }

                // if time slot has no affinities we can schedule
                // if time slot has affinities and no strict affinity was scheduled, then we can schedule
                // if time slot has affinities and a strict affinity was scheduled, then if the assessment matches the
                // affinity, we can schedule

                final boolean canSchedule = nextTimeSlot.isStrictAffinityScheduled()
                        && doesAssessmentFulfillAffinity(nextTimeSlot, curAssessment, gradesToAssessments)
                        || !nextTimeSlot.isStrictAffinityScheduled();

                if (canSchedule) {

                    // get unscheduled seats with equipment
                    final Set<ScheduledSeat> seatsWithEquipment = nextTimeSlot
                            .getUnscheduledSeatsWithAccessbilityEquip();

                    // for each seat with equipment
                    for (final ScheduledSeat nextSeatWithEquip : seatsWithEquipment) {
                        // for each student
                        for (final ScheduledStudent studentToSchedule : studentsToBeScheduled.values()) {

                            // does the seat have accessiblity equipment objects loaded yet?
                            // if not, load them
                            // if so, skip the load and continue
                            loadAccessibilityEquipmentObj(nextSeatWithEquip);

                            // if not scheduled for assessment && able to use seat equipment?
                            if (!studentToSchedule.isStudentScheduled(curAssessment)
                                    && studentToSchedule.canUseAccessibilityEquipment(
                                            nextSeatWithEquip.getAccessibilityEquipmentObjs(), curAssessment)) {
                                // then schedule for seat
                                final boolean assigned = assignStudentToSeat(nextTimeSlot, curAssessment,
                                        nextSeatWithEquip, Lists.newArrayList(studentToSchedule),
                                        scheduled.getId());

                                if (assigned) {
                                    studentToSchedule.scheduledToEquipment(curAssessment,
                                            nextSeatWithEquip.getAccessibilityEquipmentObjs());
                                }

                                // break
                                break;
                            }

                        }

                    } // end iteration of seats with equipment

                    // we have scheduled any students that qualify for seats with equipment
                    // now add any student to any seat

                    // get all unscheduled seats
                    final Set<ScheduledSeat> unscheduledSeats = nextTimeSlot.getAllUnscheduledSeats();

                    // for each seat
                    for (final ScheduledSeat nextSeat : unscheduledSeats) {
                        // for each student
                        for (final ScheduledStudent studentToSchedule : studentsToBeScheduled.values()) {
                            // if student not scheduled for assessment
                            if (!studentToSchedule.isStudentScheduled(curAssessment)) {
                                // then schedule for seat
                                assignStudentToSeat(nextTimeSlot, curAssessment, nextSeat,
                                        Lists.newArrayList(studentToSchedule), scheduled.getId());
                                // break
                                break;
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Allocate proctors to all time slots that need a proctor
     * Be aware that the actual scheduling modifies the Schedule argument passed into this function
     * 
     * @param scheduled
     * @param proctors
     */
    public void allocateProctors(final Schedule scheduled,
            final HashMultimap<String, Assessment> gradesToAssessments, final boolean reschedule) {
        // iterate through time slots
        // -- next time slot, does it have a proctor associated?
        // ---- if yes, then loop to next time slot
        // ---- if no, then find proctors from list that can proctor the type/types of assessments allocated to this
        // time slot
        // ------ iterate through proctors
        // -------- get proctor availability, is proctor available for entire time slot?
        // ---------- if yes, does proctor have affinities?
        // ------------ if yes, then what is the affinity strictness?
        // -------------- if none or non-exclusive then add proctor to list of possible proctors for this time slot,
        // loop and get next proctor
        // -------------- if strict then what affinity type does the proctor have?
        // ---------------- if assessment, then do(es) the affinity assessment(s) match the assessment(s) for the time
        // slot?
        // ------------------ if yes then add proctor to list of possible proctors for this time slot, loop and get next
        // proctor
        // ------------------ if no then, loop and get next proctor
        // ---------------- if subject, then do(es) the affinity subject(s) match the subject(s) on the assessments for
        // the time slot?
        // ------------------ if yes then add proctor to list of possible proctors for this time slot, loop and get next
        // proctor
        // ------------------ if no then loop and get next proctor
        // ---------------- if grade, then do(es) the affinity grade(s) match the grade(s) on the students assigned to
        // the time slot?
        // ------------------ if yes then add proctor to list of possible proctors for this time slot, loop and get next
        // proctor
        // ------------------ if no then loop and get next proctor
        // ------------ if no, then add proctor to list of possible proctors for this time slot, loop and get next
        // proctor
        // ---------- if no, loop and get next proctor
        // ------ How many proctors are in the list of possible proctors for this time slot?
        // -------- if 0, Cannot allocate any proctors, rescheduling must occur to move things around (FUTURE, not
        // defined yet)
        // -------- if 1, Allocate proctor to this time slot, loop to next time slot
        // -------- if >1, Check to see how many time slots each proctor is assigned to currently, choose proctor with
        // least assignments and allocate to this tie slot, loop to next time slot
        //
        // all time slots with scheduled students have proctors assigned, done

        final Map<String, Integer> proctorAllocationCount = new HashMap<String, Integer>();

        List<Proctor> possibleProctorsForTimeSlot;
        Set<Assessment> timeSlotAssessments;

        final Set<ScheduledTimeSlot> timeSlots = scheduled.getOrderedTimeSlots(reschedule);

        for (final ScheduledTimeSlot timeSlot : timeSlots) {

            if (timeSlot.getProctor() == null) {

                possibleProctorsForTimeSlot = new ArrayList<Proctor>();
                timeSlotAssessments = findAssessmentsForTimeSlot(timeSlot);
                final List<ProctorRole> proctorRoles = findProctorRoles(
                        new ArrayList<Assessment>(timeSlotAssessments));
                final List<Proctor> proctorsForTimeSlotAssessments = findProctors(proctorRoles,
                        scheduled.getInstitutionId());
                // build proctors for a particular institution
                buildAvailableProctors(gradesToAssessments, possibleProctorsForTimeSlot, timeSlotAssessments,
                        timeSlot, proctorsForTimeSlotAssessments);
                // build proctors up the entity hierarchy
                buildParentProctors(scheduled, gradesToAssessments, possibleProctorsForTimeSlot,
                        timeSlotAssessments, timeSlot, proctorRoles);
                if (possibleProctorsForTimeSlot.size() == 0) {
                    LOGGER.debug("No Proctors found for scheduling");
                } else if (possibleProctorsForTimeSlot.size() == 1) {
                    timeSlot.setProctor(possibleProctorsForTimeSlot.get(0));

                    if (proctorAllocationCount.containsKey(possibleProctorsForTimeSlot.get(0).getId())) {

                        proctorAllocationCount.put(possibleProctorsForTimeSlot.get(0).getId(), new Integer(
                                proctorAllocationCount.get(possibleProctorsForTimeSlot.get(0).getId()).intValue()
                                        + 1));
                    } else {
                        proctorAllocationCount.put(possibleProctorsForTimeSlot.get(0).getId(), 1);
                    }
                } else {
                    Proctor lowestProctor = null;

                    for (final Proctor possProctor : possibleProctorsForTimeSlot) {
                        if (lowestProctor == null) {
                            lowestProctor = possProctor;
                        }

                        if (!proctorAllocationCount.containsKey(possProctor.getId())) {
                            proctorAllocationCount.put(possProctor.getId(), 0);
                        }

                        if (!proctorAllocationCount.containsKey(lowestProctor.getId())) {
                            proctorAllocationCount.put(lowestProctor.getId(), 0);
                        }

                        if (proctorAllocationCount.get(possProctor.getId())
                                .compareTo(proctorAllocationCount.get(lowestProctor.getId())) <= -1) {
                            lowestProctor = possProctor;
                        }
                    }

                    timeSlot.setProctor(lowestProctor);

                    proctorAllocationCount.put(lowestProctor.getId(),
                            new Integer(proctorAllocationCount.get(lowestProctor.getId()).intValue() + 1));
                }

            }

        }

    }

    private void buildParentProctors(final Schedule scheduled,
            final HashMultimap<String, Assessment> gradesToAssessments,
            final List<Proctor> possibleProctorsForTimeSlot, final Set<Assessment> timeSlotAssessments,
            final ScheduledTimeSlot timeSlot, final List<ProctorRole> proctorRoles) {

        String entityId = scheduled.getInstitutionId();
        FormatType entityType = FormatType.INSTITUTION;
        while (possibleProctorsForTimeSlot.size() == 0 && entityType != null) {
            final Sb11Entity entity = this.testRegPersister.findById(entityId, entityType);
            final List<Proctor> proctorsForTimeSlotAssessments = findProctors(proctorRoles, entity.getParentId());
            buildAvailableProctors(gradesToAssessments, possibleProctorsForTimeSlot, timeSlotAssessments, timeSlot,
                    proctorsForTimeSlotAssessments);
            entityId = entity.getParentId();
            entityType = entity.getParentEntityType() == null ? null
                    : FormatType.valueOf(entity.getParentEntityType().toString());
        }
    }

    private void buildAvailableProctors(final HashMultimap<String, Assessment> gradesToAssessments,
            final List<Proctor> possibleProctorsForTimeSlot, final Set<Assessment> timeSlotAssessments,
            final ScheduledTimeSlot timeSlot, final List<Proctor> proctorsForTimeSlotAssessments) {
        for (final Proctor tmpProc : proctorsForTimeSlotAssessments) {
            if (tmpProc.isAvailableForTimeSlot(timeSlot)) {
                if (tmpProc.hasAffinities()) {
                    // each of the affinity can specify level of affinity.
                    if (!tmpProc.hasStrictAffinity()) {
                        if (doAllProctorAffinitiesMatchAssessments(tmpProc, timeSlotAssessments, timeSlot,
                                gradesToAssessments, false)) {
                            possibleProctorsForTimeSlot.add(tmpProc);
                        }
                    } else {
                        if (doAllProctorAffinitiesMatchAssessments(tmpProc, timeSlotAssessments, timeSlot,
                                gradesToAssessments, true)) {
                            possibleProctorsForTimeSlot.add(tmpProc);
                        }
                    }
                } else {
                    possibleProctorsForTimeSlot.add(tmpProc);
                }
            }
        }
    }

    /**
     * Look through the studentsScheduled Map to find any students eligible for the assessment given who have not yet
     * been scheduled to take that assessment
     * 
     * @param studentsScheduled
     * @param assessment
     * @return
     */
    public List<ScheduledStudent> findStudentsEligibleForAssessment(
            final Map<String, ScheduledStudent> studentsScheduled, final Assessment assessment) {
        final List<ScheduledStudent> students = new ArrayList<ScheduledStudent>();
        for (final Map.Entry<String, ScheduledStudent> entry : studentsScheduled.entrySet()) {
            final ScheduledStudent scheduledStudent = entry.getValue();
            if (scheduledStudent.isEligibleForAssessment(assessment)
                    && !scheduledStudent.isStudentScheduled(assessment)) {
                students.add(scheduledStudent);
            }
        }
        return students;
    }

    /**
     * Look through the studentsScheduled Map to find any students eligible for the assessment given who have not yet
     * been scheduled to take that assessment and who are eligible to use the accessbility equipment given
     * 
     * @param studentsScheduled
     * @param assessment
     * @param accessibilityEquipment
     * @return
     */
    public List<ScheduledStudent> findStudentsEligibleForAssessmentAndEquipment(
            final Map<String, ScheduledStudent> studentsScheduled, final Assessment assessment,
            final List<AccessibilityEquipment> accessibilityEquipment) {
        final List<ScheduledStudent> students = new ArrayList<ScheduledStudent>();
        for (final Map.Entry<String, ScheduledStudent> entry : studentsScheduled.entrySet()) {
            final ScheduledStudent scheduledStudent = entry.getValue();
            if (scheduledStudent.isEligibleForAssessment(assessment)
                    && !scheduledStudent.isStudentScheduled(assessment)
                    && scheduledStudent.canUseAccessibilityEquipment(accessibilityEquipment, assessment)) {
                students.add(scheduledStudent);
            }
        }
        return students;
    }

    /**
     * Validate the schedule to ensure that all students that should be scheduled are scheduled and that each time slot
     * with scheduled students has a proctor.
     * TBD whether we should validate everything from scratch or assume that the collections we're passing into this
     * method are good enough that we don't have to re-load all the data.
     * 
     * @param scheduled
     * @param eligibleStudents
     * @param studentsScheduled
     * @return
     */
    public ScheduleCreationInfo validateSchedule(final Schedule scheduled,
            final List<EligibleStudent> eligibleStudents, final Map<String, ScheduledStudent> studentsScheduled,
            final boolean reschedule) {

        // the "easy" way to do validation

        // -- iterate through all the time slots in the Schedule
        // ---- for each timeslot, if any seats are scheduled there MUST be a Proctor
        // ------ if there is no proctor, add an error to the list
        // -- iterate through all the ScheduledStudents
        // ---- for each ScheduledStudent ensure that each assessment is marked as scheduled
        // ------ if one is not marked as scheduled, add an error to the list

        // questions that we can answer here to make it easier to get data to the user through the UI:
        // how many test slots do not have a proctor?
        // which student-assessment combinations were unable to be scheduled?

        /*
         * more questions % capacity: # of testing slots, students scheduled, how many unscheduled students and
         * assessments what kind of excess capacity is there if everyone scheduled?
         * 
         * questions of accessibility equipment: did everyone who needs accessibility equipment get allocated?
         */

        int numTimeslots = 0;
        int numEmptyTimeslots = 0;
        int numSeats = 0;
        int numStudentsScheduled = 0;
        int numAssessmentsScheduled = 0;
        int numStudentsNotScheduled = 0;
        int numAssessmentsNotScheduled = 0;
        int numEmptySeats = 0;
        int numSeatsWithAccessEquip = 0;
        int numStudentsNeedingAccessEquipForAssessment = 0;
        int numStudentsCorrectlyAllocatedToEquipForAssessment = 0;
        int numStudentsNotCorrectlyAllocatedToEquipForAssessment = 0;

        boolean hasAccessEquip = false;

        final Set<String> uniqueStudents = new HashSet<String>();

        final ScheduleCreationInfo info = new ScheduleCreationInfo();

        final List<SchedulerValidationError> errors = new ArrayList<SchedulerValidationError>();

        int numNoProctor = 0;

        for (final ScheduledTimeSlot timeSlot : scheduled.getOrderedTimeSlots(reschedule)) {

            numTimeslots++;

            if (timeSlot.getProctor() == null && !timeSlot.isTimeslotCompletelyEmpty()) {
                numNoProctor++;

                errors.add(new SchedulerValidationError(ErrorType.NO_PROCTOR, "No proctor assigned to test slot",
                        String.format(NO_PROCTOR_ERROR_TEMPLATE, timeSlot.getStartTime().toDate(),
                                timeSlot.getEndTime().toDate())));

            }
            if (timeSlot.isTimeslotCompletelyEmpty()) {
                numEmptyTimeslots++;
            }
            for (final ScheduledSeat seat : timeSlot.getSeats()) {
                numSeats++;

                if (seat.getAccessibilityEquipments() != null && !seat.getAccessibilityEquipments().isEmpty()) {
                    numSeatsWithAccessEquip++;
                    hasAccessEquip = true;
                }

                if (seat.getStudent() != null) {

                    if (uniqueStudents.add(seat.getStudent().getEntityId())) {
                        numStudentsScheduled++;
                    }

                    numAssessmentsScheduled++;

                    // if this seat has accessibility equipment
                    // then we look at this student's ScheduledStudent object
                    // and see if there is an entry for this assessment and the equipment
                    // if the value is TRUE, then the student was correctly allocated to the equipment
                    if (seat.getStudent().hasAccommodations(seat.getAssessment())) {
                        if (hasAccessEquip) {
                            final ScheduledStudent thisStudent = studentsScheduled
                                    .get(seat.getStudent().getEntityId());
                            if (thisStudent != null) {
                                for (final AccessibilityEquipment equip : seat.getAccessibilityEquipmentObjs()) {
                                    if (thisStudent.isScheduledForEquipmentInAssessment(seat.getAssessment(),
                                            equip)) {
                                        numStudentsCorrectlyAllocatedToEquipForAssessment++;
                                        break; // only want to count the equipment allocation once per
                                               // student/assessment
                                    }
                                }
                            }

                        }
                    }

                } else {
                    numEmptySeats++;
                }

                hasAccessEquip = false;

            }
        }

        info.setNumTimeslotsWithNoProctor(numNoProctor);
        info.setNumEmptyTestSlots(numEmptyTimeslots);

        final Map<String, ScheduledStudent> studentsScheduledCopy = Maps.newHashMap(studentsScheduled);

        final Map<String, ScheduledStudent> studentsNotScheduled = ImmutableMap
                .copyOf(Maps.filterValues(studentsScheduledCopy, new Predicate<ScheduledStudent>() {

                    @Override
                    public boolean apply(final ScheduledStudent scheduledStudent) {

                        final List<Assessment> toRemove = new ArrayList<Assessment>();

                        for (final Map.Entry<Assessment, Boolean> entry : scheduledStudent.getScheduledAssessments()
                                .entrySet()) {
                            if (entry.getValue()) {
                                toRemove.add(entry.getKey());
                            }
                        }

                        if (!toRemove.isEmpty()) {
                            for (final Assessment assess : toRemove) {
                                scheduledStudent.getScheduledAssessments().remove(assess);
                            }
                        }

                        if (scheduledStudent.getScheduledAssessments().size() > 0) {

                            for (final Assessment assess : scheduledStudent.getScheduledAssessments().keySet()) {

                                errors.add(new SchedulerValidationError(ErrorType.STUDENT_NOT_SCHEDULED,
                                        "Student not scheduled for an assessment he/she is eligible for",
                                        String.format(NOT_SCHEDULED_FOR_ASSESSMENT_ERROR_TEMPLATE,
                                                scheduledStudent.getStudent().getEntityId(),
                                                assess.getTestName())));
                            }

                            return true;
                        } else {
                            return false;
                        }
                    }
                }));

        final Map<String, List<String>> studentsNotScheduledMap = new HashMap<String, List<String>>();

        for (final Map.Entry<String, ScheduledStudent> entry : studentsNotScheduled.entrySet()) {
            numStudentsNotScheduled++;
            final List<String> notScheduledAssessments = new ArrayList<String>();
            for (final Assessment assess : entry.getValue().getScheduledAssessments().keySet()) {
                numAssessmentsNotScheduled++;
                notScheduledAssessments.add(assess.getTestName());
            }
            studentsNotScheduledMap.put(entry.getKey(), notScheduledAssessments);
        }

        // iterate through Students Scheduled
        // get scheduled assessments
        // if TRUE (assessment scheduled for the student)
        // then check assessment equipment association
        // if there is one and the value is false
        // this means that the student was scheduled for the assessment, but was NOT scheduled into a seat
        // that had the proper equipment the student needs

        for (final ScheduledStudent schedStudent : studentsScheduled.values()) {
            for (final Map.Entry<Assessment, Boolean> entry : schedStudent.getScheduledAssessments().entrySet()) {
                boolean scheduledForAllEquip = true;
                if (entry.getValue()) {
                    final Map<AccessibilityEquipment, Boolean> equipmentScheduled = schedStudent
                            .getAllScheduledForEquipmentInAssessment(entry.getKey());

                    for (final Map.Entry<AccessibilityEquipment, Boolean> accessEquipEntry : equipmentScheduled
                            .entrySet()) {
                        if (!accessEquipEntry.getValue()) {
                            scheduledForAllEquip = false;
                            errors.add(new SchedulerValidationError(ErrorType.NOT_ALLOCATED_TO_REQUIRED_EQUIPMENT,
                                    "Student not allocated to necessary accessibility equipment",
                                    String.format(ACCESS_EQUIP_ERROR_TEMPLATE,
                                            schedStudent.getStudent().getEntityId(),
                                            accessEquipEntry.getKey().getName(), entry.getKey().getTestName())));
                        }
                    }

                    if (!scheduledForAllEquip) {
                        numStudentsNotCorrectlyAllocatedToEquipForAssessment++;
                    }
                }
            }
        }

        // now just iterate through all the scheduled students
        // and see if any of them is eligible for accessibility equipment
        // in order to generate the count of all students who require equipment

        for (final ScheduledStudent schedStudent : studentsScheduled.values()) {
            numStudentsNeedingAccessEquipForAssessment += schedStudent.numAssessmentsAccessEquipRequiredFor();
        }

        info.setStudentsNotScheduled(studentsNotScheduledMap);

        info.setErrors(errors);

        info.setNumEmptySeats(numEmptySeats);
        info.setNumSeatsWithAccessibilityEquipment(numSeatsWithAccessEquip);
        info.setNumStudentAssessmentsReqAccessEquipScheduled(numStudentsCorrectlyAllocatedToEquipForAssessment);
        info.setNumStudentAssessmentsReqAccessEquipScheduledIncorrect(
                numStudentsNotCorrectlyAllocatedToEquipForAssessment);
        info.setNumStudentAssessmentsRequiringAccessibilityEquipment(numStudentsNeedingAccessEquipForAssessment);
        info.setNumStudentsScheduled(numStudentsScheduled);
        info.setNumUnscheduledAssessments(numAssessmentsNotScheduled);
        info.setNumUnscheduledStudents(numStudentsNotScheduled);
        info.setPercentCapacityUsed((float) numAssessmentsScheduled / (float) numSeats * 100);
        info.setTotalNumSeats(numSeats);
        info.setTotalNumTestSlots(numTimeslots);

        return info;
    }

    /**
     * Returns a list of EligibleStudents that only contain those students that match the assessments
     * 
     * @param eligibleStudents
     * @param assessments
     * @return
     */
    public List<EligibleStudent> filterStudents(final List<EligibleStudent> eligibleStudents,
            final List<Assessment> assessments) {

        final List<EligibleStudent> filtered = new ArrayList<EligibleStudent>();

        final com.google.common.collect.ListMultimap<Assessment, EligibleStudent> multimap = ArrayListMultimap
                .create();

        for (final EligibleStudent eligStudent : eligibleStudents) {
            // create multimap so we know which eligible students go with which assessments
            for (final Assessment assess : eligStudent.getAssessments()) {
                multimap.put(assess, eligStudent);
            }
        }

        // iterate through assessments and get the eligible students for each, putting them into filtered and then
        // return filtered

        for (final Assessment assess : assessments) {
            if (multimap.containsKey(assess)) {
                filtered.addAll(multimap.get(assess));
            }
        }

        return filtered;
    }

    /**
     * Figures out what accessibility equipment each student is eligible to use
     * 
     * @param studentsScheduled
     * @param schedule
     * @param reschedule
     */
    public void determineStudentAccessEquip(final Map<String, ScheduledStudent> studentsScheduled,
            final Schedule schedule, final boolean reschedule) {
        final TreeSet<ScheduledTimeSlot> timeSlots = schedule.getOrderedTimeSlots(reschedule);

        final List<ScheduledSeat> seatsWithEquipment = new ArrayList<ScheduledSeat>();
        final Set<AccessibilityEquipment> uniqueEquipment = new HashSet<AccessibilityEquipment>();

        for (final ScheduledTimeSlot timeSlot : timeSlots) {
            seatsWithEquipment.addAll(timeSlot.getSeatsWithAccessbilityEquip());
        }

        // ensure the seats have the access equip objects loaded from the db
        // load all equipment into a Set so we have all the unique equipment from all seats and no duplicates
        for (final ScheduledSeat seat : seatsWithEquipment) {
            loadAccessibilityEquipmentObj(seat);
            uniqueEquipment.addAll(seat.getAccessibilityEquipmentObjs());
        }

        // iterate through each assessment the student is eligible for
        // and each accessibility equipment
        for (final ScheduledStudent schedStudent : studentsScheduled.values()) {
            for (final Assessment assessment : schedStudent.getScheduledAssessments().keySet()) {
                for (final AccessibilityEquipment equip : uniqueEquipment) {
                    if (schedStudent.canUseAccessibilityEquipment(Lists.newArrayList(equip), assessment)) {
                        schedStudent.addAccessEquipEligibility(assessment, equip);
                    }
                }
            }
        }

    }

    /**
     * Returns the filtered list of assessments by affinity type
     */
    private Collection<Assessment> filterAssessmentByAffinity(final List<Assessment> assessments,
            final AffinityType affinityType, final String affinity,
            final HashMultimap<String, Assessment> gradesToAssessments,
            final HashMultimap<String, Assessment> studentGroupToAssessments) {

        switch (affinityType) {
        case ASSESSMENT:
            return Collections2.filter(assessments, new Predicate<Assessment>() {
                @Override
                public boolean apply(final Assessment assessment) {
                    return assessment.getId().equals(affinity);
                }
            });
        case SUBJECT:
            return Collections2.filter(assessments, new Predicate<Assessment>() {
                @Override
                public boolean apply(final Assessment assessment) {
                    return assessment.getSubjectCode().equals(affinity);
                }
            });
        case GRADE:
            return gradesToAssessments.get(affinity);
        case STUDENTGROUP:
            return studentGroupToAssessments.get(affinity);
        default:
            return null;
        }
    }

    /**
     * Gets assessments by affinity type and allocates it to the timeslot having this affinity
     */
    private void allocateTimeslotAffinity(final List<Assessment> assessments, final ScheduledTimeSlot timeslot,
            final Map<String, ScheduledStudent> studentsScheduled, final String scheduleId, final Affinity affinity,
            final HashMultimap<String, Assessment> gradesToAssessments) {

        boolean successfulAllocation = false;
        final Collection<Assessment> affinityAssessments = filterAssessmentByAffinity(assessments,
                affinity.getType(), affinity.getValue(), gradesToAssessments, null);
        for (final Assessment affinityAssessment : affinityAssessments) {
            successfulAllocation = allocateToTimeslot(timeslot, affinityAssessment, studentsScheduled, false,
                    scheduleId, gradesToAssessments);

            if (successfulAllocation) {
                timeslot.addScheduledAffinity(affinity);
            }
        }
    }

    /**
     * Allocates all the affinities defined in the schedule to all the available seats in each timeslot
     */
    private void allocateToScheduleAffinity(final Schedule scheduled,
            final Map<String, ScheduledStudent> studentsScheduled, final Assessment affinityAssessment,
            final Affinity affinity, final HashMultimap<String, Assessment> gradesToAssessments,
            final boolean reschedule) {

        // this will be one assessment at a time
        // if the affinity is strict then we must find a timeslot that either already has this affinity scheduled for
        // this assessment
        // or a time slot that has NO scheduled seats
        // if the affinity is not strict, then we can find any time slot with empty seats and fill it up

        // be sure to mark the timeslot as having the affinity

        // each loop for a timeslot we need to check to see if all students have been scheduled for the current
        // assessment we're scheduling so that
        // we make sure that we schedule ALL students for any particular assessment in contiguous time slots (or as
        // close to contiguous as possible)
        // in order to avoid missing scheduling students for a priority in the correct order

        for (final ScheduledTimeSlot timeslot : scheduled.getOrderedTimeSlots(reschedule)) {

            if (studentsStillToBeScheduled(studentsScheduled, affinityAssessment)) {

                if (affinity.isStrict()) {
                    if (timeslot.getNumberOfAssignedSeats() == 0 || !timeslot.getAffinitiesScheduled().isEmpty()
                            && timeslot.getAffinitiesScheduled().contains(affinity)) {
                        final boolean affinityScheduled = allocateToTimeslot(timeslot, affinityAssessment,
                                studentsScheduled, true, scheduled.getId(), gradesToAssessments);
                        if (affinityScheduled) {
                            timeslot.addScheduledAffinity(affinity);
                        }
                    }
                } else {
                    final boolean affinityScheduled = allocateToTimeslot(timeslot, affinityAssessment,
                            studentsScheduled, true, scheduled.getId(), gradesToAssessments);
                    if (affinityScheduled) {
                        timeslot.addScheduledAffinity(affinity);
                    }
                }

            }

        }
    }

    /**
     * Allocates eligible student for an assessment to an available seat. Once assigned marks the seat and student as
     * scheduled
     */
    private boolean assignStudentToSeat(final ScheduledTimeSlot scheduledTimeSlot, final Assessment assessment,
            final ScheduledSeat seat, final List<ScheduledStudent> eligibleStudents, final String scheduleId) {

        for (final ScheduledStudent eligibleStudent : eligibleStudents) {
            if (scheduledTimeSlot.getAssignedStudents().add(eligibleStudent.getStudent().getEntityId())) {
                seat.setStudent(eligibleStudent.getStudent());
                seat.setAssessment(assessment);
                seat.setSeatScheduled(true);
                eligibleStudent.getScheduledAssessments().put(assessment, Boolean.TRUE);
                this.scheduleTestStatusCreator.addStatus(new ScheduleTestStatus(scheduleId, assessment.getId(),
                        eligibleStudent.getStudent().getEntityId(),
                        eligibleStudent.getStudent().getStateAbbreviation(), scheduledTimeSlot.getStartTime()));
                return true;
            }
        }

        return false;
    }

    private boolean allStudentsScheduled(final Map<String, ScheduledStudent> studentsToBeScheduled) {

        for (final ScheduledStudent student : studentsToBeScheduled.values()) {
            if (!student.isStudentFullyScheduled()) {
                return false;
            }
        }

        return true;
    }

    private Set<Assessment> findAssessmentsForTimeSlot(final ScheduledTimeSlot timeSlot) {

        final Set<Assessment> assessmentsForTimeSlot = new HashSet<Assessment>();

        final Set<ScheduledSeat> seats = timeSlot.getSeats();

        for (final ScheduledSeat seat : seats) {
            if (seat.getAssessment() != null) {
                assessmentsForTimeSlot.add(seat.getAssessment());
            }
        }

        return assessmentsForTimeSlot;
    }

    private boolean doAllProctorAffinitiesMatchAssessments(final Proctor proctor, final Set<Assessment> assessments,
            final ScheduledTimeSlot timeSlot, final HashMultimap<String, Assessment> gradesToAssessments,
            final boolean isStrict) {

        // if strict find the first strict affinity
        // else get all the affinities from the proctor
        // for each affinity
        // if affinity is grade then the match should be based on the students grades in who are scheduled to the
        // timeslot
        // --if there are student grades matching affinity grade return true else continue
        // else match assessments for the affinity value
        // --if there are assessments matching the affinity return true else continue
        List<Affinity> affinities = new ArrayList<Affinity>();
        if (isStrict) {
            final Affinity affinity = proctor.findFirstStrictAffinity();
            affinities = affinity != null ? com.google.common.collect.Lists.newArrayList(affinity) : affinities;
        } else {
            affinities = proctor.getAffinities();
        }
        for (final Affinity affinity : affinities) {
            if (affinity.getType() == AffinityType.GRADE) {
                final Set<GradeLevel> studentGrades = getGradesFromScheduledStudents(timeSlot);
                for (final GradeLevel grade : studentGrades) {
                    if (grade.getGrade().equalsIgnoreCase(affinity.getValue())) {
                        return true;
                    }
                }
            } else {
                final Collection<Assessment> affinityAssessments = filterAssessmentByAffinity(
                        com.google.common.collect.Lists.newArrayList(assessments), affinity.getType(),
                        affinity.getValue(), gradesToAssessments, null);
                if (!CollectionUtils.isEmpty(affinityAssessments)) {
                    return true;
                }
            }
        }

        return false;
    }

    private Set<GradeLevel> getGradesFromScheduledStudents(final ScheduledTimeSlot timeSlot) {

        final Set<GradeLevel> grades = new HashSet<GradeLevel>();
        final Set<ScheduledSeat> seats = timeSlot.getSeats();
        for (final ScheduledSeat seat : seats) {
            grades.add(seat.getStudent().getGradeLevelWhenAssessed());
        }

        return grades;
    }

    // specifically, the strict affinity that was previously scheduled here
    private boolean doesAssessmentFulfillAffinity(final ScheduledTimeSlot timeSlot, final Assessment assessment,
            final HashMultimap<String, Assessment> gradesToAssessments) {

        boolean returnVal = false;

        final Set<Affinity> affSet = timeSlot.getAffinitiesScheduled();

        Affinity aff = null;

        for (final Affinity tmpAff : affSet) {
            if (tmpAff.isStrict()) {
                aff = tmpAff;
                break;
            }
        }

        if (aff == null) {
            // immediately return true because if this is called with a non-strict affinity scheduled
            // to the time slot, then we can schedule anything else here
            return true;
        }

        switch (aff.getType()) {
        case ASSESSMENT:
            if (aff.getValue().equals(assessment.getTestName())) {
                returnVal = true;
            }

            break;
        case GRADE:

            final Set<Assessment> assessmentsForGrade = gradesToAssessments.get(aff.getValue());

            for (final Assessment tmpAssess : assessmentsForGrade) {
                if (assessment.equals(tmpAssess)) {
                    returnVal = true;
                    break;
                }
            }

            break;
        case SUBJECT:

            if (aff.getValue().equals(assessment.getSubjectCode())) {
                returnVal = true;
            }

            break;
        default:
            returnVal = false;
            break;
        }

        return returnVal;
    }

    private boolean studentsStillToBeScheduled(final Map<String, ScheduledStudent> studentsToBeScheduled,
            final Assessment assessment) {

        final Map<String, ScheduledStudent> filtered = Maps.filterEntries(studentsToBeScheduled,
                new Predicate<Map.Entry<String, ScheduledStudent>>() {
                    @Override
                    public boolean apply(final Map.Entry<String, ScheduledStudent> entry) {

                        if (entry.getValue().isEligibleForAssessment(assessment)
                                && !entry.getValue().isStudentScheduled(assessment)) {
                            return true;
                        } else {
                            return false;
                        }
                    }
                });

        return !filtered.isEmpty();
    }

    private void loadAccessibilityEquipmentObj(final ScheduledSeat seat) {

        // does the seat have accessiblity equipment objects loaded yet?
        // if not, load them
        // if so, skip the load and continue
        if (seat.getAccessibilityEquipmentObjs() == null || seat.getAccessibilityEquipmentObjs().isEmpty()) {
            final List<AccessibilityEquipment> accessEquipList = new ArrayList<AccessibilityEquipment>();

            if (seat.getAccessibilityEquipments() != null && !seat.getAccessibilityEquipments().isEmpty()) {
                for (final String equipName : seat.getAccessibilityEquipments()) {
                    final Map<String, String[]> reqMap = new HashMap<String, String[]>();
                    reqMap.put("name", new String[] { equipName });
                    final AccessibilityEquipmentSearchRequest searchReq = new AccessibilityEquipmentSearchRequest(
                            reqMap);
                    final SearchResponse<AccessibilityEquipment> equipSearchResp = this.accessibilityEquipmentService
                            .searchAccessibilityEquipments(searchReq);
                    if (equipSearchResp.getTotalCount() > 0) {
                        accessEquipList.add(equipSearchResp.getSearchResults().get(0));
                    }
                }
            }

            seat.setAccessibilityEquipmentObjs(accessEquipList);
        }
    }

    public List<StudentGroup> findStudentGroups(final String institutionMongoId) {
        return this.studentGroupService.findStudentGroups(institutionMongoId);
    }

}