first commit

This commit is contained in:
2025-08-01 14:10:44 -04:00
commit cf32cfcbcd
149 changed files with 80416 additions and 0 deletions
@@ -0,0 +1,52 @@
using Core.Entities;
namespace Core.Parsers;
public class AssignmentAssumptionParser : CsvParserBase
{
public AssignmentAssumptionParser(FileSystemInfo csvFile, bool ignoreBlankLines = true) : base(csvFile, ignoreBlankLines)
{
}
public AssignmentAssumption[] Parse(ICollection<CompetitiveEvent> events, ICollection<Student> students)
{
var assumptions = new List<AssignmentAssumption>();
CsvReader.Read();
CsvReader.ReadHeader();
var studentColumns =
CsvReader.HeaderRecord.Select(h => h.Trim()).Where(h => !string.IsNullOrEmpty(h)).ToArray();
var studentArray = studentColumns.Select(c => students.First(s => s.FirstName == c)).ToArray();
while (CsvReader.Read())
{
var eventShortName= CsvReader.GetField(0);
var evt = events.FirstOrDefault(e => e.ShortName == eventShortName);
if (evt == null)
throw new Exception($"Could not find event named {eventShortName}");
for (int i = 0; i <= studentArray.Length; i++)
{
var field = CsvReader.GetField(i + 1);
switch (field)
{
case "x":
case "X":
assumptions.Add(new AssignmentAssumption(evt, studentArray[i], Assumption.Exclude));
break;
case "i":
case "I":
assumptions.Add(new AssignmentAssumption(evt, studentArray[i], Assumption.Include));
break;
default:
break;
}
}
}
return assumptions.ToArray();
}
}
+53
View File
@@ -0,0 +1,53 @@
using System.Globalization;
using CsvHelper;
using CsvHelper.Configuration;
namespace Core.Parsers;
public class CsvParserBase : IDisposable
{
private readonly StreamReader _reader;
//private readonly MemoryStream _memoryStream;
protected readonly CsvReader CsvReader;
protected CsvParserBase(FileSystemInfo csvFile, bool ignoreBlankLines)
{
_reader = OpenCsv(csvFile);
CsvReader = InitCsvReader(_reader, ignoreBlankLines);
}
//protected CsvParserBase(byte[] fileContents, bool ignoreBlankLines)
//{
// _memoryStream = new MemoryStream(fileContents);
// _reader = new StreamReader(_memoryStream);
// CsvReader = InitCsvReader(_reader, ignoreBlankLines);
//}
private static CsvReader InitCsvReader(TextReader reader, bool ignoreBlankLines)
{
var csvConfiguration = new CsvConfiguration(CultureInfo.CurrentCulture)
{
HasHeaderRecord = true,
IgnoreBlankLines = ignoreBlankLines,
ReadingExceptionOccurred = exception => false,
MissingFieldFound = null
};
var csvReader = new CsvReader(reader, csvConfiguration);
return csvReader;
}
internal static StreamReader OpenCsv(FileSystemInfo csvFile)
{
if (!csvFile.Exists)
throw new FileNotFoundException($"Cannot find file '{csvFile.Name}'");
return File.OpenText(csvFile.FullName);
}
public void Dispose()
{
_reader.Dispose();
}
}
+76
View File
@@ -0,0 +1,76 @@
using System.Text.RegularExpressions;
using Core.Entities;
namespace Core.Parsers;
public class EventDefinitionParser : CsvParserBase
{
public EventDefinitionParser(FileSystemInfo csvFile, bool ignoreBlankLines = true) : base(csvFile, ignoreBlankLines)
{
}
public CompetitiveEvent[] Parse()
{
var events = new List<CompetitiveEvent>();
CsvReader.Read();
CsvReader.ReadHeader();
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);
var teamSize = CsvReader.GetField("Team Size");
if (string.IsNullOrEmpty(teamSize))
throw new ArgumentException(@"Team Size is null for {name}");
var match = Regex.Match(teamSize, @"(\d)(?:\s?to\s?)?(\d)?");
var min = int.Parse(match.Groups[1].Captures[0].Value);
var max = match.Groups[2].Success ? int.Parse(match.Groups[2].Captures[0].Value) : min;
var stateTeams = CsvReader.GetField<int>("State Count");
var semifinalistActivity = CsvReader.GetField("Semifinalist Activity");
var regionalCount = CsvReader.GetField("Regional Count");
var regionalPresubmit = CsvReader.GetField("Regional Presubmission");
var statePresubmission = CsvReader.GetField("State Presubmission");
var statePretesting = CsvReader.GetField("State Pretesting");
var statePreliminary = CsvReader.GetField("State Preliminary Round");
var regionalNotes = CsvReader.GetField("Regional Notes");
var documentation = CsvReader.GetField("Documentation");
var eligibility = CsvReader.GetField("Eligibility");
var theme = CsvReader.GetField("Theme");
var description = CsvReader.GetField("Description");
var levelOfEffort = CsvReader.GetField<int?>("Level of Effort");
//var regionalTeams = CsvReader.GetField<int>("Regional Teams");
var competitiveEvent = new CompetitiveEvent
{
Name = name.Trim(),
ShortName = shortName.Trim(),
Format = format,
MaxTeamCountState = stateTeams,
MinTeamSize = min,
MaxTeamSize = max,
SemifinalistActivity = semifinalistActivity,
RegionalEvent = !string.IsNullOrEmpty(regionalCount),
RegionalPresubmit = regionalPresubmit.Trim() == "TRUE",
RegionalNotes = regionalNotes,
Documentation= documentation,
StatePresubmission = statePresubmission.Trim() == "TRUE",
StatePretesting = statePretesting.Trim() == "TRUE",
StatePreliminaryRound = statePreliminary.Trim() == "TRUE",
Eligibility = eligibility,
Theme = theme,
Description = description,
LevelOfEffort = levelOfEffort
};
events.Add(competitiveEvent);
}
return events.ToArray();
}
}
+184
View File
@@ -0,0 +1,184 @@
using System.Text.RegularExpressions;
using Core.Entities;
using FuzzySharp;
namespace Core.Parsers;
public class EventOccurrenceParser
{
private FileSystemInfo _txtFile;
private ICollection<CompetitiveEvent> _events;
public EventOccurrenceParser(FileSystemInfo txtFile, ICollection<CompetitiveEvent> events)
{
_events = events;
_txtFile = txtFile;
}
private Regex _re =
new (
@"" + //
@"(?<Name>^[^#].*)\s" +
@"(?<Month>February|March|April|May|June|July)\s" +
@"(?<DayOfMonth>\d{1,2});?\s" +
@"(?<TimeAndLocation>.*)"
);
private readonly Regex _timeRe = new(@"(?<Hour>\d{1,2}):?(?<Minute>\d{2})?\s?(?<APM>(?:a|p)\.?m\.?)");
private readonly Regex _timeLocationRegex = new(@"(?<Time>.*(?>[AaPp]\.?[Mm]\.?))(?<Location>[\s\t].*)?");
public IDictionary<CompetitiveEvent, List<EventOccurrence>> Parse()
{
var occurrences = new Dictionary<CompetitiveEvent, List<EventOccurrence>>();
CompetitiveEvent currentEvent = null;
var lines = File.ReadLines(_txtFile.FullName);
foreach (var line in lines)
{
var match = _re.Match(line);
if (!match.Success)
{
if (line.Contains("MS"))
{
var evt =
(from e in _events
let rat = Fuzz.Ratio(e.Name, line.Trim())
where rat > 50
orderby rat descending
select e).FirstOrDefault();
if (evt == null)
continue;
currentEvent = evt;
continue;
}
if (line == "General Schedule")
{
currentEvent = CompetitiveEvent.GeneralSchedule;
continue;
}
if (line == "Voting Delegates")
{
currentEvent = CompetitiveEvent.VotingDelegates;
continue;
}
continue;
}
if (currentEvent == null)
continue;
var occurrenceName = match.Groups["Name"].Captures[0].Value;
var month = match.Groups["Month"].Captures[0].Value;
var dayOfMonth = match.Groups["DayOfMonth"].Captures[0].Value;
var timeAndLocation = match.Groups["TimeAndLocation"].Captures[0].Value;
occurrenceName = Regex.Replace(occurrenceName,
@"(?<Weekday>Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),\s?$", "").Trim();
timeAndLocation = SanitizeInput(timeAndLocation);
var timeAndLocationMatch = _timeLocationRegex.Match(timeAndLocation);
var time = timeAndLocation;
var location = string.Empty;
if (timeAndLocationMatch.Success)
{
time= timeAndLocationMatch.Groups["Time"].Captures[0].Value;
if (timeAndLocationMatch.Groups["Location"].Success)
location = timeAndLocationMatch.Groups["Location"].Captures[0].Value;
}
var startDate = ParseDate(month, dayOfMonth, DateTime.Now.Year);
var startTime = ParseStartTime(time);
var t = new DateTime(startDate, startTime);
var eventOccurrence = new EventOccurrence
{
Name = occurrenceName, StartTime = t, Time = $"{time}", Date = $"{month} {dayOfMonth}",
Location = location
};
if (!occurrences.ContainsKey(currentEvent))
occurrences.Add(currentEvent, []);
occurrences[currentEvent].Add(eventOccurrence);
}
return occurrences;
}
private string SanitizeInput(string input)
{
input = input.Replace("", "-");
input = input.Replace("—", "-");
return input;
}
private DateOnly ParseDate(string month, string dayOfMonth, int year)
{
int monthNum = 1;
switch (month)
{
case "February":
monthNum = 2;
break;
case "March":
monthNum = 3;
break;
case "April":
monthNum = 4;
break;
case "May":
monthNum = 5;
break;
case "June":
monthNum = 6;
break;
case "July":
monthNum = 7;
break;
}
var day = int.Parse(dayOfMonth);
return new DateOnly(year, monthNum, day); ;
}
private TimeOnly ParseStartTime(string time)
{
int hour = 0;
int minute = 0;
// get the part of the time before a timespan
if (time.Contains(" - "))
{
time = time[..time.IndexOf(" - ", StringComparison.Ordinal)];
}
if (time == "NOON")
hour = 12;
else
{
var timeMatch = _timeRe.Match(time.ToLower());
if (timeMatch.Success)
{
hour = int.Parse(timeMatch.Groups["Hour"].Captures[0].Value);
if (timeMatch.Groups["Minute"].Success)
{
minute = int.Parse(timeMatch.Groups["Minute"].Captures[0].Value);
}
if (timeMatch.Groups["APM"].Captures[0].Value is "p.m." or "pm" && hour < 12)
hour += 12;
}
}
return new TimeOnly(hour, minute, 0);
}
}
+89
View File
@@ -0,0 +1,89 @@
using Core.Entities;
using FuzzySharp;
namespace Core.Parsers;
public class StudentParser : CsvParserBase
{
public StudentParser(FileSystemInfo csvFile, bool ignoreBlankLines = true) : base(csvFile, ignoreBlankLines)
{
}
public Student[] Parse(ICollection<CompetitiveEvent> events)
{
var s = new List<Student>();
CsvReader.Read();
CsvReader.ReadHeader();
while (CsvReader.Read())
{
var name = CsvReader.GetField("Student Name");
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 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 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);
s.Add(student);
}
return s.ToArray();
}
}
+166
View File
@@ -0,0 +1,166 @@
using System.Globalization;
using Core.Entities;
using CsvHelper;
using CsvHelper.Configuration;
using FuzzySharp;
namespace Core.Parsers
{
public class TeamWriter
{
private readonly ICollection<Team> _teams;
public string Filename { get; }
public TeamWriter(ICollection<Team> teams, string filename)
{
_teams = teams;
Filename = filename;
}
public void Write()
{
var csvConfiguration = new CsvConfiguration(CultureInfo.CurrentCulture)
{
HasHeaderRecord = true,
};
using var writer = new StreamWriter(Filename);
using var csv = new CsvWriter(writer, csvConfiguration);
// header
csv.WriteField("Team Name");
csv.WriteField("Event Name");
var max = _teams.Max(t => t.Students.Count);
for (var i = 1; i < max + 1; i++)
{
csv.WriteField($"Student {i}");
}
foreach (var team in _teams)
{
csv.WriteField(team.Name);
csv.WriteField(team.Event.Name);
foreach (var teamStudent in team.Students)
{
csv.WriteField(teamStudent.Name);
}
csv.NextRecord();
}
}
}
public class TeamParser : CsvParserBase
{
public TeamParser(FileSystemInfo csvFile, bool ignoreBlankLines = true) : base(csvFile, ignoreBlankLines)
{
}
public Team[] Parse(ICollection<CompetitiveEvent> events, ICollection<Student> students)
{
var teams = new List<Team>();
CsvReader.Read();
CsvReader.ReadHeader();
while (CsvReader.Read())
{
var eventName
= CsvReader.GetField("Event Name");
if (string.IsNullOrEmpty(eventName))
continue;
if (eventName.StartsWith("Team Size"))
continue;
eventName = eventName.Replace("ᵃ", string.Empty);
eventName = eventName.Replace("ⁱ", string.Empty);
eventName = eventName.TrimEnd();
var teamName = CsvReader.GetField("Team Name");
if (string.IsNullOrEmpty(teamName))
teamName = eventName;
eventName = eventName.Trim();
var @event =
(from e in events
let rat = Fuzz.Ratio(e.Name, eventName)
where rat > 50
orderby rat descending
select e).FirstOrDefault();
if (@event == null)
continue;
var regionalTimeSlot = CsvReader.GetField("Regional Time Slot");
if (!string.IsNullOrEmpty(regionalTimeSlot))
regionalTimeSlot.Trim();
var teamStudents = new List<Student>();
Student? captain = null;
for (var i = 1; i <= 9; i++)
{
var studentName = CsvReader.GetField($"Student {i}");
if (string.IsNullOrEmpty(studentName)) continue;
studentName = studentName.Trim();
if (studentName == "?")
continue;
var studentMatches =
from s in students
let rat = new[]
{
Fuzz.Ratio(s.Name, studentName),
Fuzz.Ratio(s.FirstNameLastName, studentName),
Fuzz.Ratio(s.FirstName, studentName)
}.Max()
where rat > 90
orderby rat descending
select s;
var student = studentMatches.FirstOrDefault();
if (student == null)
{
//continue;
throw new ArgumentException($"Student named '{studentName}' not found");
}
teamStudents.Add(student);
if (i == 1)
captain = student;
}
var teamNumber = string.Empty;
if (teamName.EndsWith("Team 2"))
teamNumber = "12227-2";
else if (@event.Format == EventFormat.Team)
teamNumber = "2227";
if (teamStudents.Count > 0)
{
if (@event.Format is EventFormat.Team)
{
teams.Add(new Team(teamName, @event, teamStudents, captain, teamNumber,
regionalTimeSlot: regionalTimeSlot));
}
else if (@event.Format is EventFormat.Individual)
{
foreach (var student in teamStudents)
{
teams.Add(new Team($"{teamName} - {student.FirstName}", @event,
new List<Student> { student }, student, teamNumber, regionalTimeSlot));
}
}
}
}
return teams.ToArray();
}
}
}