Enhance event occurrence parsing to skip unmatched high school section headers
This commit introduces a new property to track skipped high school section headers in the EventOccurrenceParseResult and EventOccurrenceParserResult classes. The EventOccurrenceParser has been updated to gracefully skip HS section headers that do not match any event definitions, improving the parsing logic. Additionally, the LocationParsingConfiguration has been removed from the EventOccurrenceParser, simplifying its constructor. Unit tests have been updated to reflect these changes and ensure correct behavior during parsing.
This commit is contained in:
@@ -96,55 +96,6 @@ public class EventOccurrenceParser_Tests
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes location parsing failures and extracts common patterns.
|
||||
/// </summary>
|
||||
private static Dictionary<string, int> AnalyzeLocationFailures(
|
||||
List<ParsingIssue> locationIssues, List<string> fileLines)
|
||||
{
|
||||
var locationPatterns = new Dictionary<string, int>();
|
||||
|
||||
foreach (var issue in locationIssues)
|
||||
{
|
||||
// Try to extract the location part from the line
|
||||
// The format is typically: "EventName Month Day Time Location"
|
||||
var parts = issue.LineContent.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// Look for location-like strings (usually after time)
|
||||
// This is a heuristic - we'll look for parts that don't match date/time patterns
|
||||
var timeRegex = new System.Text.RegularExpressions.Regex(
|
||||
@"\d{1,2}:?\d{0,2}\s*[AaPp]\.?[Mm]\.?|NOON");
|
||||
|
||||
bool foundTime = false;
|
||||
var locationParts = new List<string>();
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
if (timeRegex.IsMatch(part) || part == "NOON")
|
||||
{
|
||||
foundTime = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (foundTime && !string.IsNullOrWhiteSpace(part))
|
||||
{
|
||||
locationParts.Add(part);
|
||||
}
|
||||
}
|
||||
|
||||
if (locationParts.Any())
|
||||
{
|
||||
var location = string.Join(" ", locationParts).Trim();
|
||||
if (!string.IsNullOrWhiteSpace(location))
|
||||
{
|
||||
locationPatterns.TryGetValue(location, out var count);
|
||||
locationPatterns[location] = count + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return locationPatterns;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts HS vs MS event sections in the file.
|
||||
@@ -300,8 +251,7 @@ public class EventOccurrenceParser_Tests
|
||||
// Arrange
|
||||
var events = TestEntityHandler.GetEvents();
|
||||
var fileInfo = TestEntityHandler.GetEventOccurrenceNationalsFileInfo();
|
||||
var locationConfig = LocationParsingConfiguration.Default;
|
||||
var parser = new EventOccurrenceParser(fileInfo, events, locationConfig);
|
||||
var parser = new EventOccurrenceParser(fileInfo, events);
|
||||
|
||||
// Act
|
||||
var result = parser.Parse();
|
||||
@@ -369,19 +319,7 @@ public class EventOccurrenceParser_Tests
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern Analysis
|
||||
var locationFailures = fixableIssues.Where(i => i.IssueType == ParsingIssueType.LocationParseFailure).ToList();
|
||||
if (locationFailures.Any())
|
||||
{
|
||||
Console.WriteLine($"\n--- Location Parse Failure Analysis ---");
|
||||
var locationPatterns = AnalyzeLocationFailures(locationFailures, fileLines);
|
||||
var topLocations = locationPatterns.OrderByDescending(x => x.Value).Take(10);
|
||||
Console.WriteLine($"Top unmatched location strings:");
|
||||
foreach (var loc in topLocations)
|
||||
{
|
||||
Console.WriteLine($" \"{loc.Key}\" (appears {loc.Value} times)");
|
||||
}
|
||||
}
|
||||
// Pattern Analysis - LocationParseFailure issues are no longer created (pattern matching removed)
|
||||
|
||||
var unmatchedLines = fixableIssues.Where(i => i.IssueType == ParsingIssueType.UnmatchedLine).ToList();
|
||||
if (unmatchedLines.Any())
|
||||
@@ -408,8 +346,7 @@ public class EventOccurrenceParser_Tests
|
||||
// Arrange
|
||||
var events = TestEntityHandler.GetEvents();
|
||||
var fileInfo = TestEntityHandler.GetEventOccurrenceStateFileInfo();
|
||||
var locationConfig = LocationParsingConfiguration.Default;
|
||||
var parser = new EventOccurrenceParser(fileInfo, events, locationConfig);
|
||||
var parser = new EventOccurrenceParser(fileInfo, events);
|
||||
|
||||
// Act
|
||||
var result = parser.Parse();
|
||||
@@ -477,19 +414,7 @@ public class EventOccurrenceParser_Tests
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern Analysis
|
||||
var locationFailures = fixableIssues.Where(i => i.IssueType == ParsingIssueType.LocationParseFailure).ToList();
|
||||
if (locationFailures.Any())
|
||||
{
|
||||
Console.WriteLine($"\n--- Location Parse Failure Analysis ---");
|
||||
var locationPatterns = AnalyzeLocationFailures(locationFailures, fileLines);
|
||||
var topLocations = locationPatterns.OrderByDescending(x => x.Value).Take(10);
|
||||
Console.WriteLine($"Top unmatched location strings:");
|
||||
foreach (var loc in topLocations)
|
||||
{
|
||||
Console.WriteLine($" \"{loc.Key}\" (appears {loc.Value} times)");
|
||||
}
|
||||
}
|
||||
// Pattern Analysis - LocationParseFailure issues are no longer created (pattern matching removed)
|
||||
|
||||
var unmatchedLines = fixableIssues.Where(i => i.IssueType == ParsingIssueType.UnmatchedLine).ToList();
|
||||
if (unmatchedLines.Any())
|
||||
@@ -516,8 +441,7 @@ public class EventOccurrenceParser_Tests
|
||||
// Arrange
|
||||
var events = TestEntityHandler.GetEvents();
|
||||
var fileInfo = TestEntityHandler.GetEventOccurrenceState2024FileInfo();
|
||||
var locationConfig = LocationParsingConfiguration.Default;
|
||||
var parser = new EventOccurrenceParser(fileInfo, events, locationConfig);
|
||||
var parser = new EventOccurrenceParser(fileInfo, events);
|
||||
|
||||
// Act
|
||||
var result = parser.Parse();
|
||||
@@ -585,19 +509,7 @@ public class EventOccurrenceParser_Tests
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern Analysis
|
||||
var locationFailures = fixableIssues.Where(i => i.IssueType == ParsingIssueType.LocationParseFailure).ToList();
|
||||
if (locationFailures.Any())
|
||||
{
|
||||
Console.WriteLine($"\n--- Location Parse Failure Analysis ---");
|
||||
var locationPatterns = AnalyzeLocationFailures(locationFailures, fileLines);
|
||||
var topLocations = locationPatterns.OrderByDescending(x => x.Value).Take(10);
|
||||
Console.WriteLine($"Top unmatched location strings:");
|
||||
foreach (var loc in topLocations)
|
||||
{
|
||||
Console.WriteLine($" \"{loc.Key}\" (appears {loc.Value} times)");
|
||||
}
|
||||
}
|
||||
// Pattern Analysis - LocationParseFailure issues are no longer created (pattern matching removed)
|
||||
|
||||
var unmatchedLines = fixableIssues.Where(i => i.IssueType == ParsingIssueType.UnmatchedLine).ToList();
|
||||
if (unmatchedLines.Any())
|
||||
@@ -617,4 +529,124 @@ public class EventOccurrenceParser_Tests
|
||||
// Test passes if no exceptions were thrown
|
||||
Assert.Pass($"Successfully parsed {totalParsed} occurrences with {result.Issues.Count} issues ({fixableIssues.Count} fixable)");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Parse_Section_Lines64To92_ChildrensStoriesToConstructionChallenge()
|
||||
{
|
||||
// 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 sectionContent = string.Join("\n", sectionLines);
|
||||
|
||||
var tempFile = EventOccurrenceParserTestHelpers.CreateTempFile(sectionContent);
|
||||
var events = TestEntityHandler.GetEvents();
|
||||
var parser = new EventOccurrenceParser(tempFile, events);
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
var result = parser.Parse();
|
||||
|
||||
// Assert - Should parse without exceptions
|
||||
Assert.That(result, Is.Not.Null, "Parser should return a result");
|
||||
|
||||
// Count occurrences by event type
|
||||
var totalOccurrences = result.Occurrences.Values.Sum(list => list.Count);
|
||||
|
||||
// Verify MS events are parsed
|
||||
var childrensStories = events.FirstOrDefault(e => e.Name.Contains("Children's Stories", StringComparison.OrdinalIgnoreCase));
|
||||
var coding = events.FirstOrDefault(e => e.Name == "Coding");
|
||||
var communityServiceVideo = events.FirstOrDefault(e => e.Name.Contains("Community Service Video", StringComparison.OrdinalIgnoreCase));
|
||||
var constructionChallenge = events.FirstOrDefault(e => e.Name.Contains("Construction Challenge", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// Count expected MS occurrences:
|
||||
// Children's Stories – MS: 5 occurrences (lines 65-69)
|
||||
// Coding – MS: 2 occurrences (lines 76-77)
|
||||
// Community Service Video – MS: 4 occurrences (lines 79-82)
|
||||
// Construction Challenge – MS: 5 occurrences (lines 88-92)
|
||||
// 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;
|
||||
|
||||
// Verify HS events are skipped gracefully (no issues should be created for them)
|
||||
var hsIssues = result.Issues.Where(i =>
|
||||
i.LineContent.Contains("Coding – HS") ||
|
||||
i.LineContent.Contains("CAD") && i.LineContent.Contains("HS") ||
|
||||
i.LineNumber >= 72 && i.LineNumber <= 86 && IsHighSchoolEvent(i.LineContent)
|
||||
).ToList();
|
||||
|
||||
// Verify HS section headers are tracked in SkippedHSSectionHeaders
|
||||
var skippedHSHeaders = result.SkippedHSSectionHeaders;
|
||||
|
||||
// Verify continuation lines are skipped
|
||||
// Line 70 starts with "*The" - this enters continuation mode and both line 70 and 71 should be skipped
|
||||
var continuationLineIssues = result.Issues.Where(i =>
|
||||
i.LineContent.Contains("books of semifinalist teams") ||
|
||||
i.LineContent.Contains("be returned to teams")
|
||||
).ToList();
|
||||
|
||||
// Verify specific time formats are parsed correctly
|
||||
var noonOccurrence = result.Occurrences.Values
|
||||
.SelectMany(list => list)
|
||||
.FirstOrDefault(eo => eo.Time.Contains("NOON", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var lateTimeOccurrence = result.Occurrences.Values
|
||||
.SelectMany(list => list)
|
||||
.FirstOrDefault(eo => eo.Time.Contains("11:59", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// Output detailed analysis
|
||||
Console.WriteLine($"\n=== Section Lines 64-92 Parsing Results ===");
|
||||
Console.WriteLine($"Total occurrences parsed: {totalOccurrences}");
|
||||
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($"Continuation line issues: {continuationLineIssues.Count}");
|
||||
|
||||
Console.WriteLine($"\n--- Issue Types ---");
|
||||
foreach (var issueType in result.Issues.GroupBy(i => i.IssueType))
|
||||
{
|
||||
Console.WriteLine($" {issueType.Key}: {issueType.Count()}");
|
||||
}
|
||||
|
||||
// Assertions
|
||||
Assert.That(totalOccurrences, Is.GreaterThan(0), "Should parse at least some occurrences");
|
||||
Assert.That(msEventCount, Is.GreaterThanOrEqualTo(14), "Should parse most MS occurrences (at least 14 out of 16)");
|
||||
// HS events should not create issues - they should be skipped gracefully
|
||||
Assert.That(hsIssues, Has.Count.EqualTo(0), "HS events should be skipped gracefully without creating issues");
|
||||
// HS section headers should be tracked
|
||||
Assert.That(skippedHSHeaders, Has.Count.GreaterThanOrEqualTo(2), "Should track at least 2 HS section headers (Coding - HS, CAD Architecture - HS, CAD Engineering - HS)");
|
||||
// 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");
|
||||
Assert.That(noonOccurrence, Is.Not.Null, "Should parse NOON time format");
|
||||
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))
|
||||
{
|
||||
var locations = result.Occurrences[childrensStories]
|
||||
.Select(eo => eo.Location)
|
||||
.Where(loc => !string.IsNullOrWhiteSpace(loc))
|
||||
.ToList();
|
||||
Assert.That(locations, Has.Count.GreaterThan(0), "Children's Stories should have locations parsed");
|
||||
}
|
||||
|
||||
// Test passes with detailed information
|
||||
Assert.Pass($"Successfully parsed section: {totalOccurrences} occurrences, {result.Issues.Count} issues, {msEventCount} MS events");
|
||||
}
|
||||
finally
|
||||
{
|
||||
EventOccurrenceParserTestHelpers.CleanupTempFile(tempFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user