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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user