From c462ed4561641f77aa17e9f189d05fa20ec9994b Mon Sep 17 00:00:00 2001 From: James Kolpack Date: Sat, 27 Dec 2025 19:32:54 -0500 Subject: [PATCH] Enhance event definitions and parsing logic for new event types Added new event definitions for "Meet the Candidates", "Chapter Officer Meeting", "Voting Delegate Meeting", and "Social Gathering". Updated the EventOccurrenceParser to handle these new event types and modified related services and views to accommodate the changes. Improved test coverage for the new event definitions and ensured proper parsing and display in the calendar components. --- Core/Entities/EventDefinition.cs | 5 +- Core/Models/EventOccurrenceParseResult.cs | 3 +- Core/Parsers/EventOccurrenceParser.cs | 53 ++++++--- Core/Services/EventOccurrenceParserService.cs | 19 +++- Tests/Parsers/EventOccurrenceParser_Tests.cs | 101 ++++++++++++++++-- Tests/Parsers/TestEntityHandler.cs | 10 +- Web-Original/Views/Home/Nationals.cshtml | 18 +++- Web-Original/Views/Home/State.cshtml | 18 +++- .../Components/Features/Calendar/Import.razor | 10 +- .../Components/Features/Calendar/Index.razor | 2 +- 10 files changed, 198 insertions(+), 41 deletions(-) diff --git a/Core/Entities/EventDefinition.cs b/Core/Entities/EventDefinition.cs index befea1e..7ea2e53 100644 --- a/Core/Entities/EventDefinition.cs +++ b/Core/Entities/EventDefinition.cs @@ -85,6 +85,9 @@ public class EventDefinition } public static readonly EventDefinition GeneralSchedule = new(){Name = "General Schedule"}; - public static readonly EventDefinition VotingDelegates = new(){Name = "Voting Delegates"}; + public static readonly EventDefinition MeetTheCandidates = new(){Name = "Meet the Candidates"}; + public static readonly EventDefinition ChapterOfficerMeeting = new(){Name = "Chapter Officer Meeting"}; + public static readonly EventDefinition VotingDelegateMeeting = new(){Name = "Voting Delegate Meeting"}; + public static readonly EventDefinition SocialGathering = new(){Name = "Social Gathering"}; } \ No newline at end of file diff --git a/Core/Models/EventOccurrenceParseResult.cs b/Core/Models/EventOccurrenceParseResult.cs index c84d376..f8661e2 100644 --- a/Core/Models/EventOccurrenceParseResult.cs +++ b/Core/Models/EventOccurrenceParseResult.cs @@ -10,7 +10,8 @@ public class EventOccurrenceParseResult { /// /// Dictionary of parsed event occurrences, keyed by EventDefinition. - /// For special events (GeneralSchedule/VotingDelegates), the EventDefinition key will be the static instance. + /// For special events (GeneralSchedule, MeetTheCandidates, ChapterOfficerMeeting, VotingDelegateMeeting, SocialGathering), + /// the EventDefinition key will be the static instance. /// public IDictionary> Occurrences { get; set; } = new Dictionary>(); diff --git a/Core/Parsers/EventOccurrenceParser.cs b/Core/Parsers/EventOccurrenceParser.cs index 9d2ea60..111eaba 100644 --- a/Core/Parsers/EventOccurrenceParser.cs +++ b/Core/Parsers/EventOccurrenceParser.cs @@ -52,33 +52,31 @@ public class EventOccurrenceParser currentEventDefinition = evt; continue; } - if (line == "General Schedule") + if (line == "General Schedule" || line == "General Session") { currentEventDefinition = EventDefinition.GeneralSchedule; continue; } - if (line == "Voting Delegates") - { - currentEventDefinition = EventDefinition.VotingDelegates; - continue; - } - + // "Voting Delegates" section header is no longer used - occurrences are categorized by name pattern + // Continue without setting currentEventDefinition for this section continue; } - if (currentEventDefinition == 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, @"(?Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),\s?$", "").Trim(); + // Determine event definition based on occurrence name pattern or current section + EventDefinition? eventDefinition = DetermineEventDefinition(occurrenceName, currentEventDefinition); + + // Skip if we can't determine the event definition + if (eventDefinition == null) + continue; timeAndLocation = SanitizeInput(timeAndLocation); var timeAndLocationMatch = _timeLocationRegex.Match(timeAndLocation); @@ -103,14 +101,41 @@ public class EventOccurrenceParser Location = location }; - if (!occurrences.ContainsKey(currentEventDefinition)) - occurrences.Add(currentEventDefinition, []); - occurrences[currentEventDefinition].Add(eventOccurrence); + if (!occurrences.ContainsKey(eventDefinition)) + occurrences.Add(eventDefinition, []); + occurrences[eventDefinition].Add(eventOccurrence); } return occurrences; } + /// + /// Determines the EventDefinition for an occurrence based on its name pattern or current section context. + /// + private EventDefinition? DetermineEventDefinition(string occurrenceName, EventDefinition? currentEventDefinition) + { + // Check for special event name patterns first (regardless of current section) + if (occurrenceName.Contains("Meet the Candidates Session", StringComparison.OrdinalIgnoreCase)) + return EventDefinition.MeetTheCandidates; + + if (occurrenceName.Contains("Chapter Officer Meeting", StringComparison.OrdinalIgnoreCase)) + return EventDefinition.ChapterOfficerMeeting; + + if (occurrenceName.Contains("Voting Delegate Meeting", StringComparison.OrdinalIgnoreCase)) + return EventDefinition.VotingDelegateMeeting; + + // If we're in a General Schedule/Session section and no pattern matched, use GeneralSchedule + if (currentEventDefinition == EventDefinition.GeneralSchedule) + return EventDefinition.GeneralSchedule; + + // If we have a current event definition from section header (e.g., regular events), use it + if (currentEventDefinition != null) + return currentEventDefinition; + + // Cannot determine event definition + return null; + } + private string SanitizeInput(string input) { diff --git a/Core/Services/EventOccurrenceParserService.cs b/Core/Services/EventOccurrenceParserService.cs index 8952e56..06fc631 100644 --- a/Core/Services/EventOccurrenceParserService.cs +++ b/Core/Services/EventOccurrenceParserService.cs @@ -41,17 +41,26 @@ public class EventOccurrenceParserService : IEventOccurrenceParserService var eventDefinition = kvp.Key; var occurrences = kvp.Value; - // Check if this is a special event type (GeneralSchedule or VotingDelegates) + // Check if this is a special event type (not stored in database) if (eventDefinition == EventDefinition.GeneralSchedule || - eventDefinition == EventDefinition.VotingDelegates) + eventDefinition == EventDefinition.MeetTheCandidates || + eventDefinition == EventDefinition.ChapterOfficerMeeting || + eventDefinition == EventDefinition.VotingDelegateMeeting || + eventDefinition == EventDefinition.SocialGathering) { // For special events, set EventDefinitionId to null and set SpecialEventType foreach (var occurrence in occurrences) { occurrence.EventDefinitionId = null; - occurrence.SpecialEventType = eventDefinition == EventDefinition.GeneralSchedule - ? "GeneralSchedule" - : "VotingDelegates"; + occurrence.SpecialEventType = eventDefinition switch + { + var ed when ed == EventDefinition.GeneralSchedule => "GeneralSchedule", + var ed when ed == EventDefinition.MeetTheCandidates => "MeetTheCandidates", + var ed when ed == EventDefinition.ChapterOfficerMeeting => "ChapterOfficerMeeting", + var ed when ed == EventDefinition.VotingDelegateMeeting => "VotingDelegateMeeting", + var ed when ed == EventDefinition.SocialGathering => "SocialGathering", + _ => throw new InvalidOperationException($"Unknown special event type: {eventDefinition.Name}") + }; } // Add to result with the special EventDefinition as key diff --git a/Tests/Parsers/EventOccurrenceParser_Tests.cs b/Tests/Parsers/EventOccurrenceParser_Tests.cs index 75ff65f..b741167 100644 --- a/Tests/Parsers/EventOccurrenceParser_Tests.cs +++ b/Tests/Parsers/EventOccurrenceParser_Tests.cs @@ -7,10 +7,10 @@ public class EventOccurrenceParser_Tests { [Test] - public void ParseTest() + public void ParseNationalsTest() { var events = TestEntityHandler.GetEvents(); - var parser = new EventOccurrenceParser(TestEntityHandler.GetEventOccurrenceFileInfo(), events); + var parser = new EventOccurrenceParser(TestEntityHandler.GetEventOccurrenceNationalsFileInfo(), events); var dictionary = parser.Parse(); Console.WriteLine($"Occurrence, Month, Date, Time, Location"); foreach (var @event in events) @@ -30,17 +30,104 @@ public class EventOccurrenceParser_Tests } Console.WriteLine("General Schedule"); - foreach (var eo in dictionary[EventDefinition.GeneralSchedule].OrderBy(occurrence => occurrence.StartTime)) + if (dictionary.ContainsKey(EventDefinition.GeneralSchedule)) { - Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}"); + foreach (var eo in dictionary[EventDefinition.GeneralSchedule].OrderBy(occurrence => occurrence.StartTime)) + { + Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}"); + } } - if (dictionary.ContainsKey(EventDefinition.VotingDelegates)) - foreach (var eo in dictionary[EventDefinition.VotingDelegates]) + Console.WriteLine("Meet the Candidates"); + if (dictionary.ContainsKey(EventDefinition.MeetTheCandidates)) { - Console.WriteLine($"{eo.Name} {eo.StartTime}, {eo.Location}"); + foreach (var eo in dictionary[EventDefinition.MeetTheCandidates]) + { + Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}"); + } + } + + Console.WriteLine("Chapter Officer Meeting"); + if (dictionary.ContainsKey(EventDefinition.ChapterOfficerMeeting)) + { + foreach (var eo in dictionary[EventDefinition.ChapterOfficerMeeting]) + { + Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}"); + } + } + + Console.WriteLine("Voting Delegate Meeting"); + if (dictionary.ContainsKey(EventDefinition.VotingDelegateMeeting)) + { + foreach (var eo in dictionary[EventDefinition.VotingDelegateMeeting]) + { + Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}"); + } } Assert.Pass(); } + + + [Test] + public void ParseStatesTest() + { + var events = TestEntityHandler.GetEvents(); + var parser = new EventOccurrenceParser(TestEntityHandler.GetEventOccurrenceStateFileInfo(), events); + var dictionary = parser.Parse(); + Console.WriteLine($"Occurrence, Month, Date, Time, Location"); + foreach (var @event in events) + { + Console.WriteLine($"{@event.Name}"); + + if (!dictionary.ContainsKey(@event)) + { + Console.WriteLine("!!! eventDefinition not found " + @event.Name); + continue; + } + var eventOccurrences = dictionary[@event]; + foreach (var eo in eventOccurrences) + { + Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}"); + } + } + + Console.WriteLine("General Schedule"); + if (dictionary.ContainsKey(EventDefinition.GeneralSchedule)) + { + foreach (var eo in dictionary[EventDefinition.GeneralSchedule].OrderBy(occurrence => occurrence.StartTime)) + { + Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}"); + } + } + + Console.WriteLine("Meet the Candidates"); + if (dictionary.ContainsKey(EventDefinition.MeetTheCandidates)) + { + foreach (var eo in dictionary[EventDefinition.MeetTheCandidates]) + { + Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}"); + } + } + + Console.WriteLine("Chapter Officer Meeting"); + if (dictionary.ContainsKey(EventDefinition.ChapterOfficerMeeting)) + { + foreach (var eo in dictionary[EventDefinition.ChapterOfficerMeeting]) + { + Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}"); + } + } + + Console.WriteLine("Voting Delegate Meeting"); + if (dictionary.ContainsKey(EventDefinition.VotingDelegateMeeting)) + { + foreach (var eo in dictionary[EventDefinition.VotingDelegateMeeting]) + { + Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}"); + } + } + + Assert.Pass(); + } } \ No newline at end of file diff --git a/Tests/Parsers/TestEntityHandler.cs b/Tests/Parsers/TestEntityHandler.cs index 7e94159..d96ffa4 100644 --- a/Tests/Parsers/TestEntityHandler.cs +++ b/Tests/Parsers/TestEntityHandler.cs @@ -16,12 +16,18 @@ public static class TestEntityHandler return eventRankingsParser.Parse(); } - public static FileInfo GetEventOccurrenceFileInfo() + public static FileInfo GetEventOccurrenceNationalsFileInfo() { return FileUtility.GetContentFile(ContentDirectory, "2025 TSA Nationals Competition Event Times.txt"); } - public static Student[] GetStudents(IList events) + + public static FileInfo GetEventOccurrenceStateFileInfo() + { + return FileUtility.GetContentFile(ContentDirectory, "2025 TN TSA State Competition Event Times.txt"); + } + + public static Student[] GetStudents(IList events) { //var studentEventRankingsCsv = "Student Event Rankings.csv"; var studentEventRankingsCsv = "2024 Student Event Rankings.csv"; diff --git a/Web-Original/Views/Home/Nationals.cshtml b/Web-Original/Views/Home/Nationals.cshtml index 0120c8c..a8af6f2 100644 --- a/Web-Original/Views/Home/Nationals.cshtml +++ b/Web-Original/Views/Home/Nationals.cshtml @@ -138,7 +138,9 @@ .Where(eo => student.Teams.Select(t => t.EventDefinition).Any(a => a == eo.Key) || eo.Key == EventDefinition.GeneralSchedule - || (eo.Key == EventDefinition.VotingDelegates && student.VotingDelegate)) + || eo.Key == EventDefinition.MeetTheCandidates + || (eo.Key == EventDefinition.ChapterOfficerMeeting && student.OfficerRole != null) + || (eo.Key == EventDefinition.VotingDelegateMeeting && student.VotingDelegate)) .SelectMany(eo => eo.Value.Select(v => Tuple.Create(v, eo.Key))) .GroupBy(de => de.Item1.StartTime.Date) .OrderBy(d => d.Key) @@ -218,16 +220,24 @@ @foreach (var occurrence in eventsForDate.OrderBy(o => o.Item1.StartTime)) { var teams = Model.Item1.Where(t => t.EventDefinition == occurrence.Item2); - if (occurrence.Item2 != EventDefinition.GeneralSchedule && occurrence.Item2 != EventDefinition.VotingDelegates && !teams.Any()) + if (occurrence.Item2 != EventDefinition.GeneralSchedule + && occurrence.Item2 != EventDefinition.MeetTheCandidates + && occurrence.Item2 != EventDefinition.ChapterOfficerMeeting + && occurrence.Item2 != EventDefinition.VotingDelegateMeeting + && !teams.Any()) continue; @occurrence.Item1.Time - @if (occurrence.Item2 == EventDefinition.GeneralSchedule) + @if (occurrence.Item2 == EventDefinition.GeneralSchedule || occurrence.Item2 == EventDefinition.MeetTheCandidates) { Everyone } - else if (occurrence.Item2 == EventDefinition.VotingDelegates) + else if (occurrence.Item2 == EventDefinition.ChapterOfficerMeeting) + { + Chapter Officers - @string.Join(", ", Model.Item2.Where(stu => stu.OfficerRole != null).Select(stu => stu.FirstName)) + } + else if (occurrence.Item2 == EventDefinition.VotingDelegateMeeting) { Voting Delegates - @string.Join(", ", Model.Item2.Where(stu => stu.VotingDelegate).Select(stu => stu.FirstName)) } diff --git a/Web-Original/Views/Home/State.cshtml b/Web-Original/Views/Home/State.cshtml index 378779b..02ea628 100644 --- a/Web-Original/Views/Home/State.cshtml +++ b/Web-Original/Views/Home/State.cshtml @@ -139,7 +139,9 @@ .Where(eo => student.Teams.Select(t => t.EventDefinition).Any(a => a == eo.Key) || eo.Key == EventDefinition.GeneralSchedule - || (eo.Key == EventDefinition.VotingDelegates && student.VotingDelegate)) + || eo.Key == EventDefinition.MeetTheCandidates + || (eo.Key == EventDefinition.ChapterOfficerMeeting && student.OfficerRole != null) + || (eo.Key == EventDefinition.VotingDelegateMeeting && student.VotingDelegate)) .SelectMany(eo => eo.Value.Select(v => Tuple.Create(v, eo.Key))) .GroupBy(de => de.Item1.Date + ", " + de.Item1.StartTime.DayOfWeek) .OrderBy(d => d.Key) @@ -211,16 +213,24 @@ @foreach (var occurrence in eventsForDate.OrderBy(o => o.Item1.StartTime)) { var teams = Model.Item1.Where(t => t.EventDefinition == occurrence.Item2); - if (occurrence.Item2 != EventDefinition.GeneralSchedule && occurrence.Item2 != EventDefinition.VotingDelegates && !teams.Any()) + if (occurrence.Item2 != EventDefinition.GeneralSchedule + && occurrence.Item2 != EventDefinition.MeetTheCandidates + && occurrence.Item2 != EventDefinition.ChapterOfficerMeeting + && occurrence.Item2 != EventDefinition.VotingDelegateMeeting + && !teams.Any()) continue; @occurrence.Item1.Time - @if (occurrence.Item2 == EventDefinition.GeneralSchedule) + @if (occurrence.Item2 == EventDefinition.GeneralSchedule || occurrence.Item2 == EventDefinition.MeetTheCandidates) { Everyone } - else if (occurrence.Item2 == EventDefinition.VotingDelegates) + else if (occurrence.Item2 == EventDefinition.ChapterOfficerMeeting) + { + Chapter Officers - @string.Join(", ", Model.Item2.Where(stu => stu.OfficerRole != null).Select(stu => stu.FirstName)) + } + else if (occurrence.Item2 == EventDefinition.VotingDelegateMeeting) { Voting Delegates - @string.Join(", ", Model.Item2.Where(stu => stu.VotingDelegate).Select(stu => stu.FirstName)) } diff --git a/WebApp/Components/Features/Calendar/Import.razor b/WebApp/Components/Features/Calendar/Import.razor index 42668a6..bc3aed3 100644 --- a/WebApp/Components/Features/Calendar/Import.razor +++ b/WebApp/Components/Features/Calendar/Import.razor @@ -239,8 +239,14 @@ { if (eventDefinition == EventDefinition.GeneralSchedule) return "General Schedule"; - if (eventDefinition == EventDefinition.VotingDelegates) - return "Voting Delegates"; + if (eventDefinition == EventDefinition.MeetTheCandidates) + return "Meet the Candidates"; + if (eventDefinition == EventDefinition.ChapterOfficerMeeting) + return "Chapter Officer Meeting"; + if (eventDefinition == EventDefinition.VotingDelegateMeeting) + return "Voting Delegate Meeting"; + if (eventDefinition == EventDefinition.SocialGathering) + return "Social Gathering"; return eventDefinition.Name; } } diff --git a/WebApp/Components/Features/Calendar/Index.razor b/WebApp/Components/Features/Calendar/Index.razor index 29dbb51..ae44787 100644 --- a/WebApp/Components/Features/Calendar/Index.razor +++ b/WebApp/Components/Features/Calendar/Index.razor @@ -8,7 +8,7 @@ - Import + Import