Refactor event occurrence parsing to unify section header handling and event count tracking

This commit updates the EventOccurrenceParseResult and EventOccurrenceParserResult classes to consolidate the handling of skipped section headers and event counts into a single set of properties. The previous separate lists and counts for middle school and high school sections have been replaced with a unified approach, improving clarity and maintainability. Additionally, the EventOccurrenceParserService has been modified to reflect these changes, ensuring consistent behavior across the application. This refactor enhances the overall structure of the event parsing logic.
This commit is contained in:
2026-01-10 18:19:16 -05:00
parent b7e812bb63
commit ecd6173a44
11 changed files with 528 additions and 472 deletions
+225 -347
View File
@@ -9,6 +9,16 @@ namespace Tests.Parsers;
/// </summary>
public class EventOccurrenceParser_Tests
{
#region Constants
private const int MaxLookbackLines = 20;
private const int TopUnmatchedPatternsCount = 10;
private const int SampleIssuesCount = 5;
private const int SectionTestStartLineIndex = 63; // Line 64 (1-based) = index 63 (0-based)
private const int SectionTestLineCount = 29; // Lines 64-92 inclusive
#endregion
#region Helper Methods
/// <summary>
@@ -41,20 +51,18 @@ public class EventOccurrenceParser_Tests
return true;
// For MissingEventDefinition issues, check if we're in an HS section
if (issue.IssueType == ParsingIssueType.MissingEventDefinition)
if (issue.IssueType != ParsingIssueType.MissingEventDefinition) return false;
// Look backwards to find the most recent section header
for (int i = currentLineIndex - 1; i >= 0 && i >= currentLineIndex - MaxLookbackLines; i--)
{
// Look backwards to find the most recent section header
for (int i = currentLineIndex - 1; i >= 0 && i >= currentLineIndex - 20; i--)
{
if (i < fileLines.Count)
{
var line = fileLines[i].Trim();
if (IsHighSchoolEvent(line))
return true;
if (IsMiddleSchoolEvent(line))
return false; // Found MS section, so this is fixable
}
}
if (i >= fileLines.Count) continue;
var line = fileLines[i].Trim();
if (IsHighSchoolEvent(line))
return true;
if (IsMiddleSchoolEvent(line))
return false; // Found MS section, so this is fixable
}
return false;
@@ -88,7 +96,7 @@ public class EventOccurrenceParser_Tests
/// <summary>
/// Gets sample lines for a list of issues.
/// </summary>
private static List<string> GetSampleLines(List<ParsingIssue> issues, int count = 5)
private static List<string> GetSampleLines(List<ParsingIssue> issues, int count = SampleIssuesCount)
{
return issues
.Take(count)
@@ -96,6 +104,115 @@ public class EventOccurrenceParser_Tests
.ToList();
}
/// <summary>
/// Writes special events summary to console.
/// </summary>
private static void WriteSpecialEventsSummary(IDictionary<EventDefinition, List<Core.Entities.EventOccurrence>> occurrences)
{
Console.WriteLine($"\n--- Special Events Found ---");
if (occurrences.TryGetValue(EventDefinition.GeneralSchedule, out var gs))
Console.WriteLine($" GeneralSchedule: {gs.Count} occurrences");
if (occurrences.TryGetValue(EventDefinition.MeetTheCandidates, out var mtc))
Console.WriteLine($" MeetTheCandidates: {mtc.Count} occurrences");
if (occurrences.TryGetValue(EventDefinition.ChapterOfficerMeeting, out var com))
Console.WriteLine($" ChapterOfficerMeeting: {com.Count} occurrences");
if (occurrences.TryGetValue(EventDefinition.VotingDelegateMeeting, out var vdm))
Console.WriteLine($" VotingDelegateMeeting: {vdm.Count} occurrences");
}
/// <summary>
/// Writes issue breakdown by type to console.
/// </summary>
private static void WriteIssueBreakdown(
Dictionary<ParsingIssueType, List<ParsingIssue>> issuesByType,
Dictionary<ParsingIssueType, int> expectedByType,
Dictionary<ParsingIssueType, int> fixableByType,
List<ParsingIssue> fixableIssues)
{
Console.WriteLine($"\n--- Issue Breakdown by Type ---");
foreach (var kvp in issuesByType.OrderByDescending(x => x.Value.Count))
{
var issueType = kvp.Key;
var allIssues = kvp.Value;
expectedByType.TryGetValue(issueType, out var expectedCount);
fixableByType.TryGetValue(issueType, out var fixableCount);
Console.WriteLine($"\n{issueType}:");
Console.WriteLine($" Total: {allIssues.Count} (Expected: {expectedCount}, Fixable: {fixableCount})");
if (fixableCount > 0)
{
var fixableOfType = fixableIssues.Where(i => i.IssueType == issueType).ToList();
var samples = GetSampleLines(fixableOfType, SampleIssuesCount);
Console.WriteLine($" Sample fixable issues:");
foreach (var sample in samples)
{
Console.WriteLine(sample);
}
}
}
}
/// <summary>
/// Writes unmatched line analysis to console.
/// </summary>
private static void WriteUnmatchedLineAnalysis(List<ParsingIssue> fixableIssues)
{
var unmatchedLines = fixableIssues.Where(i => i.IssueType == ParsingIssueType.UnmatchedLine).ToList();
if (!unmatchedLines.Any()) return;
Console.WriteLine($"\n--- Unmatched Line Analysis ---");
var unmatchedPatterns = unmatchedLines
.GroupBy(i => i.LineContent.Trim())
.OrderByDescending(g => g.Count())
.Take(TopUnmatchedPatternsCount);
Console.WriteLine($"Top unmatched line formats:");
foreach (var pattern in unmatchedPatterns)
{
Console.WriteLine($" \"{pattern.Key}\" (appears {pattern.Count()} times)");
}
}
/// <summary>
/// Analyzes and reports parsing results for a file.
/// </summary>
private static void AnalyzeParsingResults(
EventOccurrenceParserResult result,
FileInfo fileInfo,
string title)
{
// Load file lines for analysis
var fileLines = File.ReadAllLines(fileInfo.FullName).ToList();
var totalLines = fileLines.Count;
var totalParsed = result.Occurrences.Values.Sum(list => list.Count);
// Categorize issues
var (expectedIssues, fixableIssues) = CategorizeIssues(result.Issues, fileLines);
// Count event sections
var (hsSections, msSections) = CountEventSections(fileLines);
// Group issues by type
var issuesByType = result.Issues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.ToList());
var expectedByType = expectedIssues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.Count());
var fixableByType = fixableIssues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.Count());
// Output analysis
Console.WriteLine($"\n=== {title} ===");
Console.WriteLine($"\n--- Summary Statistics ---");
Console.WriteLine($"Total lines in file: {totalLines}");
Console.WriteLine($"Total occurrences parsed: {totalParsed}");
Console.WriteLine($"Total issues found: {result.Issues.Count}");
Console.WriteLine($" Expected issues (HS-related): {expectedIssues.Count} ({100.0 * expectedIssues.Count / Math.Max(1, result.Issues.Count):F1}%)");
Console.WriteLine($" Fixable issues: {fixableIssues.Count} ({100.0 * fixableIssues.Count / Math.Max(1, result.Issues.Count):F1}%)");
Console.WriteLine($"Event sections: HS={hsSections}, MS={msSections}");
Console.WriteLine($"Events with occurrences: {result.Occurrences.Count}");
WriteSpecialEventsSummary(result.Occurrences);
WriteIssueBreakdown(issuesByType, expectedByType, fixableByType, fixableIssues);
WriteUnmatchedLineAnalysis(fixableIssues);
}
/// <summary>
/// Counts HS vs MS event sections in the file.
@@ -117,68 +234,77 @@ public class EventOccurrenceParser_Tests
return (hsCount, msCount);
}
/// <summary>
/// Writes special events to console output.
/// </summary>
private static void WriteSpecialEvents(IDictionary<EventDefinition, List<Core.Entities.EventOccurrence>> occurrences)
{
Console.WriteLine("General Schedule");
if (occurrences.TryGetValue(EventDefinition.GeneralSchedule, out var generalSchedule))
{
foreach (var eo in generalSchedule.OrderBy(occurrence => occurrence.StartTime))
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("Meet the Candidates");
if (occurrences.TryGetValue(EventDefinition.MeetTheCandidates, out var meetTheCandidates))
{
foreach (var eo in meetTheCandidates)
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("Chapter Officer Meeting");
if (occurrences.TryGetValue(EventDefinition.ChapterOfficerMeeting, out var chapterOfficerMeeting))
{
foreach (var eo in chapterOfficerMeeting)
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("Voting Delegate Meeting");
if (occurrences.TryGetValue(EventDefinition.VotingDelegateMeeting, out var votingDelegateMeeting))
{
foreach (var eo in votingDelegateMeeting)
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
}
#endregion
[Test]
public void ParseNationalsTest()
{
var events = TestEntityHandler.GetEvents();
var parser = new EventOccurrenceParser(TestEntityHandler.GetEventOccurrenceNationalsFileInfo(), events);
var result = parser.Parse();
var dictionary = result.Occurrences;
Console.WriteLine($"Occurrence, Month, Date, Time, Location");
foreach (var @event in events)
{
Console.WriteLine($"{@event.Name}");
var events = TestEntityHandler.GetEvents();
var parser = new EventOccurrenceParser(TestEntityHandler.GetEventOccurrenceNationalsFileInfo(), events);
var result = parser.Parse();
var dictionary = result.Occurrences;
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}");
}
}
if (!dictionary.TryGetValue(@event, out var eventOccurrences))
{
Console.WriteLine($"!!! eventDefinition not found {@event.Name}");
continue;
}
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}");
}
}
foreach (var eo in eventOccurrences)
{
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}");
}
}
WriteSpecialEvents(dictionary);
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();
Assert.Pass();
}
@@ -194,53 +320,19 @@ public class EventOccurrenceParser_Tests
{
Console.WriteLine($"{@event.Name}");
if (!dictionary.ContainsKey(@event))
if (!dictionary.TryGetValue(@event, out var eventOccurrences))
{
Console.WriteLine("!!! eventDefinition not found " + @event.Name);
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}");
}
}
WriteSpecialEvents(dictionary);
Assert.Pass();
}
@@ -259,82 +351,11 @@ public class EventOccurrenceParser_Tests
// Assert - Should parse without exceptions
Assert.That(result, Is.Not.Null, "Parser should return a result");
// Load file lines for analysis
AnalyzeParsingResults(result, fileInfo, "2025 TSA Nationals Competition Event Times Analysis");
var fileLines = File.ReadAllLines(fileInfo.FullName).ToList();
var totalLines = fileLines.Count;
var (_, fixableIssues) = CategorizeIssues(result.Issues, fileLines);
var totalParsed = result.Occurrences.Values.Sum(list => list.Count);
// Categorize issues
var (expectedIssues, fixableIssues) = CategorizeIssues(result.Issues, fileLines);
// Count event sections
var (hsSections, msSections) = CountEventSections(fileLines);
// Group issues by type
var issuesByType = result.Issues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.ToList());
var expectedByType = expectedIssues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.Count());
var fixableByType = fixableIssues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.Count());
// Output analysis
Console.WriteLine($"\n=== 2025 TSA Nationals Competition Event Times Analysis ===");
Console.WriteLine($"\n--- Summary Statistics ---");
Console.WriteLine($"Total lines in file: {totalLines}");
Console.WriteLine($"Total occurrences parsed: {totalParsed}");
Console.WriteLine($"Total issues found: {result.Issues.Count}");
Console.WriteLine($" Expected issues (HS-related): {expectedIssues.Count} ({100.0 * expectedIssues.Count / Math.Max(1, result.Issues.Count):F1}%)");
Console.WriteLine($" Fixable issues: {fixableIssues.Count} ({100.0 * fixableIssues.Count / Math.Max(1, result.Issues.Count):F1}%)");
Console.WriteLine($"Event sections: HS={hsSections}, MS={msSections}");
Console.WriteLine($"Events with occurrences: {result.Occurrences.Count}");
Console.WriteLine($"\n--- Special Events Found ---");
if (result.Occurrences.ContainsKey(EventDefinition.GeneralSchedule))
Console.WriteLine($" GeneralSchedule: {result.Occurrences[EventDefinition.GeneralSchedule].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.MeetTheCandidates))
Console.WriteLine($" MeetTheCandidates: {result.Occurrences[EventDefinition.MeetTheCandidates].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.ChapterOfficerMeeting))
Console.WriteLine($" ChapterOfficerMeeting: {result.Occurrences[EventDefinition.ChapterOfficerMeeting].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.VotingDelegateMeeting))
Console.WriteLine($" VotingDelegateMeeting: {result.Occurrences[EventDefinition.VotingDelegateMeeting].Count} occurrences");
Console.WriteLine($"\n--- Issue Breakdown by Type ---");
foreach (var kvp in issuesByType.OrderByDescending(x => x.Value.Count))
{
var issueType = kvp.Key;
var allIssues = kvp.Value;
expectedByType.TryGetValue(issueType, out var expectedCount);
fixableByType.TryGetValue(issueType, out var fixableCount);
Console.WriteLine($"\n{issueType}:");
Console.WriteLine($" Total: {allIssues.Count} (Expected: {expectedCount}, Fixable: {fixableCount})");
if (fixableCount > 0)
{
var fixableOfType = fixableIssues.Where(i => i.IssueType == issueType).ToList();
var samples = GetSampleLines(fixableOfType, 5);
Console.WriteLine($" Sample fixable issues:");
foreach (var sample in samples)
{
Console.WriteLine(sample);
}
}
}
// Pattern Analysis - LocationParseFailure issues are no longer created (pattern matching removed)
var unmatchedLines = fixableIssues.Where(i => i.IssueType == ParsingIssueType.UnmatchedLine).ToList();
if (unmatchedLines.Any())
{
Console.WriteLine($"\n--- Unmatched Line Analysis ---");
var unmatchedPatterns = unmatchedLines
.GroupBy(i => i.LineContent.Trim())
.OrderByDescending(g => g.Count())
.Take(10);
Console.WriteLine($"Top unmatched line formats:");
foreach (var pattern in unmatchedPatterns)
{
Console.WriteLine($" \"{pattern.Key}\" (appears {pattern.Count()} times)");
}
}
// Test passes if no exceptions were thrown
Assert.Pass($"Successfully parsed {totalParsed} occurrences with {result.Issues.Count} issues ({fixableIssues.Count} fixable)");
@@ -354,82 +375,11 @@ public class EventOccurrenceParser_Tests
// Assert - Should parse without exceptions
Assert.That(result, Is.Not.Null, "Parser should return a result");
// Load file lines for analysis
AnalyzeParsingResults(result, fileInfo, "2025 TN TSA State Competition Event Times Analysis");
var fileLines = File.ReadAllLines(fileInfo.FullName).ToList();
var totalLines = fileLines.Count;
var (_, fixableIssues) = CategorizeIssues(result.Issues, fileLines);
var totalParsed = result.Occurrences.Values.Sum(list => list.Count);
// Categorize issues
var (expectedIssues, fixableIssues) = CategorizeIssues(result.Issues, fileLines);
// Count event sections
var (hsSections, msSections) = CountEventSections(fileLines);
// Group issues by type
var issuesByType = result.Issues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.ToList());
var expectedByType = expectedIssues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.Count());
var fixableByType = fixableIssues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.Count());
// Output analysis
Console.WriteLine($"\n=== 2025 TN TSA State Competition Event Times Analysis ===");
Console.WriteLine($"\n--- Summary Statistics ---");
Console.WriteLine($"Total lines in file: {totalLines}");
Console.WriteLine($"Total occurrences parsed: {totalParsed}");
Console.WriteLine($"Total issues found: {result.Issues.Count}");
Console.WriteLine($" Expected issues (HS-related): {expectedIssues.Count} ({100.0 * expectedIssues.Count / Math.Max(1, result.Issues.Count):F1}%)");
Console.WriteLine($" Fixable issues: {fixableIssues.Count} ({100.0 * fixableIssues.Count / Math.Max(1, result.Issues.Count):F1}%)");
Console.WriteLine($"Event sections: HS={hsSections}, MS={msSections}");
Console.WriteLine($"Events with occurrences: {result.Occurrences.Count}");
Console.WriteLine($"\n--- Special Events Found ---");
if (result.Occurrences.ContainsKey(EventDefinition.GeneralSchedule))
Console.WriteLine($" GeneralSchedule: {result.Occurrences[EventDefinition.GeneralSchedule].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.MeetTheCandidates))
Console.WriteLine($" MeetTheCandidates: {result.Occurrences[EventDefinition.MeetTheCandidates].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.ChapterOfficerMeeting))
Console.WriteLine($" ChapterOfficerMeeting: {result.Occurrences[EventDefinition.ChapterOfficerMeeting].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.VotingDelegateMeeting))
Console.WriteLine($" VotingDelegateMeeting: {result.Occurrences[EventDefinition.VotingDelegateMeeting].Count} occurrences");
Console.WriteLine($"\n--- Issue Breakdown by Type ---");
foreach (var kvp in issuesByType.OrderByDescending(x => x.Value.Count))
{
var issueType = kvp.Key;
var allIssues = kvp.Value;
expectedByType.TryGetValue(issueType, out var expectedCount);
fixableByType.TryGetValue(issueType, out var fixableCount);
Console.WriteLine($"\n{issueType}:");
Console.WriteLine($" Total: {allIssues.Count} (Expected: {expectedCount}, Fixable: {fixableCount})");
if (fixableCount > 0)
{
var fixableOfType = fixableIssues.Where(i => i.IssueType == issueType).ToList();
var samples = GetSampleLines(fixableOfType, 5);
Console.WriteLine($" Sample fixable issues:");
foreach (var sample in samples)
{
Console.WriteLine(sample);
}
}
}
// Pattern Analysis - LocationParseFailure issues are no longer created (pattern matching removed)
var unmatchedLines = fixableIssues.Where(i => i.IssueType == ParsingIssueType.UnmatchedLine).ToList();
if (unmatchedLines.Any())
{
Console.WriteLine($"\n--- Unmatched Line Analysis ---");
var unmatchedPatterns = unmatchedLines
.GroupBy(i => i.LineContent.Trim())
.OrderByDescending(g => g.Count())
.Take(10);
Console.WriteLine($"Top unmatched line formats:");
foreach (var pattern in unmatchedPatterns)
{
Console.WriteLine($" \"{pattern.Key}\" (appears {pattern.Count()} times)");
}
}
// Test passes if no exceptions were thrown
Assert.Pass($"Successfully parsed {totalParsed} occurrences with {result.Issues.Count} issues ({fixableIssues.Count} fixable)");
@@ -449,82 +399,11 @@ public class EventOccurrenceParser_Tests
// Assert - Should parse without exceptions
Assert.That(result, Is.Not.Null, "Parser should return a result");
// Load file lines for analysis
AnalyzeParsingResults(result, fileInfo, "2024 TN TSA State Competition Event Times Analysis");
var fileLines = File.ReadAllLines(fileInfo.FullName).ToList();
var totalLines = fileLines.Count;
var (_, fixableIssues) = CategorizeIssues(result.Issues, fileLines);
var totalParsed = result.Occurrences.Values.Sum(list => list.Count);
// Categorize issues
var (expectedIssues, fixableIssues) = CategorizeIssues(result.Issues, fileLines);
// Count event sections
var (hsSections, msSections) = CountEventSections(fileLines);
// Group issues by type
var issuesByType = result.Issues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.ToList());
var expectedByType = expectedIssues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.Count());
var fixableByType = fixableIssues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.Count());
// Output analysis
Console.WriteLine($"\n=== 2024 TN TSA State Competition Event Times Analysis ===");
Console.WriteLine($"\n--- Summary Statistics ---");
Console.WriteLine($"Total lines in file: {totalLines}");
Console.WriteLine($"Total occurrences parsed: {totalParsed}");
Console.WriteLine($"Total issues found: {result.Issues.Count}");
Console.WriteLine($" Expected issues (HS-related): {expectedIssues.Count} ({100.0 * expectedIssues.Count / Math.Max(1, result.Issues.Count):F1}%)");
Console.WriteLine($" Fixable issues: {fixableIssues.Count} ({100.0 * fixableIssues.Count / Math.Max(1, result.Issues.Count):F1}%)");
Console.WriteLine($"Event sections: HS={hsSections}, MS={msSections}");
Console.WriteLine($"Events with occurrences: {result.Occurrences.Count}");
Console.WriteLine($"\n--- Special Events Found ---");
if (result.Occurrences.ContainsKey(EventDefinition.GeneralSchedule))
Console.WriteLine($" GeneralSchedule: {result.Occurrences[EventDefinition.GeneralSchedule].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.MeetTheCandidates))
Console.WriteLine($" MeetTheCandidates: {result.Occurrences[EventDefinition.MeetTheCandidates].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.ChapterOfficerMeeting))
Console.WriteLine($" ChapterOfficerMeeting: {result.Occurrences[EventDefinition.ChapterOfficerMeeting].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.VotingDelegateMeeting))
Console.WriteLine($" VotingDelegateMeeting: {result.Occurrences[EventDefinition.VotingDelegateMeeting].Count} occurrences");
Console.WriteLine($"\n--- Issue Breakdown by Type ---");
foreach (var kvp in issuesByType.OrderByDescending(x => x.Value.Count))
{
var issueType = kvp.Key;
var allIssues = kvp.Value;
expectedByType.TryGetValue(issueType, out var expectedCount);
fixableByType.TryGetValue(issueType, out var fixableCount);
Console.WriteLine($"\n{issueType}:");
Console.WriteLine($" Total: {allIssues.Count} (Expected: {expectedCount}, Fixable: {fixableCount})");
if (fixableCount > 0)
{
var fixableOfType = fixableIssues.Where(i => i.IssueType == issueType).ToList();
var samples = GetSampleLines(fixableOfType, 5);
Console.WriteLine($" Sample fixable issues:");
foreach (var sample in samples)
{
Console.WriteLine(sample);
}
}
}
// Pattern Analysis - LocationParseFailure issues are no longer created (pattern matching removed)
var unmatchedLines = fixableIssues.Where(i => i.IssueType == ParsingIssueType.UnmatchedLine).ToList();
if (unmatchedLines.Any())
{
Console.WriteLine($"\n--- Unmatched Line Analysis ---");
var unmatchedPatterns = unmatchedLines
.GroupBy(i => i.LineContent.Trim())
.OrderByDescending(g => g.Count())
.Take(10);
Console.WriteLine($"Top unmatched line formats:");
foreach (var pattern in unmatchedPatterns)
{
Console.WriteLine($" \"{pattern.Key}\" (appears {pattern.Count()} times)");
}
}
// Test passes if no exceptions were thrown
Assert.Pass($"Successfully parsed {totalParsed} occurrences with {result.Issues.Count} issues ({fixableIssues.Count} fixable)");
@@ -536,7 +415,7 @@ public class EventOccurrenceParser_Tests
// Arrange
// Extract lines 64-92 from the test file - contains MS and HS events with various formats
var allLines = File.ReadAllLines(TestEntityHandler.GetEventOccurrenceStateFileInfo().FullName);
var sectionLines = allLines.Skip(63).Take(29).ToArray(); // Lines 64-92 (0-indexed: 63-91)
var sectionLines = allLines.Skip(SectionTestStartLineIndex).Take(SectionTestLineCount).ToArray(); // Lines 64-92 (0-indexed: 63-91)
var sectionContent = string.Join("\n", sectionLines);
var tempFile = EventOccurrenceParserTestHelpers.CreateTempFile(sectionContent);
@@ -568,14 +447,14 @@ public class EventOccurrenceParser_Tests
// Total expected MS occurrences: 16
var msEventCount = 0;
if (childrensStories != null && result.Occurrences.ContainsKey(childrensStories))
msEventCount += result.Occurrences[childrensStories].Count;
if (coding != null && result.Occurrences.ContainsKey(coding))
msEventCount += result.Occurrences[coding].Count;
if (communityServiceVideo != null && result.Occurrences.ContainsKey(communityServiceVideo))
msEventCount += result.Occurrences[communityServiceVideo].Count;
if (constructionChallenge != null && result.Occurrences.ContainsKey(constructionChallenge))
msEventCount += result.Occurrences[constructionChallenge].Count;
if (childrensStories != null && result.Occurrences.TryGetValue(childrensStories, out var csOccurrences))
msEventCount += csOccurrences.Count;
if (coding != null && result.Occurrences.TryGetValue(coding, out var codingOccurrences))
msEventCount += codingOccurrences.Count;
if (communityServiceVideo != null && result.Occurrences.TryGetValue(communityServiceVideo, out var csvOccurrences))
msEventCount += csvOccurrences.Count;
if (constructionChallenge != null && result.Occurrences.TryGetValue(constructionChallenge, out var ccOccurrences))
msEventCount += ccOccurrences.Count;
// When no school level is set, HS events should be processed (not skipped)
// Verify HS events are processed or handled appropriately
@@ -585,8 +464,8 @@ public class EventOccurrenceParser_Tests
i.LineNumber >= 72 && i.LineNumber <= 86 && IsHighSchoolEvent(i.LineContent)
).ToList();
// Verify HS section headers are NOT tracked in SkippedHSSectionHeaders when no school level is set
var skippedHSHeaders = result.SkippedHSSectionHeaders;
// Verify HS section headers are NOT tracked in SkippedSectionHeaders when no school level is set
var skippedHeaders = result.SkippedSectionHeaders;
// Verify continuation lines are skipped
// Line 70 starts with "*The" - this enters continuation mode and both line 70 and 71 should be skipped
@@ -610,7 +489,7 @@ public class EventOccurrenceParser_Tests
Console.WriteLine($"MS event occurrences: {msEventCount}");
Console.WriteLine($"Total issues: {result.Issues.Count}");
Console.WriteLine($"HS-related issues: {hsIssues.Count}");
Console.WriteLine($"Skipped HS section headers: {skippedHSHeaders.Count}");
Console.WriteLine($"Skipped section headers: {skippedHeaders.Count}");
Console.WriteLine($"Continuation line issues: {continuationLineIssues.Count}");
Console.WriteLine($"\n--- Issue Types ---");
@@ -625,7 +504,7 @@ public class EventOccurrenceParser_Tests
// When no school level is set, HS events should be processed (not skipped)
// HS events may create issues if they don't match event definitions, which is expected
// HS section headers should NOT be tracked when no school level is set
Assert.That(skippedHSHeaders, Has.Count.EqualTo(0), "HS section headers should NOT be tracked when no school level is set");
Assert.That(skippedHeaders, Has.Count.EqualTo(0), "Section headers should NOT be tracked when no school level is set");
// Line 70 (starts with "*The") enters continuation mode and both line 70 and 71 should be skipped without issues
Assert.That(continuationLineIssues, Has.Count.EqualTo(0),
"Continuation lines starting with '*' and subsequent lines should be skipped without issues");
@@ -633,9 +512,9 @@ public class EventOccurrenceParser_Tests
Assert.That(lateTimeOccurrence, Is.Not.Null, "Should parse 11:59 p.m. time format");
// Verify specific locations are parsed
if (childrensStories != null && result.Occurrences.ContainsKey(childrensStories))
if (childrensStories != null && result.Occurrences.TryGetValue(childrensStories, out var childrensStoriesOccurrences))
{
var locations = result.Occurrences[childrensStories]
var locations = childrensStoriesOccurrences
.Select(eo => eo.Location)
.Where(loc => !string.IsNullOrWhiteSpace(loc))
.ToList();
@@ -680,13 +559,12 @@ public class EventOccurrenceParser_Tests
// When no school level is set, all events (MS and HS) should be processed
// HS section header should NOT be skipped (note: normalized to regular hyphen)
Assert.That(result.SkippedHSSectionHeaders, Does.Not.Contain("Biotechnology Design - HS"),
"HS section header should NOT be in SkippedHSSectionHeaders when no school level is set");
Assert.That(result.SkippedSectionHeaders, Does.Not.Contain("Biotechnology Design - HS"),
"HS section header should NOT be in SkippedSectionHeaders when no school level is set");
// With no school level filtering, both MS and HS events are processed
if (result.Occurrences.ContainsKey(biotechnology))
if (result.Occurrences.TryGetValue(biotechnology, out var allOccurrences))
{
var allOccurrences = result.Occurrences[biotechnology];
// With no school level set, we process all occurrences (both MS and HS)
// Expected: 2 MS occurrences (Submit Entry, Judging) + 3 HS occurrences (Submit Entry, Judging, Pick-up) = 5 total
Assert.That(allOccurrences, Has.Count.EqualTo(5),