Add Blazor WebApp and rework data handling to utilize Entity Framework

This commit is contained in:
2025-09-11 11:49:48 -04:00
parent 5220e61c79
commit 3daa3b81b3
111 changed files with 6039 additions and 946 deletions
-20
View File
@@ -1,20 +0,0 @@
using Core.Entities;
namespace Core.Calculation;
public class DataProcessing
{
public static EventStudentPicks[] GetEventStudentPicks(IList<CompetitiveEvent> events, IList<Student> students)
{
return
students.SelectMany(
student => student.RankedEventPicks.Select((e, i) => (e, student, i + 1)))
.OrderBy(tuple => tuple.Item3)
.ThenByDescending(tuple => tuple.student.Grade + tuple.student.TsaYear)
.GroupBy(tuple => tuple.e)
.OrderBy(tuples => tuples.Key.Name)
.Select(tuples =>
new EventStudentPicks(tuples.Key, tuples.Select(tuple => Tuple.Create(tuple.student, tuple.Item3)).ToList())
).ToArray();
}
}
-316
View File
@@ -1,316 +0,0 @@
using System.Diagnostics;
using Core.Entities;
using Google.OrTools.Sat;
using IntVar = Google.OrTools.Sat.IntVar;
namespace Core.Calculation
{
public class EventAssigner
{
private readonly IList<CompetitiveEvent> _events;
private readonly IList<Student> _students;
private readonly AssignmentParameters _parameters;
private readonly int[] _allEvents;
private readonly int[] _allStudents;
// how many students have picked each event?
private readonly int[] _eventPickCounts;
private IList<EventAssignment> _preAssigned = new List<EventAssignment>();
private IList<EventAssignment> _prohibited = new List<EventAssignment>();
private IList<CompetitiveEvent> _droppedEvents;
private IList<CompetitiveEvent> _includedEvents;
public EventAssigner(IList<CompetitiveEvent> events, IList<Student> students, AssignmentParameters parameters)
{
_events = events;
_students = students;
_parameters = parameters;
_allEvents = Enumerable.Range(0, _events.Count).ToArray();
_allStudents = Enumerable.Range(0, _students.Count).ToArray();
_eventPickCounts = new int[_allEvents.Length];
_preAssigned = new List<EventAssignment>();
_droppedEvents = new List<CompetitiveEvent>();
_includedEvents = new List<CompetitiveEvent>();
for (var i = 0; i < _events.Count; i++)
{
var e = _events[i];
_eventPickCounts[i] = _students.Count(s => s.RankedEventPicks.Contains(e));
}
}
public void AssignToEvent(EventAssignment preAssigned)
{
_preAssigned.Add(preAssigned);
}
public void ExcludeFromEvent(EventAssignment prohibited)
{
_prohibited.Add(prohibited);
}
public void RemoveEvent(IList<CompetitiveEvent> events)
{
_droppedEvents = events;
}
public void IncludedEvents(IList<CompetitiveEvent> events)
{
_includedEvents = events;
}
public class SolutionPrinter : CpSolverSolutionCallback
{
private readonly IList<CompetitiveEvent> _events;
private readonly IList<Student> _students;
private readonly BoolVar[,] _eventAssignment;
public SolutionPrinter(IList<CompetitiveEvent> events, IList<Student> students, BoolVar[,] eventAssignment)
{
_events = events;
_students = students;
_eventAssignment = eventAssignment;
}
public override void OnSolutionCallback()
{
Console.WriteLine($"Solution ");
foreach (var evt in Enumerable.Range(0,_events.Count))
{
foreach (var student in Enumerable.Range(0, _students.Count))
{
if (Value(_eventAssignment[evt, student]) == 1L)
{
}
}
}
}
}
public Team[] Solve()
{
// Model.
var model = new CpModel();
// Variables.
var x = new BoolVar[_allEvents.Length, _allStudents.Length];
foreach (var e in _allEvents)
foreach (var s in _allStudents)
x[e, s] = model.NewBoolVar($"eventAssignments[{e},{s}]");
foreach (var preAssignment in _preAssigned)
{
var e = _events.IndexOf(preAssignment.Event);
var s = _students.IndexOf(preAssignment.Student);
model.AddAssumption(x[e, s]);
}
foreach (var prohibit in _prohibited)
{
var e = _events.IndexOf(prohibit.Event);
var s = _students.IndexOf(prohibit.Student);
var prohibitVar = new List<IntVar> { x[e, s] };
model.AddLinearConstraint(LinearExpr.Sum(prohibitVar), 0, 0);
prohibitVar.Clear();
}
// Limit the capacity of each event
foreach (var e in _allEvents)
{
var evt = _events[e];
var eventPickCounts = _eventPickCounts[e];
var evtMinTeamSize = evt.MinTeamSize;
var evtMaxTeamSize = evt.MaxTeamSize;
var teamDivs = eventPickCounts / (evtMinTeamSize * 1.0);
if (_includedEvents.Contains(evt))
teamDivs = 1;
//var teamsCount = (int)Math.Ceiling(teamDivs);
var teamCount = (int)Math.Round(teamDivs);
if (teamCount > evt.MaxTeamCountState)
teamCount = evt.MaxTeamCountState;
// limit to one team for group events
if (_parameters.LimitTeamsToOne && evt.Format is EventFormat.Team && teamCount > 1) teamCount = 1;
if (evt.Name == "Tech Bowl")
teamCount = 1;
var eventCapacity = new List<IntVar>();
foreach (var s in _allStudents)
{
eventCapacity.Add(x[e, s]);
}
if (_droppedEvents != null && _droppedEvents.Contains(evt))
teamCount = 0;
var lb = evtMinTeamSize * teamCount;
var ub = Math.Min(evtMaxTeamSize * teamCount, _parameters.TeamSizeLimit);
model.AddLinearConstraint(LinearExpr.Sum(eventCapacity), lb, ub);
Debug.WriteLine($"{evt.Name,30}\t{evt.Format,-10}\t{lb} - {ub}");
model.Minimize(LinearExpr.Sum(eventCapacity));
eventCapacity.Clear();
}
// Limit the number of events a student is assigned
foreach (var s in _allStudents)
{
var student = _students[s];
var studentCapacity = new List<IntVar>();
foreach (var e in _allEvents)
{
studentCapacity.Add(x[e, s]);
}
model.AddLinearConstraint(LinearExpr.Sum(studentCapacity), _parameters.AssignmentLowerBound, _parameters.AssignmentUpperBound);
studentCapacity.Clear();
}
var eventEffortCoefficients = GetEventEffortCoefficients(_events);
foreach (var s in _allStudents)
{
var effortVar = new BoolVar[_allEvents.Length];
foreach (var e in _allEvents)
{
effortVar[e] = x[e, s];
}
var student = _students[s];
var experienceOffset = 0;
switch (student.TsaYear)
{
case 1: experienceOffset = 1; break;
default: break;
}
var ub = _parameters.EffortUpperBound - experienceOffset;
var lb = _parameters.EffortLowerBound;
if (ub <= lb)
lb = ub - 1;
model.Add(LinearExpr.WeightedSum(effortVar, eventEffortCoefficients) >= lb);
model.Add(LinearExpr.WeightedSum(effortVar, eventEffortCoefficients) <= ub);
}
// each student should be assigned at least one on site activity event
foreach (var s in _allStudents)
{
var onSiteActivity = new List<ILiteral>();
foreach (var e in _allEvents)
{
if (_events[e].OnSiteActivity)
onSiteActivity.Add(x[e, s]);
}
if (_parameters.RequireOnSite)
model.AddAtLeastOne(onSiteActivity);
onSiteActivity.Clear();
}
// students should have at maximum one individual event
foreach (var s in _allStudents)
{
var individualEvent = new List<ILiteral>();
foreach (var e in _allEvents)
{
if (_events[e].Format == EventFormat.Individual)
individualEvent.Add(x[e, s]);
}
model.AddAtMostOne(individualEvent);
individualEvent.Clear();
}
// students should have at maximum regional events
foreach (var s in _allStudents)
{
var regionalEvent = new List<ILiteral>();
foreach (var e in _allEvents)
{
if (_events[e].RegionalEvent)
regionalEvent.Add(x[e, s]);
}
if (_parameters.RequireRegional)
model.AddLinearConstraint(LinearExpr.Sum(regionalEvent), 1, 2);
regionalEvent.Clear();
}
var maximizePicks = LinearExpr.NewBuilder();
// optimize student selections
foreach (var s in _allStudents)
{
var eventPickCoefficients = GetEventPickCoefficients(_students[s].RankedEventPicks, _events);
foreach (var e in _allEvents)
{
maximizePicks.AddTerm(x[e, s], eventPickCoefficients[e]);
}
}
model.Maximize(maximizePicks);
var solver = new CpSolver();
var cpSolverStatus = solver.Solve(model);
// print solver status
Console.WriteLine($"Solver status: {cpSolverStatus}");
var eventAssignmentsList = new List<Team>();
if (cpSolverStatus == CpSolverStatus.Optimal || cpSolverStatus == CpSolverStatus.Feasible)
{
foreach (var e in _allEvents)
{
var students = new List<Student>();
foreach (var s in _allStudents)
{
if (solver.BooleanValue(x[e, s]))
{
students.Add(_students[s]);
//Console.WriteLine($"{_events[evt].Name} : {_students[s].Name}");
}
}
if (students.Count > 0)
eventAssignmentsList.Add(new Team(_events[e].Name, _events[e], students));
}
}
else
{
//Console.WriteLine("No solution found.");
}
return eventAssignmentsList.ToArray();
}
private long[] GetEventPickCoefficients(IList<CompetitiveEvent> rankedEvents, IEnumerable<CompetitiveEvent> events)
{
var eventPickCount = rankedEvents.Count;
return
events.Select(e =>
{
var eventPickIndex = rankedEvents.IndexOf(e);
return eventPickIndex switch
{
0 => eventPickCount + 1,
1 => eventPickCount + 0,
> 1 => eventPickCount ,
_ => 0L
};
}).ToArray();
}
private long[] GetEventEffortCoefficients(IEnumerable<CompetitiveEvent> events)
{
return
events.Select(e => e.Name == "Tech Bowl" ? 0 : e.LevelOfEffort.HasValue ? e.LevelOfEffort.Value : 10L).ToArray();
}
}
}
+394
View File
@@ -0,0 +1,394 @@
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
using Core.Entities;
using Google.OrTools.Sat;
using Microsoft.EntityFrameworkCore.Metadata;
using IntVar = Google.OrTools.Sat.IntVar;
namespace Core.Calculation
{
public class EventAssignment
{
private readonly IList<EventDefinition> _events;
private readonly IList<Student> _students;
private readonly AssignmentParameters _parameters;
private readonly int[] _allEvents;
private readonly int[] _allStudents;
// how many students have picked each eventDefinition?
private readonly int[] _eventPickCounts;
private IList<AssignmentRequirement> _assignmentRequirements = new List<AssignmentRequirement>();
private IList<EventDefinition> _droppedEvents = new List<EventDefinition>();
private IList<EventDefinition> _includedEvents = new List<EventDefinition>();
private IList<EventDefinition> _twoTeams = new List<EventDefinition>();
public EventAssignment(IList<EventDefinition> events, IList<Student> students, AssignmentParameters parameters)
{
_events = events;
_students = students;
_parameters = parameters;
_allEvents = Enumerable.Range(0, _events.Count).ToArray();
_allStudents = Enumerable.Range(0, _students.Count).ToArray();
_eventPickCounts = new int[_allEvents.Length];
for (var i = 0; i < _events.Count; i++)
{
var e = _events[i];
_eventPickCounts[i] = _students.Count(s => s.EventRankings.Count(er => er.EventDefinition == e) > 0);
}
}
public void AddAssignmentRequirement(AssignmentRequirement assignmentRequirement)
{
_assignmentRequirements.Add(assignmentRequirement);
}
public void RemoveEvents(IList<EventDefinition> events)
{
_droppedEvents = events;
}
public void IncludedEvents(IList<EventDefinition> events)
{
_includedEvents = events;
}
public void AllowTwoTeams(IList<EventDefinition> events)
{
_twoTeams = events;
}
public async Task<EventAssignmentSolution> Solve()
{
Debug.WriteLine(_parameters);
// Model.
var model = new CpModel();
// Variables.
var x = new BoolVar[_allEvents.Length, _allStudents.Length];
foreach (var e in _allEvents)
foreach (var s in _allStudents)
x[e, s] = model.NewBoolVar($"eventAssignments[{e},{s}]");
AddAssignmentRequirements(model, x);
var assignmentThresholdsList = AddEventAssignmentThresholds(model, x);
LimitStudentAssignment(model, x);
// set the range for level of effort
var eventEffortCoefficients = GetEventEffortCoefficients(_events);
SetLevelOfEffort(model, x, eventEffortCoefficients);
// each student should be assigned at least one on site activity eventDefinition
if (_parameters.RequireOnSite)
RequireOnSiteActivity(model, x);
// students should have at maximum one individual eventDefinition
LimitIndividualEvent(model, x);
// students should have at least one regional event
if (_parameters.RequireRegional)
RequireRegionalEvent(model, x);
OptimizeStudentEventRankings(model, x);
Debug.WriteLine("Starting optimization");
var solver = new CpSolver();
var cpSolverStatus = await Task.Run(() => solver.Solve(model));
// print solver status
Debug.WriteLine($"Solver status: {cpSolverStatus}");
var eventAssignmentsList = GetEventAssignments(x, solver, cpSolverStatus);
var eventAssignmentSolution =
new EventAssignmentSolution
(
eventAssignmentsList.ToArray(),
cpSolverStatus.ToString(),
assignmentThresholdsList
);
return eventAssignmentSolution;
}
private List<Team> GetEventAssignments(BoolVar[,] x, CpSolver solver, CpSolverStatus cpSolverStatus)
{
var eventAssignmentsList = new List<Team>();
if (cpSolverStatus == CpSolverStatus.Optimal || cpSolverStatus == CpSolverStatus.Feasible)
{
foreach (var e in _allEvents)
{
var students = new List<Student>();
foreach (var s in _allStudents)
{
if (solver.BooleanValue(x[e, s]))
{
students.Add(_students[s]);
}
}
if (students.Count > 0)
eventAssignmentsList.Add(new Team { TeamId = _events[e].Name, Event = _events[e], Students = students});
}
}
return eventAssignmentsList;
}
private void OptimizeStudentEventRankings(CpModel model, BoolVar[,] x)
{
var maximizePicks = LinearExpr.NewBuilder();
// optimize student event rankings
foreach (var s in _allStudents)
{
var eventPickCoefficients = GetEventPickCoefficients(_events, _students[s].EventRankings);
foreach (var e in _allEvents)
{
maximizePicks.AddTerm(x[e, s], eventPickCoefficients[e]);
}
}
model.Maximize(maximizePicks);
}
private void RequireRegionalEvent(CpModel model, BoolVar[,] x)
{
foreach (var s in _allStudents)
{
var regionalEvent = new List<ILiteral>();
foreach (var e in _allEvents)
{
if (_events[e].RegionalEvent)
regionalEvent.Add(x[e, s]);
}
//if (_parameters.RequireRegional)
model.AddLinearConstraint(LinearExpr.Sum(regionalEvent), 1, 2);
regionalEvent.Clear();
}
}
private void LimitIndividualEvent(CpModel model, BoolVar[,] x)
{
foreach (var s in _allStudents)
{
var individualEvent = new List<ILiteral>();
foreach (var e in _allEvents)
{
if (_events[e].EventFormat == EventFormat.Individual)
individualEvent.Add(x[e, s]);
}
model.AddAtMostOne(individualEvent);
individualEvent.Clear();
}
}
private void RequireOnSiteActivity(CpModel model, BoolVar[,] x)
{
foreach (var s in _allStudents)
{
var onSiteActivity = new List<ILiteral>();
foreach (var e in _allEvents)
{
if (_events[e].OnSiteActivity)
onSiteActivity.Add(x[e, s]);
}
//if (_parameters.RequireOnSite)
model.AddAtLeastOne(onSiteActivity);
onSiteActivity.Clear();
}
}
private void SetLevelOfEffort(CpModel model, BoolVar[,] x, long[] eventEffortCoefficients)
{
foreach (var s in _allStudents)
{
var effortVar = new BoolVar[_allEvents.Length];
foreach (var e in _allEvents)
{
effortVar[e] = x[e, s];
}
var student = _students[s];
var experienceOffset = 0;
switch (student.TsaYear)
{
case 1: experienceOffset = 1; break;
default: break;
}
var ub = _parameters.EffortUpperBound - experienceOffset;
var lb = _parameters.EffortLowerBound;
if (ub <= lb)
lb = ub - 1;
model.Add(LinearExpr.WeightedSum(effortVar, eventEffortCoefficients) >= lb);
model.Add(LinearExpr.WeightedSum(effortVar, eventEffortCoefficients) <= ub);
}
}
private void LimitStudentAssignment(CpModel model, BoolVar[,] x)
{
// Limit the number of events a student is assigned
foreach (var s in _allStudents)
{
var studentCapacity = new List<IntVar>();
foreach (var e in _allEvents)
{
studentCapacity.Add(x[e, s]);
}
model.AddLinearConstraint(
LinearExpr.Sum(studentCapacity), _parameters.EventsLowerBound, _parameters.EventsUpperBound);
studentCapacity.Clear();
}
}
private List<EventAssignmentThresholds> AddEventAssignmentThresholds(CpModel model, BoolVar[,] x)
{
var assignmentThresholdsList = new List<EventAssignmentThresholds>();
// Limit the capacity of each event
foreach (var e in _allEvents)
{
var evt = _events[e];
var eventPickCounts = _eventPickCounts[e];
var evtMinTeamSize = evt.MinTeamSize;
var evtMaxTeamSize = evt.MaxTeamSize;
var teamDivs = eventPickCounts / (evtMinTeamSize * 1.25);
if (_includedEvents.Contains(evt))
teamDivs = 1;
//var teamsCount = (int)Math.Ceiling(teamDivs);
var teamCount = (int)Math.Round(teamDivs);
if (teamCount > evt.MaxTeamCountState)
teamCount = evt.MaxTeamCountState;
// limit to one team for group events
if (_parameters.LimitTeamsToOne
&& evt.EventFormat is EventFormat.Team
&& teamCount > 1
&& !_twoTeams.Contains(evt)
)
teamCount = 1;
if (evt.Name == "Tech Bowl")
teamCount = 1;
var eventCapacity = new List<IntVar>();
foreach (var s in _allStudents)
{
eventCapacity.Add(x[e, s]);
}
if (_droppedEvents != null && _droppedEvents.Contains(evt))
teamCount = 0;
var lb = evtMinTeamSize * teamCount;
var ub = Math.Min(evtMaxTeamSize * teamCount, _parameters.TeamSizeLimit * teamCount);
assignmentThresholdsList.Add(
new EventAssignmentThresholds
{
Event = evt,
TeamCount = teamCount,
LowerBound = evtMinTeamSize,
UpperBound = evtMaxTeamSize,
StudentRankingCount = eventPickCounts
});
model.AddLinearConstraint(LinearExpr.Sum(eventCapacity), lb, ub);
Debug.WriteLine($"{evt.Name,30}\t{evt.EventFormat,-10}\t{lb} - {ub}");
model.Minimize(LinearExpr.Sum(eventCapacity));
eventCapacity.Clear();
}
return assignmentThresholdsList;
}
private void AddAssignmentRequirements(CpModel model, BoolVar[,] x)
{
foreach (var includedAssignment in _assignmentRequirements.Where(e => e.Requirement == Requirement.Include))
{
var e = _events.IndexOf(includedAssignment.EventDefinition);
var s = _students.IndexOf(includedAssignment.Student);
model.AddAssumption(x[e, s]);
}
foreach (var excludedAssignment in _assignmentRequirements.Where(e => e.Requirement == Requirement.Exclude))
{
var e = _events.IndexOf(excludedAssignment.EventDefinition);
var s = _students.IndexOf(excludedAssignment.Student);
var prohibitVar = new List<IntVar> { x[e, s] };
model.AddLinearConstraint(LinearExpr.Sum(prohibitVar), 0, 0);
prohibitVar.Clear();
}
}
private static long[] GetEventPickCoefficients(IList<EventDefinition> events, List<StudentEventRanking> eventRankings)
{
return
events.Select(e =>
{
var eventRank = eventRankings.FirstOrDefault(er => er.EventDefinition == e);
return
eventRank == null
? 0L
: StudentEventRanking.MaxRank - eventRank.Rank; // inverse
}).ToArray();
}
private long[] GetEventEffortCoefficients(IEnumerable<EventDefinition> events)
{
return
events.Select(
e =>
//e.Name == "Tech Bowl"
// ? 0 // Tech bowl gets requires no effort?
//:
e.LevelOfEffort ?? 10L
).ToArray();
}
public class SolutionPrinter : CpSolverSolutionCallback
{
private readonly IList<EventDefinition> _events;
private readonly IList<Student> _students;
private readonly BoolVar[,] _eventAssignment;
public SolutionPrinter(IList<EventDefinition> events, IList<Student> students, BoolVar[,] eventAssignment)
{
_events = events;
_students = students;
_eventAssignment = eventAssignment;
}
public override void OnSolutionCallback()
{
Console.WriteLine($"Solution ");
foreach (var evt in Enumerable.Range(0, _events.Count))
{
foreach (var student in Enumerable.Range(0, _students.Count))
{
if (Value(_eventAssignment[evt, student]) == 1L)
{
}
}
}
}
}
}
}
@@ -0,0 +1,13 @@
using Core.Entities;
namespace Core.Calculation;
public class EventAssignmentSolution(
Team[] teams,
string status,
List<EventAssignmentThresholds> assignmentThresholds)
{
public Team[] Teams { get; set; } = teams;
public string Status { get; set; } = status;
public List<EventAssignmentThresholds> AssignmentThresholds { get; } = assignmentThresholds;
}
@@ -0,0 +1,12 @@
using Core.Entities;
namespace Core.Calculation;
public class EventAssignmentThresholds
{
public EventDefinition Event { get; set; }
public int TeamCount { get; set; }
public int LowerBound { get; set; }
public int UpperBound { get; set; }
public int StudentRankingCount { get; set; }
}
@@ -0,0 +1,10 @@
namespace Core.Calculation;
public enum UnassignedScheduleStrategy
{
BiggestGroup,
IndividualEvents,
AnyNotMeetingAlready,
LevelOfEffort,
Any
}
+5 -14
View File
@@ -2,15 +2,6 @@
namespace Core.Calculation;
public enum UnassignedScheduleStrategy
{
BiggestGroup,
IndividualEvents,
AnyNotMeetingAlready,
LevelOfEffort,
Any
}
public class UnassignedStudentScheduler
{
private readonly IList<Student> _students;
@@ -86,21 +77,21 @@ public class UnassignedStudentScheduler
_teams
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
.Where(t => t.Students.Count > 1) //|| t.Event.Format is EventFormat.Individual
.Where(t => t.Students.Count > 1) //|| t.Event.EventFormat is EventFormat.Individual
//.OrderBy(t => scheduledTeams.Count(st => st.Name == t.Name))
.OrderByDescending(t => t.Students.Count); // select descending greatest number of students assigned
//.ThenBy(t => Random.Shared.Next()); // todo: sort by student historic record of event assignment
//.ThenBy(t => Random.Shared.Next()); // todo: sort by student historic record of eventDefinition assignment
// find individual events unassigned students can work on
private IEnumerable<Team> GetAvailableTeams_Individual(
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
_teams
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
.Where(t => t.Event.Format == EventFormat.Individual || t.Students.Count == 1)
.Where(t => t.Event.EventFormat == EventFormat.Individual || t.Students.Count == 1)
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
.Where(t => t.Students.Count > 0);
// find any unassigned event students can work on
// find any unassigned eventDefinition students can work on
private IEnumerable<Team> GetAvailableTeams_AnyNotMeetingAlready(
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
_teams
@@ -121,7 +112,7 @@ public class UnassignedStudentScheduler
_teams
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
.Where(t => t.Students.Count > 1) //|| t.Event.Format is EventFormat.Individual
.Where(t => t.Students.Count > 1) //|| t.Event.EventFormat is EventFormat.Individual
//.OrderBy(t => scheduledTeams.Count(st => st.Name == t.Name))
.OrderByDescending(t => t.Event.LevelOfEffort); // select descending greatest number of students assigned
+3 -2
View File
@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CsvHelper" Version="30.0.1" />
<PackageReference Include="CsvHelper" Version="33.1.0" />
<PackageReference Include="FuzzySharp" Version="2.0.2" />
<PackageReference Include="Google.OrTools" Version="9.7.2996" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
</ItemGroup>
</Project>
-10
View File
@@ -1,10 +0,0 @@
namespace Core.Entities;
public class AssignmentAssumption(CompetitiveEvent @event, Student student, Assumption assumption)
{
public CompetitiveEvent Event { get; } = @event;
public Student Student { get; } = student;
public Assumption Assumption { get; } = assumption;
public EventAssignment EventAssignment => new(@event, student);
}
+14 -5
View File
@@ -3,8 +3,8 @@
public class AssignmentParameters(
int effortLowerBound = 6,
int effortUpperBound = 8,
int assignmentLowerBound = 2,
int assignmentUpperBound = 4,
int eventsLowerBound = 2,
int eventsUpperBound = 4,
int teamSizeLimit = 4,
bool limitTeamsToOne = true,
bool requireRegional = true,
@@ -12,11 +12,20 @@
{
public int EffortLowerBound { get; set; } = effortLowerBound;
public int EffortUpperBound { get; set; } = effortUpperBound;
public int AssignmentLowerBound { get; set; } = assignmentLowerBound;
public int AssignmentUpperBound { get; set; } = assignmentUpperBound;
public int EventsLowerBound { get; set; } = eventsLowerBound;
public int EventsUpperBound { get; set; } = eventsUpperBound;
public int TeamSizeLimit { get; set; } = teamSizeLimit;
public bool LimitTeamsToOne { get; set; } = limitTeamsToOne;
public bool RequireRegional { get; set; } = requireRegional;
public bool RequireOnSite { get; set; } = requireOnSite;
}
public override string ToString()
{
return $"Team Size Limit: {TeamSizeLimit}" + Environment.NewLine +
$"Require Regional: {RequireRegional}" + Environment.NewLine +
$"Require On-site: {RequireOnSite}" + Environment.NewLine +
$"Events Range: [{EventsLowerBound}-{EventsUpperBound}]" + Environment.NewLine +
$"Effort Range: [{EffortLowerBound}-{EffortUpperBound}]";
}
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace Core.Entities;
public class AssignmentRequirement(EventDefinition eventDefinition, Student student, Requirement requirement)
{
public EventDefinition EventDefinition { get; } = eventDefinition;
public Student Student { get; } = student;
public Requirement Requirement { get; } = requirement;
}
-71
View File
@@ -1,71 +0,0 @@
namespace Core.Entities;
public class CompetitiveEvent
{
public string Name { get; set; }
public string ShortName { get; set; }
public EventFormat Format { get; set; }
public int MinTeamSize { get; set; }
public int MaxTeamSize { get; set; }
public string TeamSize =>
MinTeamSize == MaxTeamSize
? MinTeamSize.ToString()
: $"{MinTeamSize.ToString()}-{MaxTeamSize.ToString()}";
public string SemifinalistActivity { get; set; }
public bool InterviewOrPresentation
=> SemifinalistActivity.Contains("Interview") || SemifinalistActivity.Contains("Presentation");
public bool OnSiteActivity
=> SemifinalistActivity.Contains("Challenge")
|| SemifinalistActivity.Contains("Race")
|| SemifinalistActivity.Contains("Speech")
|| SemifinalistActivity.Contains("Test")
|| SemifinalistActivity.Contains("Flight")
|| SemifinalistActivity.Contains("Debate")
|| SemifinalistActivity.Contains("Photography")
|| SemifinalistActivity.Contains("Build")
|| Name.Contains("Chapter")
|| Name.Contains("Essay")
|| SemifinalistActivity.Contains("Fly");
public string RegionalNotes { get; set; }
public int MaxTeamCountState { get; set; }
public bool RegionalEvent { get; set; }
public bool RegionalPresubmit { get; set; }
public bool StatePresubmission { get; set; }
public bool StatePretesting { get; set; }
public bool StatePreliminaryRound { get; set; }
public string Documentation { get; set; }
public string Eligibility { get; set; }
public string Theme { get; set; }
public string Description { get; set; }
public int? LevelOfEffort { get; set; }
public override string ToString()
{
return Name;
}
public static readonly CompetitiveEvent GeneralSchedule = new(){Name = "General Schedule"};
public static readonly CompetitiveEvent VotingDelegates = new(){Name = "Voting Delegates"};
public string EventAttributes ()
{
var st = new List<string>();
if (Format is EventFormat.Individual)
st.Add( "Ind.");
if (RegionalEvent)
st.Add( "Reg.");
return string.Join(", ", st);
}
}
+3 -9
View File
@@ -1,13 +1,7 @@
namespace Core.Entities;
public class EventAssignment
public class EventAssignment(EventDefinition eventDefinition, Student student)
{
public CompetitiveEvent Event { get; }
public Student Student { get; }
public EventAssignment(CompetitiveEvent @event, Student student)
{
Event = @event;
Student = student;
}
public EventDefinition EventDefinition { get; } = eventDefinition;
public Student Student { get; } = student;
}
+80
View File
@@ -0,0 +1,80 @@
using System.ComponentModel.DataAnnotations;
namespace Core.Entities;
public class EventDefinition
{
public int Id { get; set; }
public string Name { get; set; }
public string? ShortName { get; set; }
public EventFormat EventFormat { get; set; }
[Range(1, 6)]
public int MinTeamSize { get; set; }
[Range(1, 6)]
public int MaxTeamSize { get; set; }
public string TeamSize =>
MinTeamSize == MaxTeamSize
? MinTeamSize.ToString()
: $"{MinTeamSize.ToString()}-{MaxTeamSize.ToString()}";
public string? SemifinalistActivity { get; set; }
public bool InterviewOrPresentation
=> SemifinalistActivity != null && (SemifinalistActivity.Contains("Interview") || SemifinalistActivity.Contains("Presentation"));
public bool OnSiteActivity
=> SemifinalistActivity != null
&& (SemifinalistActivity.Contains("Challenge")
|| SemifinalistActivity.Contains("Race")
|| SemifinalistActivity.Contains("Speech")
|| SemifinalistActivity.Contains("Test")
|| SemifinalistActivity.Contains("Flight")
|| Name.Contains("Flight")
|| SemifinalistActivity.Contains("Debate")
|| SemifinalistActivity.Contains("Photography")
|| SemifinalistActivity.Contains("Build")
|| Name.Contains("Chapter")
|| Name.Contains("Podcast"));
public string? Notes { get; set; }
[Range(1, 3)]
public int MaxTeamCountState { get; set; }
public bool RegionalEvent { get; set; }
public bool RegionalPresubmit { get; set; }
public bool StatePresubmission { get; set; }
public bool StatePretesting { get; set; }
public bool StatePreliminaryRound { get; set; }
public string? Documentation { get; set; }
public string Eligibility { get; set; }
public string? Theme { get; set; }
public string? Description { get; set; }
public int? LevelOfEffort { get; set; }
public override string ToString()
{
return Name;
}
public static readonly EventDefinition GeneralSchedule = new(){Name = "General Schedule"};
public static readonly EventDefinition VotingDelegates = new(){Name = "Voting Delegates"};
public string EventAttributes ()
{
var st = new List<string>();
if (EventFormat is EventFormat.Individual)
st.Add( "Ind.");
if (RegionalEvent)
st.Add( "Reg.");
return string.Join(", ", st);
}
}
-13
View File
@@ -1,13 +0,0 @@
namespace Core.Entities;
public class EventStudentPicks
{
public CompetitiveEvent Event { get; }
public IList<Tuple<Student,int>> StudentPicks { get; }
public EventStudentPicks(CompetitiveEvent @event, IList<Tuple<Student, int>> studentPicks)
{
Event = @event;
StudentPicks = studentPicks;
}
}
+15
View File
@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
namespace Core.Entities;
public enum OfficerRole
{
President=1,
[Display(Name = "Vice President")]
VicePresident=2,
Secretary=3,
Treasurer=4,
Reporter=5,
[Display(Name = "Sergeant at Arms")]
SergeantAtArms =6
}
+3 -8
View File
@@ -2,17 +2,12 @@
public class PartialTeam : Team
{
public IList<Student> OmittedStudents { get; }
public IList<Student> OmittedStudents { get; set; }
public PartialTeam(string name, CompetitiveEvent @event, IList<Student> students, IList<Student> omittedStudents) : base(name, @event, students)
{
OmittedStudents = omittedStudents;
}
public override Team CloneWithOmittedStudents(IEnumerable<Student> studentsToOmit)
public override Team CloneWithOmittedStudents(IEnumerable<Student> studentsToOmit)
{
var remainingStudents = Students.Where(s => !studentsToOmit.Contains(s)).ToList();
var omittedStudents = OmittedStudents.Union(Students.Where(studentsToOmit.Contains)).Distinct().ToList();
return new PartialTeam(Name, Event, remainingStudents, omittedStudents );
return new PartialTeam{TeamId = Name, Event = Event, Students = remainingStudents, OmittedStudents = omittedStudents };
}
}
@@ -1,6 +1,6 @@
namespace Core.Entities;
public enum Assumption
public enum Requirement
{
Include,
Exclude
+62 -59
View File
@@ -1,74 +1,77 @@
using System.Text.RegularExpressions;
using System.ComponentModel.DataAnnotations;
using static System.Text.RegularExpressions.Regex;
namespace Core.Entities;
public class Student
{
public string Name { get; set; }
public int Id { get; set; }
public string LastNameFirstName
[Required]
[StringLength(50,MinimumLength = 2)]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[StringLength(50, MinimumLength = 2)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Range(5,12)]
[Display(Name = "Grade")]
public int Grade { get; set; }
[EmailAddress]
[Display(Name = "Email Address")]
public string? Email { get; set; }
[Phone]
[Display(Name = "Phone Number")]
public string? PhoneNumber { get; set; }
[Display(Name = "TSA Year")]
public int TsaYear { get; set; }
[Display(Name = "State Id")]
public string? StateId { get; set; }
[Display(Name = "Regional Id")]
public string? RegionalId { get; set; }
[Display(Name = "National Id")]
public string? NationalId { get; set; }
[Display(Name = "Officer Role")]
public OfficerRole? OfficerRole { get; set; }
public List<Team> Teams { get; set; } = null;
public List<EventDefinition> RankedEvents { get; } = [];
public List<StudentEventRanking> EventRankings { get; } = [];
public string Name => FirstNameLastName;
public string LastNameFirstName => $"{LastName}, {FirstName}";
public string FirstNameLastName => $"{FirstName} {LastName}";
public static Tuple<string, string> ParseNameParts(string fullName)
{
get
{
if (Name.Contains(',')) return Name;
var match = Regex.Match(Name, @"(.*)\s(.*)");
if (match.Success)
return $"{match.Groups[2].Value}, {match.Groups[2].Value} ";
return Name;
}
var match = Match(fullName, @"(.*),\s*(.*)");
if (match.Success)
return Tuple.Create(match.Groups[2].Value, match.Groups[1].Value);
match = Match(fullName, @"(.*)\s*(.*)");
return
match.Success
? Tuple.Create(match.Groups[1].Value, match.Groups[2].Value)
: new Tuple<string, string>(fullName, string.Empty);
}
public string FirstNameLastName
{
get
{
if (!Name.Contains(',')) return Name;
var match = Regex.Match(Name, @"(.*),\s*(.*)");
if (match.Success)
return $"{match.Groups[2].Value} {match.Groups[1].Value}";
return Name;
}
}
public string FirstName
{
get
{
var match = Regex.Match(LastNameFirstName, @"(.*),\s*(.*)");
if (match.Success)
return $"{match.Groups[2].Value}";
return Name;
}
}
public int Grade { get; }
public string StateID { get; }
public string RegionalID { get; }
public string NationalID { get; }
public int TsaYear { get; }
public string Officer { get; }
public IList<CompetitiveEvent> RankedEventPicks { get; }
public ICollection<Team> Teams { get; set; }
public Student(string name, int grade, int tsaYear, string officer, IList<CompetitiveEvent> rankedEventPicks,
string stateID, string regionalID, string nationalId)
{
Name = name;
Grade = grade;
TsaYear = tsaYear;
Officer = officer;
RankedEventPicks = rankedEventPicks;
StateID=stateID;
RegionalID = regionalID;
NationalID = nationalId;
}
public override string ToString()
{
return FirstName;
}
public bool VotingDelegate => Officer.Contains("Pres");
public bool VotingDelegate => OfficerRole is Entities.OfficerRole.President or Entities.OfficerRole.VicePresident;
}
+12
View File
@@ -0,0 +1,12 @@
namespace Core.Entities;
public class StudentEventRanking
{
public Student Student { get; set; } = null!;
public EventDefinition EventDefinition { get; set; } = null!;
public int Rank { get; set; }
public const int MaxRank = 6;
}
+32
View File
@@ -0,0 +1,32 @@
namespace Core.Entities;
public class StudentEventStatistics
{
public Student Student { get; set; }
public List<EventDefinition> Events { get; set; } = [];
public int? TotalLevelOfEffort => Events.Sum(e => e.LevelOfEffort);
public int EventCount => Events.Count;
public bool HasRegionalEvent => Events.Any(e => e.RegionalEvent);
public bool HasOnSiteActivity => Events.Any(e => e.OnSiteActivity);
public static List<StudentEventStatistics> Generate(ICollection<Team> teams)
{
Dictionary<Student, StudentEventStatistics> statistics = [];
foreach (var team in teams)
{
foreach (var student in team.Students)
{
if (!statistics.ContainsKey(student))
statistics.Add(student, new StudentEventStatistics(){Student = student});
statistics[student].Events.Add(team.Event);
}
}
return statistics.Values.ToList();
}
}
+35 -41
View File
@@ -1,40 +1,44 @@
using System.Reflection;
using System.Text.RegularExpressions;
using System.ComponentModel.DataAnnotations;
namespace Core.Entities;
public class Team
{
public string Name { get; }
public CompetitiveEvent Event { get; }
public IList<Student> Students { get; }
public int Id { get; set; }
public Student? Captain { get; set; }
[Required]
[Display(Name = "Team Name")]
public string Name { get; set; }
public EventDefinition Event { get; set; }
public List<Student> Students { get; set; } = [];
public string TeamNumber { get; set; }
public Student? Captain { get; set; }
public string RegionalTimeSlot { get; set; }
[Display(Name = "Team Id")]
public string? TeamId { get; set; }
public Tuple<DateTime,DateTime?>? RegionalTimeSlotObj
{
get
{
//public string? RegionalTimeSlot { get; set; }
// public Tuple<DateTime,DateTime?>? RegionalTimeSlotObj
//{
// get
// {
if (string.IsNullOrEmpty(RegionalTimeSlot))
return null;
var times = Regex.Matches(RegionalTimeSlot, @"(.*)\s*-\s*(.*)");
if (times.Count == 0)
return Tuple.Create(P(RegionalTimeSlot), (DateTime?)null);
var match = times[0];
if (!match.Success)
return Tuple.Create(P(RegionalTimeSlot), (DateTime?)null);
// if (string.IsNullOrEmpty(RegionalTimeSlot))
// return null;
// var times = Regex.Matches(RegionalTimeSlot, @"(.*)\s*-\s*(.*)");
// if (times.Count == 0)
// return Tuple.Create(P(RegionalTimeSlot), (DateTime?)null);
// var match = times[0];
// if (!match.Success)
// return Tuple.Create(P(RegionalTimeSlot), (DateTime?)null);
return Tuple.Create(P(match.Groups[1].Value), (DateTime?)P(match.Groups[2].Value));
return null;
}
}
// return Tuple.Create(P(match.Groups[1].Value), (DateTime?)P(match.Groups[2].Value));
// return null;
// }
//}
private DateTime P(string s)
private DateTime P(string s)
{
var dt = DateTime.Parse(s);
if (dt.TimeOfDay < TimeSpan.FromHours(7))
@@ -42,28 +46,18 @@ public class Team
return dt;
}
public Team(string name, CompetitiveEvent @event, IList<Student> students, Student? captain = null, string teamNumber = null, string regionalTimeSlot = null)
{
Name = name;
Event = @event;
Students = students;
Captain = captain;
TeamNumber = teamNumber;
RegionalTimeSlot = regionalTimeSlot;
}
public virtual Team CloneWithOmittedStudents(IEnumerable<Student> studentsToOmit)
{
var studentsToOmitList = studentsToOmit.ToList();
var omittedStudents = Students.Where(studentsToOmitList.Contains).ToList();
if (!omittedStudents.Any())
return new Team(Name, Event, Students.ToList(), Captain);
return new Team{Captain = Captain, Event = Event, Students = Students.ToList(), TeamId = Name};
var remainingStudents = Students.Where(s => !studentsToOmitList.Contains(s)).ToList();
return new PartialTeam(Name, Event, remainingStudents, omittedStudents);
return new PartialTeam { Name = Name, Event = Event, Students = remainingStudents, OmittedStudents = omittedStudents};
}
public Team Clone() => CloneWithOmittedStudents(Array.Empty<Student>());
public Team Clone() => CloneWithOmittedStudents([]);
public static int GetStudentTeamOverlapCount(IList<Team>[] timeSlots)
{
@@ -75,7 +69,7 @@ public class Team
return GetStudentTeamOverlaps(timeSlot).Count();
}
public static IEnumerable<Tuple<Student, IEnumerable<Team>>> GetStudentTeamOverlaps(IList<Team> timeSlot)
public static IEnumerable<Tuple<Student, IEnumerable<Team>>> GetStudentTeamOverlaps(IList<Team> timeSlot)
{
return
from s in timeSlot.SelectMany(ts => ts.Students).Distinct()
@@ -92,7 +86,7 @@ public class Team
public string ToStringWithIndividualAndRegional()
{
var ind = Event.Format is EventFormat.Individual ? " (Ind.)" : string.Empty;
var ind = Event.EventFormat is EventFormat.Individual ? " (Ind.)" : string.Empty;
var regional= Event.RegionalEvent ? " (Reg.)" : string.Empty;
//var regional= Event.RegionalEvent ? " (Reg.)" : string.Empty;
@@ -2,15 +2,15 @@
namespace Core.Parsers;
public class AssignmentAssumptionParser : CsvParserBase
public class AssignmentRequirementParser : CsvParserBase
{
public AssignmentAssumptionParser(FileSystemInfo csvFile, bool ignoreBlankLines = true) : base(csvFile, ignoreBlankLines)
public AssignmentRequirementParser(FileSystemInfo csvFile, bool ignoreBlankLines = true) : base(csvFile, ignoreBlankLines)
{
}
public AssignmentAssumption[] Parse(ICollection<CompetitiveEvent> events, ICollection<Student> students)
public AssignmentRequirement[] Parse(ICollection<EventDefinition> events, ICollection<Student> students)
{
var assumptions = new List<AssignmentAssumption>();
var assumptions = new List<AssignmentRequirement>();
CsvReader.Read();
CsvReader.ReadHeader();
@@ -27,7 +27,7 @@ public class AssignmentAssumptionParser : CsvParserBase
var evt = events.FirstOrDefault(e => e.ShortName == eventShortName);
if (evt == null)
throw new Exception($"Could not find event named {eventShortName}");
throw new Exception($"Could not find eventDefinition named {eventShortName}");
for (int i = 0; i <= studentArray.Length; i++)
{
var field = CsvReader.GetField(i + 1);
@@ -35,11 +35,11 @@ public class AssignmentAssumptionParser : CsvParserBase
{
case "x":
case "X":
assumptions.Add(new AssignmentAssumption(evt, studentArray[i], Assumption.Exclude));
assumptions.Add(new AssignmentRequirement(evt, studentArray[i], Requirement.Exclude));
break;
case "i":
case "I":
assumptions.Add(new AssignmentAssumption(evt, studentArray[i], Assumption.Include));
assumptions.Add(new AssignmentRequirement(evt, studentArray[i], Requirement.Include));
break;
default:
break;
+6 -2
View File
@@ -10,9 +10,13 @@ public class CsvParserBase : IDisposable
//private readonly MemoryStream _memoryStream;
protected readonly CsvReader CsvReader;
protected CsvParserBase(FileSystemInfo csvFile, bool ignoreBlankLines)
protected CsvParserBase(FileSystemInfo csvFile, bool ignoreBlankLines) : this(OpenCsv(csvFile), ignoreBlankLines)
{
_reader = OpenCsv(csvFile);
}
protected CsvParserBase(StreamReader reader, bool ignoreBlankLines)
{
_reader = reader;
CsvReader = InitCsvReader(_reader, ignoreBlankLines);
}
+11 -7
View File
@@ -9,21 +9,25 @@ public class EventDefinitionParser : CsvParserBase
{
}
public CompetitiveEvent[] Parse()
public EventDefinitionParser(StreamReader reader, bool ignoreBlankLines = true) : base(reader, ignoreBlankLines)
{
var events = new List<CompetitiveEvent>();
}
public EventDefinition[] Parse()
{
var events = new List<EventDefinition>();
CsvReader.Read();
CsvReader.ReadHeader();
while (CsvReader.Read())
while (CsvReader.Read())
{
var name = CsvReader.GetField("Event");
if (string.IsNullOrEmpty(name))
continue;
var shortName = CsvReader.GetField("Short Name");
Enum.TryParse(CsvReader.GetField("Format"), out EventFormat format);
Enum.TryParse(CsvReader.GetField("EventFormat"), out EventFormat format);
var teamSize = CsvReader.GetField("Team Size");
if (string.IsNullOrEmpty(teamSize))
@@ -47,18 +51,18 @@ public class EventDefinitionParser : CsvParserBase
var levelOfEffort = CsvReader.GetField<int?>("Level of Effort");
//var regionalTeams = CsvReader.GetField<int>("Regional Teams");
var competitiveEvent = new CompetitiveEvent
var competitiveEvent = new EventDefinition
{
Name = name.Trim(),
ShortName = shortName.Trim(),
Format = format,
EventFormat = format,
MaxTeamCountState = stateTeams,
MinTeamSize = min,
MaxTeamSize = max,
SemifinalistActivity = semifinalistActivity,
RegionalEvent = !string.IsNullOrEmpty(regionalCount),
RegionalPresubmit = regionalPresubmit.Trim() == "TRUE",
RegionalNotes = regionalNotes,
Notes = regionalNotes,
Documentation= documentation,
StatePresubmission = statePresubmission.Trim() == "TRUE",
StatePretesting = statePretesting.Trim() == "TRUE",
+12 -12
View File
@@ -7,9 +7,9 @@ namespace Core.Parsers;
public class EventOccurrenceParser
{
private FileSystemInfo _txtFile;
private ICollection<CompetitiveEvent> _events;
private ICollection<EventDefinition> _events;
public EventOccurrenceParser(FileSystemInfo txtFile, ICollection<CompetitiveEvent> events)
public EventOccurrenceParser(FileSystemInfo txtFile, ICollection<EventDefinition> events)
{
_events = events;
_txtFile = txtFile;
@@ -28,10 +28,10 @@ public class EventOccurrenceParser
private readonly Regex _timeLocationRegex = new(@"(?<Time>.*(?>[AaPp]\.?[Mm]\.?))(?<Location>[\s\t].*)?");
public IDictionary<CompetitiveEvent, List<EventOccurrence>> Parse()
public IDictionary<EventDefinition, List<EventOccurrence>> Parse()
{
var occurrences = new Dictionary<CompetitiveEvent, List<EventOccurrence>>();
CompetitiveEvent currentEvent = null;
var occurrences = new Dictionary<EventDefinition, List<EventOccurrence>>();
EventDefinition currentEventDefinition = null;
var lines = File.ReadLines(_txtFile.FullName);
foreach (var line in lines)
@@ -49,25 +49,25 @@ public class EventOccurrenceParser
select e).FirstOrDefault();
if (evt == null)
continue;
currentEvent = evt;
currentEventDefinition = evt;
continue;
}
if (line == "General Schedule")
{
currentEvent = CompetitiveEvent.GeneralSchedule;
currentEventDefinition = EventDefinition.GeneralSchedule;
continue;
}
if (line == "Voting Delegates")
{
currentEvent = CompetitiveEvent.VotingDelegates;
currentEventDefinition = EventDefinition.VotingDelegates;
continue;
}
continue;
}
if (currentEvent == null)
if (currentEventDefinition == null)
continue;
var occurrenceName = match.Groups["Name"].Captures[0].Value;
@@ -103,9 +103,9 @@ public class EventOccurrenceParser
Location = location
};
if (!occurrences.ContainsKey(currentEvent))
occurrences.Add(currentEvent, []);
occurrences[currentEvent].Add(eventOccurrence);
if (!occurrences.ContainsKey(currentEventDefinition))
occurrences.Add(currentEventDefinition, []);
occurrences[currentEventDefinition].Add(eventOccurrence);
}
return occurrences;
+77
View File
@@ -0,0 +1,77 @@
using Core.Entities;
using FuzzySharp;
namespace Core.Parsers;
public class StudentEventRankingParser : CsvParserBase
{
public StudentEventRankingParser(FileSystemInfo csvFile, bool ignoreBlankLines = true) : base(csvFile, ignoreBlankLines)
{
}
public StudentEventRanking[] Parse(ICollection<Student> students, ICollection<EventDefinition> events)
{
var rankings = new List<StudentEventRanking>();
CsvReader.Read();
CsvReader.ReadHeader();
while (CsvReader.Read())
{
var name = CsvReader.GetField("Student Name");
if (string.IsNullOrEmpty(name))
continue;
var student = students.FirstOrDefault(s => Fuzz.Ratio(s.FirstNameLastName, name) > 90);
if (student == null)
continue;
var competitiveEvents = new List<EventDefinition>(6);
for (var i = 1; i <= 6; i++)
{
var eventName = CsvReader.GetField(i.ToString());
if (string.IsNullOrEmpty(eventName) || eventName == "") continue;
eventName = eventName.Trim();
if (eventName == "I&I")
eventName = "Inventions & Innovations";
if (eventName == "Med Tech")
eventName = "Medical Technology";
if (eventName.StartsWith("Challenging Tech"))
eventName = "Challenging Technology Issues";
var matches =
(from e in events
let rat = Fuzz.Ratio(e.Name, eventName)
where rat > 90
orderby rat descending
select e).ToList();
if (!matches.Any())
{
matches =
(from e in events
where e.Name.StartsWith(eventName)
select e).ToList();
}
var competitiveEvent = matches.FirstOrDefault();
if (competitiveEvent == null)
{
//todo: throw new ArgumentException($"Event named '{eventName}' not found");
continue;
}
rankings.Add(new StudentEventRanking{
Student = student, EventDefinition = competitiveEvent,
Rank = i});
}
}
return rankings.ToArray();
}
}
+21 -58
View File
@@ -1,6 +1,4 @@
using Core.Entities;
using FuzzySharp;
namespace Core.Parsers;
public class StudentParser : CsvParserBase
@@ -9,7 +7,11 @@ public class StudentParser : CsvParserBase
{
}
public Student[] Parse(ICollection<CompetitiveEvent> events)
public StudentParser(StreamReader reader, bool ignoreBlankLines = true) : base(reader, ignoreBlankLines)
{
}
public Student[] Parse()
{
var s = new List<Student>();
@@ -22,65 +24,26 @@ public class StudentParser : CsvParserBase
if (string.IsNullOrEmpty(name))
continue;
var stateID = CsvReader.GetField("State ID").Trim();
var regionalID = CsvReader.GetField("Regional ID").Trim();
var nationalID = CsvReader.GetField("National ID").Trim();
var gr = CsvReader.GetField("Grade");
var (firstName, lastName) = Student.ParseNameParts(name);
var stateId = CsvReader.GetField("State ID")?.Trim();
var regionalId = CsvReader.GetField("Regional ID")?.Trim();
var nationalId = CsvReader.GetField("National ID")?.Trim();
var grade = CsvReader.GetField("Grade");
var tsaYearsStr = CsvReader.GetField("TSA year");
var tsaYear = int.Parse(tsaYearsStr?[..1] ?? "1");
var officer = CsvReader.GetField("Officer");
var competitiveEvents = new List<CompetitiveEvent>(6);
for (var i = 1; i <= 6; i++)
var student = new Student
{
var eventName = CsvReader.GetField(i.ToString());
if (string.IsNullOrEmpty(eventName) || eventName == "") continue;
eventName = eventName.Trim();
if (eventName == "I&I")
eventName = "Inventions & Innovations";
if (eventName == "Med Tech")
eventName = "Medical Technology";
if (eventName.StartsWith("Challenging Tech"))
eventName = "Challenging Technology Issues";
var matches =
(from e in events
let rat = Fuzz.Ratio(e.Name, eventName)
where rat > 90
orderby rat descending
select e).ToList();
if (!matches.Any())
{
matches =
(from e in events
where e.Name.StartsWith(eventName)
select e).ToList();
}
var competitiveEvent = matches.FirstOrDefault();
if (competitiveEvent == null)
{
//todo: throw new ArgumentException($"Event named '{eventName}' not found");
continue;
}
competitiveEvents.Add(competitiveEvent);
}
if (!competitiveEvents.Any())
continue;
var student = new Student(
name.Trim(),
Convert.ToInt32(gr),
tsaYear,
officer?.Trim(),
competitiveEvents, stateID, regionalID, nationalID);
FirstName = firstName,
LastName = lastName,
Grade = Convert.ToInt32(grade),
TsaYear = tsaYear,
StateId = stateId,
RegionalId = regionalId,
NationalId = nationalId
};
s.Add(student);
}
+7 -8
View File
@@ -56,7 +56,7 @@ namespace Core.Parsers
{
}
public Team[] Parse(ICollection<CompetitiveEvent> events, ICollection<Student> students)
public Team[] Parse(ICollection<EventDefinition> events, ICollection<Student> students)
{
var teams = new List<Team>();
@@ -139,22 +139,21 @@ namespace Core.Parsers
var teamNumber = string.Empty;
if (teamName.EndsWith("Team 2"))
teamNumber = "12227-2";
else if (@event.Format == EventFormat.Team)
else if (@event.EventFormat == EventFormat.Team)
teamNumber = "2227";
if (teamStudents.Count > 0)
{
if (@event.Format is EventFormat.Team)
if (@event.EventFormat is EventFormat.Team)
{
teams.Add(new Team(teamName, @event, teamStudents, captain, teamNumber,
regionalTimeSlot: regionalTimeSlot));
teams.Add(new Team { Event = @event, Students = teamStudents, Captain = captain, Name = teamName, TeamId = teamNumber});
}
else if (@event.Format is EventFormat.Individual)
else if (@event.EventFormat is EventFormat.Individual)
{
foreach (var student in teamStudents)
{
teams.Add(new Team($"{teamName} - {student.FirstName}", @event,
new List<Student> { student }, student, teamNumber, regionalTimeSlot));
teams.Add(new Team{Name = $"{teamName} - {student.FirstName}", Event = @event,
Students = new List<Student> { student }, Captain = student, TeamId = teamNumber});
}
}
}