Enhance event occurrence parsing with school level filtering

This commit introduces a new SchoolLevel enum and updates the EventOccurrenceParser to filter event occurrences based on the specified school level (Middle School or High School). The EventOccurrenceParseResult and EventOccurrenceParserResult classes have been updated to track skipped section headers and counts for both school levels. Additionally, the EventOccurrenceParserService has been modified to read the school level from configuration, and the UI has been updated to allow users to select the school level for event imports. This enhancement improves the accuracy of event parsing and provides better user feedback on skipped occurrences.
This commit is contained in:
2026-01-09 09:39:00 -05:00
parent ea1a4a04ad
commit 8183c0200d
7 changed files with 270 additions and 27 deletions
+142 -22
View File
@@ -3,6 +3,7 @@ using Core.Entities;
using Core.Models;
using EventOccurrenceParsers = Core.Parsers.EventOccurrence;
using Core.Utility;
using SchoolLevel = Core.Models.SchoolLevel;
namespace Core.Parsers;
@@ -14,17 +15,22 @@ public class EventOccurrenceParserResult
public IDictionary<EventDefinition, List<Entities.EventOccurrence>> Occurrences { get; set; } = new Dictionary<EventDefinition, List<Entities.EventOccurrence>>();
public List<ParsingIssue> Issues { get; set; } = new();
public List<string> SkippedHSSectionHeaders { get; set; } = new();
public List<string> SkippedMSSectionHeaders { get; set; } = new();
public int SkippedMSEventCount { get; set; }
public int SkippedHSEventCount { get; set; }
}
public class EventOccurrenceParser
{
private FileSystemInfo _txtFile;
private ICollection<EventDefinition> _events;
private SchoolLevel? _schoolLevel;
public EventOccurrenceParser(FileSystemInfo txtFile, ICollection<EventDefinition> events)
public EventOccurrenceParser(FileSystemInfo txtFile, ICollection<EventDefinition> events, SchoolLevel? schoolLevel = null)
{
_events = events;
_txtFile = txtFile;
_schoolLevel = schoolLevel;
}
public EventOccurrenceParserResult Parse()
@@ -35,6 +41,7 @@ public class EventOccurrenceParser
EventDefinition? currentEventDefinition = null;
bool inContinuationMode = false;
bool inHSSection = false;
bool inMSSection = false;
var lines = File.ReadLines(_txtFile.FullName);
foreach (var (line, index) in lines.Select((line, index) => (line, index + 1)))
@@ -73,22 +80,71 @@ public class EventOccurrenceParser
// Section headers break continuation mode
inContinuationMode = false;
// Check if this is an HS event - if so, skip gracefully regardless of whether it matches
// This prevents HS events from being incorrectly associated with MS events (e.g.,
// "Biotechnology Design HS" matching "Biotechnology" MS event)
if (schoolLevel.Equals("HS", StringComparison.OrdinalIgnoreCase))
// Determine if we should skip this event based on chapter's school level setting
bool shouldSkip = false;
if (!string.IsNullOrWhiteSpace(schoolLevel))
{
if (_schoolLevel.HasValue)
{
// School level is set - filter based on it
if (_schoolLevel.Value == SchoolLevel.MiddleSchool &&
schoolLevel.Equals("HS", StringComparison.OrdinalIgnoreCase))
{
shouldSkip = true;
result.SkippedHSSectionHeaders.Add(normalizedLine);
inHSSection = true;
inMSSection = false;
}
else if (_schoolLevel.Value == SchoolLevel.HighSchool &&
schoolLevel.Equals("MS", StringComparison.OrdinalIgnoreCase))
{
shouldSkip = true;
result.SkippedMSSectionHeaders.Add(normalizedLine);
inMSSection = true;
inHSSection = false;
}
}
else
{
// No school level set - backward compatibility: skip HS events
if (schoolLevel.Equals("HS", StringComparison.OrdinalIgnoreCase))
{
shouldSkip = true;
result.SkippedHSSectionHeaders.Add(normalizedLine);
inHSSection = true;
inMSSection = false;
}
}
}
if (shouldSkip)
{
result.SkippedHSSectionHeaders.Add(normalizedLine);
currentEventDefinition = null; // Skip subsequent occurrences
inHSSection = true; // Mark that we're in an HS section
continue; // No issue created
}
// For MS events, use fuzzy matching to find the best matching event definition
// Reset section flags for events we're processing
if (schoolLevel.Equals("MS", StringComparison.OrdinalIgnoreCase))
{
inMSSection = true;
inHSSection = false;
}
else if (schoolLevel.Equals("HS", StringComparison.OrdinalIgnoreCase))
{
inHSSection = true;
inMSSection = false;
}
else
{
inHSSection = false;
inMSSection = false;
}
// Use fuzzy matching to find the best matching event definition
var evt = EventOccurrenceParsers.SectionHeaderMatcher.MatchEventDefinition(eventNamePart, _events);
if (evt == null)
{
// For unmatched MS headers, create issue
// For unmatched headers, create issue
var bestRatio = EventOccurrenceParsers.SectionHeaderMatcher.GetBestMatchRatio(eventNamePart, _events);
issues.Add(new ParsingIssue
{
@@ -100,7 +156,6 @@ public class EventOccurrenceParser
continue;
}
currentEventDefinition = evt;
inHSSection = false; // Reset HS section flag for MS events
continue;
}
@@ -109,7 +164,8 @@ public class EventOccurrenceParser
{
// General schedule breaks continuation mode
inContinuationMode = false;
inHSSection = false; // Reset HS section flag
inHSSection = false; // Reset section flags
inMSSection = false;
currentEventDefinition = EventDefinition.GeneralSchedule;
continue;
}
@@ -120,21 +176,68 @@ public class EventOccurrenceParser
// Section headers break continuation mode
inContinuationMode = false;
// Check if this is an HS event - if so, skip gracefully regardless of whether it matches
// This prevents HS events from being incorrectly associated with MS events
if (normalizedLine.Contains("HS", StringComparison.OrdinalIgnoreCase))
// Determine if we should skip this event based on chapter's school level setting
bool shouldSkip = false;
if (_schoolLevel.HasValue)
{
// School level is set - filter based on it
if (_schoolLevel.Value == SchoolLevel.MiddleSchool &&
normalizedLine.Contains("HS", StringComparison.OrdinalIgnoreCase))
{
shouldSkip = true;
result.SkippedHSSectionHeaders.Add(normalizedLine);
inHSSection = true;
inMSSection = false;
}
else if (_schoolLevel.Value == SchoolLevel.HighSchool &&
normalizedLine.Contains("MS", StringComparison.OrdinalIgnoreCase))
{
shouldSkip = true;
result.SkippedMSSectionHeaders.Add(normalizedLine);
inMSSection = true;
inHSSection = false;
}
}
else
{
// No school level set - backward compatibility: skip HS events
if (normalizedLine.Contains("HS", StringComparison.OrdinalIgnoreCase))
{
shouldSkip = true;
result.SkippedHSSectionHeaders.Add(normalizedLine);
inHSSection = true;
inMSSection = false;
}
}
if (shouldSkip)
{
result.SkippedHSSectionHeaders.Add(normalizedLine);
currentEventDefinition = null; // Skip subsequent occurrences
inHSSection = true; // Mark that we're in an HS section
continue; // No issue created
}
// For MS events, use fuzzy matching to find the best matching event definition
// Reset section flags for events we're processing
if (normalizedLine.Contains("MS", StringComparison.OrdinalIgnoreCase))
{
inMSSection = true;
inHSSection = false;
}
else if (normalizedLine.Contains("HS", StringComparison.OrdinalIgnoreCase))
{
inHSSection = true;
inMSSection = false;
}
else
{
inHSSection = false;
inMSSection = false;
}
// Use fuzzy matching to find the best matching event definition
var evt = EventOccurrenceParsers.SectionHeaderMatcher.MatchEventDefinition(normalizedLine, _events);
if (evt == null)
{
// For unmatched MS headers, create issue
// For unmatched headers, create issue
var bestRatio = EventOccurrenceParsers.SectionHeaderMatcher.GetBestMatchRatio(normalizedLine, _events);
issues.Add(new ParsingIssue
{
@@ -146,7 +249,6 @@ public class EventOccurrenceParser
continue;
}
currentEventDefinition = evt;
inHSSection = false; // Reset HS section flag for MS events
continue;
}
@@ -180,10 +282,28 @@ public class EventOccurrenceParser
// Occurrence lines break continuation mode
inContinuationMode = false;
// Skip occurrences under HS sections (they won't match any event definition)
if (inHSSection)
// Skip occurrences under sections that don't match the school level setting
if (_schoolLevel.HasValue)
{
continue;
if (_schoolLevel.Value == SchoolLevel.MiddleSchool && inHSSection)
{
result.SkippedHSEventCount++;
continue;
}
if (_schoolLevel.Value == SchoolLevel.HighSchool && inMSSection)
{
result.SkippedMSEventCount++;
continue;
}
}
else
{
// If no school level is set, skip HS sections (backward compatibility)
if (inHSSection)
{
result.SkippedHSEventCount++;
continue;
}
}
var (occurrenceName, month, dayOfMonthStr, timeAndLocation) = occurrenceLine.Value;