first commit
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user