public ActionResult SchoolSelection(Applicant applicant, FormCollection formCollection)
        {
            // Is the lottery closed?
            if (IsLotteryClosed()) // TODO implement this as an class-level annotation instead
            {
                return(RedirectToAction(actionName: "LotteryClosed"));
            }

            // Make sure someone isn't playing with the ID from the form
            if (!IsAuthorizedApplicant(applicant) || !IsActiveSession()) // TODO Use AOP/Annotations to do this instead
            {
                return(RedirectToAction("Index"));
            }

            // At least one program needs to be selected
            applicant = GetSessionApplicant(); // fills in the other properties, other than just Applicant ID
            var programIds = new List <int>();

            if (formCollection["programs"] == null || !formCollection["programs"].Any())
            {
                ModelState.AddModelError("programs", GoldenTicketText.NoSchoolSelected);
                viewHelper.PrepareSchoolSelectionView(ViewBag, applicant);
                return(View(applicant));
            }
            else
            {
                var programIdStrs = formCollection["programs"].Split(',').ToList();
                programIdStrs.ForEach(idStr => programIds.Add(int.Parse(idStr)));
            }

            // Remove existing applications for this user
            var applieds = database.Applieds.Where(applied => applied.ApplicantID == applicant.ID).ToList();

            applieds.ForEach(a => database.Applieds.Remove(a));

            // Add new Applied associations (between program and program)
            var populatedApplicant = database.Applicants.Find(applicant.ID);

            foreach (var programId in programIds)
            {
                var applied = new Applied();
                applied.ApplicantID = applicant.ID;
                applied.SchoolID    = programId;

                // Confirm that the program ID is within the city lived in (no sneakers into other districts)
                var program = database.Schools.Find(programId);
                if (program != null && program.City.Equals(populatedApplicant.StudentCity, StringComparison.CurrentCultureIgnoreCase))
                {
                    database.Applieds.Add(applied);
                }
            }

            database.SaveChanges();
            return(RedirectToAction("Review"));
        }
Esempio n. 2
0
        public void Reconcile()
        {
            while (true)
            {
                var selecteds = db.Selecteds.ToList();

                // Selected applicants can't be waitlisted anywhere
                var waitlisteds         = db.Waitlisteds.ToList();
                var waitlistedsToRemove = new List <Waitlisted>();
                selecteds.ForEach(x => waitlistedsToRemove.AddRange(waitlisteds.Where(y => y.ApplicantID == x.ApplicantID)));
                db.Waitlisteds.RemoveRange(waitlistedsToRemove);
                db.SaveChanges();

                // Find out if there are any applicants that need to be reconciled
                var selectedCount = new Dictionary <int, int>(); // key => selected applicant ID, # of schools selected for
                foreach (var selected in selecteds)
                {
                    var count = 0;
                    selectedCount.TryGetValue(selected.ApplicantID, out count);
                    selectedCount[selected.ApplicantID] = ++count;
                }

                // --- Exit case: all selected students are only selected for one school ---
                var multiSelectedApplicantCounts = selectedCount.Where(x => x.Value > 2).ToList();
                if (!multiSelectedApplicantCounts.Any())
                {
                    return;
                }

                // Applicants can only be selected once
                // Remove selected applicants from schools with a worse rank
                var schoolIdsToRerun = new List <int>();
                foreach (var pair in multiSelectedApplicantCounts)
                {
                    var applicantId = pair.Key;
                    var worseRankSchoolsForApplicant = RemoveWorseRankSelecteds(applicantId);
                    schoolIdsToRerun.AddRange(worseRankSchoolsForApplicant);
                }
                db.SaveChanges();

                // Re-run lottery for schools that had a selected removed
                var uniqueSchoolIdsToRerun = new HashSet <int>(schoolIdsToRerun); // eliminate school IDs appearing twice
                foreach (var schoolId in uniqueSchoolIdsToRerun)
                {
                    var school = db.Schools.Find(schoolId);
                    lottery.Run(school, Utils.GetApplicants(school.Waitlisteds), false);
                }
            }
        }
Esempio n. 3
0
        /**
         * <summary>Import all the applicants from the CSV file into the system.</summary>
         */
        public List <School> ReadApplicants()
        {
            // Read the CSV
            using (var textReader = new StreamReader(csvFilePath))
            {
                var csvReader = new CsvReader(textReader);
                csvReader.Configuration.SkipEmptyRecords = true;
                csvReader.Configuration.TrimFields       = true;

                var applicants = new List <Applicant>();
                var applieds   = new List <Applied>();

                while (csvReader.Read())
                {
                    var applicant = ParseApplicant(csvReader);
                    applicants.Add(applicant);

                    // Add student to each school
                    var schoolNames = csvReader.GetField <string>("Schools Applied").Split(';');
                    foreach (var rawSchoolName in schoolNames)
                    {
                        var schoolName = rawSchoolName.Trim();
                        if (schoolName.IsEmpty())
                        {
                            continue;
                        }

                        var school = db.Schools.First(s => s.Name.Equals(schoolName, StringComparison.CurrentCultureIgnoreCase));

                        var applied = new Applied {
                            Applicant = applicant, School = school
                        };

                        applieds.Add(applied);
                    }
                }

                db.Applicants.AddRange(applicants);
                db.Applieds.AddRange(applieds);
                db.SaveChanges();
            }

            return(schools.Values.ToList());
        }
Esempio n. 4
0
        /**
         * <summary>
         * Runs the lottery algorithm for an individual school.
         * A specific list of applicants is used for the lottery run.
         * </summary>
         *
         * <remarks>TODO Optimize this later. There are a lot of repeated loops.</remarks>
         *
         * <param name="school">School to run the algorithm for</param>
         * <param name="applicantList">Applicants to use for selection in the lottery run</param>
         * <returns>The school passed in</returns>
         */
        public School Run(School school, List <Applicant> applicantList, bool shuffleApplicantList)
        {
            // Order the selected and waitlists by rank
            var selecteds          = school.Selecteds.OrderBy(s => s.Rank).ToList();
            var selectedApplicants = Utils.GetApplicants(selecteds);

            var waitlisteds          = school.Waitlisteds.OrderBy(w => w.Rank).ToList();
            var waitlistedApplicants = Utils.GetApplicants(waitlisteds);

            // Clear selected and waitlisteds
            db.Selecteds.RemoveRange(selecteds);
            db.Waitlisteds.RemoveRange(waitlisteds);

            // Counts the existing numbers of selected applicants for the school
            var countMale             = 0;
            var countFemale           = 0;
            var countBelowPovertyLine = 0;
            var countAbovePovertyLine = 0;

            foreach (var applicant in selectedApplicants)
            {
                // Gender counts
                if (applicant.StudentGender == Gender.Male)
                {
                    countMale++;
                }
                else
                {
                    countFemale++;
                }

                // Poverty counts
                if (incomeCalculator.IsBelowPovertyLine(applicant))
                {
                    countBelowPovertyLine++;
                }
                else
                {
                    countAbovePovertyLine++;
                }
            }

            // Initial calculations
            var numStudents         = school.Seats;
            var numMale             = (int)Math.Round(numStudents * school.GenderBalance);
            var numFemale           = numStudents - numMale;
            var numBelowPovertyLine = (int)Math.Round(numStudents * school.PovertyRate);
            var numAbovePovertyLine = numStudents - numBelowPovertyLine;

            // Copy the list to preserve the passed in list
            var applicants = new List <Applicant>(applicantList);


            // Randomly sort the list
            if (shuffleApplicantList)
            {
                // Clear existing shuffled (there really shouldn't be any) // TODO make a utility method for this
                var existingShuffleds = db.Shuffleds.Where(s => s.SchoolID == school.ID).ToList();
                db.Shuffleds.RemoveRange(existingShuffleds);

                // Randomly shuffle the applicants
                applicants.Shuffle(new Random());

                // Record the shuffling
                var shuffledApplicants = new List <Applicant>(applicants);

                // Preserve the shuffle order
                var shuffleds = new List <Shuffled>(shuffledApplicants.Count);
                for (var i = 0; i < shuffledApplicants.Count; i++)
                {
                    var shuffled = new Shuffled
                    {
                        Applicant = shuffledApplicants[i],
                        School    = school,
                        Rank      = i
                    };
                    shuffleds.Add(shuffled);
                }
                db.Shuffleds.AddRange(shuffleds);
                db.SaveChanges();
            }

            // Select low income students
            var lowIncomeApplicants = GetByPovertyStatus(applicants, true);

            foreach (var a in lowIncomeApplicants)
            {
                // If the low income quota has been met, move on
                if (countBelowPovertyLine >= numBelowPovertyLine || selectedApplicants.Count >= numStudents)
                {
                    break;
                }

                // Add the student if the male/female ratio hasn't been violated
                if (a.StudentGender == Gender.Male && countMale < numMale)
                {
                    selectedApplicants.Add(a);
                    applicants.Remove(a);

                    countBelowPovertyLine++;
                    countMale++;
                }
                else if (a.StudentGender == Gender.Female && countFemale < numFemale)
                {
                    selectedApplicants.Add(a);
                    applicants.Remove(a);

                    countBelowPovertyLine++;
                    countFemale++;
                }
            }

            // Do a second pass on the below poverty line students in case gender balance prevented it from getting fulfilled
            // RIDE: Income balance takes priority over male to female ratio
            // Gender agnostic, income checked pass
            if (countBelowPovertyLine < numBelowPovertyLine)
            {
                lowIncomeApplicants = GetByPovertyStatus(applicants, true);
                foreach (var a in lowIncomeApplicants)
                {
                    // If the low income quota has been met, move on
                    if (countBelowPovertyLine >= numBelowPovertyLine || selectedApplicants.Count >= numStudents)
                    {
                        break;
                    }

                    // Add the student
                    if (a.StudentGender == Gender.Male)
                    {
                        selectedApplicants.Add(a);
                        applicants.Remove(a);

                        countBelowPovertyLine++;
                        countMale++;
                    }
                    else if (a.StudentGender == Gender.Female)
                    {
                        selectedApplicants.Add(a);
                        applicants.Remove(a);

                        countBelowPovertyLine++;
                        countFemale++;
                    }
                }
            }

            // Select higher income students
            // TODO refactor -- almost the same as the above loop
            var higherIncomeApplicants = GetByPovertyStatus(applicants, false);

            foreach (var a in higherIncomeApplicants)
            {
                // If the higher income quota has been met, move on
                if (countAbovePovertyLine >= numAbovePovertyLine || selectedApplicants.Count >= numStudents)
                {
                    break;
                }

                // Add the student if the male/female ratio hasn't been violated
                if (a.StudentGender == Gender.Male && countMale < numMale)
                {
                    selectedApplicants.Add(a);
                    applicants.Remove(a);

                    countAbovePovertyLine++;
                    countMale++;
                }
                else if (a.StudentGender == Gender.Female && countFemale < numFemale)
                {
                    selectedApplicants.Add(a);
                    applicants.Remove(a);

                    countAbovePovertyLine++;
                    countFemale++;
                }
            }

            // Do a second pass on the above poverty line students in case gender balance prevented it from getting fulfilled
            // RIDE: Income balance takes priority over male to female ratio
            // Gender agnostic, income checked pass
            if (countAbovePovertyLine < numAbovePovertyLine)
            {
                higherIncomeApplicants = GetByPovertyStatus(applicants, false);
                foreach (var a in higherIncomeApplicants)
                {
                    // If the low income quota has been met, move on
                    if (countAbovePovertyLine >= numAbovePovertyLine || selectedApplicants.Count >= numStudents)
                    {
                        break;
                    }

                    // Add the student
                    if (a.StudentGender == Gender.Male)
                    {
                        selectedApplicants.Add(a);
                        applicants.Remove(a);

                        countBelowPovertyLine++;
                        countMale++;
                    }
                    else if (a.StudentGender == Gender.Female)
                    {
                        selectedApplicants.Add(a);
                        applicants.Remove(a);

                        countBelowPovertyLine++;
                        countFemale++;
                    }
                }
            }

            // Are there still openings? (income agnostic, gender checked selection)
            foreach (var a in new List <Applicant>(applicants)) // prevents modification during iteration
            {
                if (selectedApplicants.Count >= numStudents)
                {
                    break;
                }

                //TODO refactor -- this chunk of code is similar to other male/female selections
                // Add the student if the male/female ratio hasn't been violated
                if (a.StudentGender == Gender.Male && countMale < numMale)
                {
                    selectedApplicants.Add(a);
                    applicants.Remove(a);

                    countMale++;
                }
                else if (a.StudentGender == Gender.Female && countFemale < numFemale)
                {
                    selectedApplicants.Add(a);
                    applicants.Remove(a);

                    countFemale++;
                }
            }

            // Are there still openings? (income and gender agnostic selection)
            foreach (var a in new List <Applicant>(applicants))
            {
                if (selectedApplicants.Count >= numStudents)
                {
                    break;
                }

                selectedApplicants.Add(a);
                applicants.Remove(a);
            }

            // Wait list the rest
            waitlistedApplicants.Clear();
            waitlistedApplicants.AddRange(applicants);

            // Preserve the ranks
            var newSelecteds = new List <Selected>(selectedApplicants.Count);

            for (var i = 0; i < selectedApplicants.Count; i++)
            {
                var selected = new Selected
                {
                    Applicant = selectedApplicants[i],
                    School    = school,
                    Rank      = i
                };
                newSelecteds.Add(selected);
            }
            db.Selecteds.AddRange(newSelecteds);

            var newWaitlisteds = new List <Waitlisted>(waitlistedApplicants.Count);

            for (var i = 0; i < waitlistedApplicants.Count; i++)
            {
                var waitlisted = new Waitlisted
                {
                    Applicant = waitlistedApplicants[i],
                    School    = school,
                    Rank      = i
                };
                newWaitlisteds.Add(waitlisted);
            }
            db.Waitlisteds.AddRange(newWaitlisteds);


            db.SaveChanges();


            return(school);
        }
Esempio n. 5
0
        public ActionResult EditApplicant(Applicant applicant, FormCollection formCollection)
        {
            // Verify that the applicant exists
            var queriedApplicant = db.Applicants.Find(applicant.ID);

            if (queriedApplicant == null)
            {
                return(HttpNotFound());
            }

            // Empty check student and guardian information
            viewHelper.EmptyCheckStudentInformation(ModelState, applicant);
            viewHelper.EmptyCheckGuardianInformation(ModelState, applicant);

            // School selection check //TODO Make this code shareable with the parent side
            var schoolIds = new List <int>();

            if (formCollection["programs"] == null || !formCollection["programs"].Any())
            {
                ModelState.AddModelError("programs", GoldenTicketText.NoSchoolSelected);
                PrepareEditApplicantView(applicant);
                return(View(applicant));
            }
            else
            {
                var programIdStrs = formCollection["programs"].Split(',').ToList();
                programIdStrs.ForEach(idStr => schoolIds.Add(int.Parse(idStr)));
            }

            if (!ModelState.IsValid)
            {
                PrepareEditApplicantView(applicant);
                return(View(applicant));
            }

            // Remove existing applications for this user
            var applieds = db.Applieds.Where(applied => applied.ApplicantID == applicant.ID).ToList();

            applieds.ForEach(a => db.Applieds.Remove(a));

            // Add new Applied associations (between program and program)
            var populatedApplicant = db.Applicants.Find(applicant.ID);

            foreach (var programId in schoolIds)
            {
                var applied = new Applied();
                applied.ApplicantID = applicant.ID;
                applied.SchoolID    = programId;

                // Confirm that the program ID is within the city lived in (no sneakers into other districts)
                var program = db.Schools.Find(programId);
                if (program != null && program.City.Equals(populatedApplicant.StudentCity, StringComparison.CurrentCultureIgnoreCase))
                {
                    db.Applieds.Add(applied);
                }
            }

            // Save changes to the database
            db.Applicants.AddOrUpdate(applicant);
            db.SaveChanges();

            return(RedirectToAction("ViewApplicant", new{ id = applicant.ID }));
        }