using Core.Entities; using Core.Models; using FuzzySharp; namespace GoogleSheetsScheduleImport; /// /// Maps a sheet cell title to the closest event definition (fuzzy) plus MS/HS for section headers. /// public static class OccurrenceEventMatcher { private const int MinTokenScore = 62; public static bool TryMatch( string occurrenceName, IReadOnlyList events, out EventDefinition? matched, out SchoolLevel? level) { matched = null; level = null; if (string.IsNullOrWhiteSpace(occurrenceName) || events.Count == 0) return false; var normalized = TextNormalization.ForEmitLine(occurrenceName); var (remainder, prefixLevel) = SchoolLevelPrefixParser.StripLeadingSchoolPrefix(normalized); // Section headers require "Event Name - MS|HS". Titles without a clear school prefix stay in General Schedule. if (!prefixLevel.HasValue) return false; var best = FindBest(remainder, events); if (best == null || string.IsNullOrWhiteSpace(remainder)) return false; matched = best; level = prefixLevel; return true; } private static EventDefinition? FindBest(string text, IReadOnlyList events) { if (string.IsNullOrWhiteSpace(text)) return null; EventDefinition? best = null; var bestScore = 0; foreach (var e in events) { if (string.IsNullOrWhiteSpace(e.Name)) continue; var score = Math.Max( Fuzz.TokenSetRatio(text, e.Name), Fuzz.PartialRatio(text, e.Name)); if (score > bestScore) { bestScore = score; best = e; } else if (score == bestScore && best != null && e.Name.Length > best.Name.Length) best = e; } if (best == null || bestScore < MinTokenScore) return null; return best; } }