Let advisors load preference ranks from a converted CSV with fuzzy matching, known aliases, and a parse-preview-save page instead of relying on the ranking editor. Co-authored-by: Cursor <cursoragent@cursor.com>
108 lines
3.0 KiB
C#
108 lines
3.0 KiB
C#
using Core.Entities;
|
|
|
|
namespace Core.Models;
|
|
|
|
/// <summary>
|
|
/// Result of parsing a student event ranking CSV.
|
|
/// </summary>
|
|
public class StudentEventRankingParseResult
|
|
{
|
|
/// <summary>
|
|
/// Accepted ranking matches, including the raw CSV text and fuzzy scores.
|
|
/// </summary>
|
|
public List<StudentEventRankingMatch> Matches { get; set; } = [];
|
|
|
|
/// <summary>
|
|
/// Unmatched students, unmatched or ambiguous events, and other row-level issues.
|
|
/// </summary>
|
|
public List<StudentEventRankingIssue> Issues { get; set; } = [];
|
|
|
|
/// <summary>
|
|
/// Critical errors that prevented parsing (for example a missing header).
|
|
/// </summary>
|
|
public List<string> Errors { get; set; } = [];
|
|
|
|
/// <summary>
|
|
/// Non-critical warnings about the file as a whole.
|
|
/// </summary>
|
|
public List<string> Warnings { get; set; } = [];
|
|
|
|
/// <summary>
|
|
/// Accepted rankings without match metadata.
|
|
/// </summary>
|
|
public IReadOnlyList<StudentEventRanking> Rankings => [.. Matches.Select(m => m.Ranking)];
|
|
|
|
/// <summary>
|
|
/// Number of accepted ranking rows.
|
|
/// </summary>
|
|
public int TotalParsed => Matches.Count;
|
|
|
|
/// <summary>
|
|
/// Distinct students who have at least one accepted rank.
|
|
/// Uses Id when assigned, otherwise first and last name, so unsaved parsed students stay distinct.
|
|
/// </summary>
|
|
public IReadOnlyList<Student> StudentsWithAcceptedRanks =>
|
|
[.. Matches
|
|
.Select(m => m.Ranking.Student)
|
|
.GroupBy(s => s.Id != 0 ? $"id:{s.Id}" : $"name:{s.FirstName}|{s.LastName}")
|
|
.Select(g => g.First())];
|
|
|
|
/// <summary>
|
|
/// True when no critical parse errors were recorded.
|
|
/// </summary>
|
|
public bool IsSuccess => Errors.Count == 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// A successfully matched ranking cell from the CSV.
|
|
/// </summary>
|
|
public class StudentEventRankingMatch
|
|
{
|
|
public required StudentEventRanking Ranking { get; set; }
|
|
|
|
public string RawStudentName { get; set; } = string.Empty;
|
|
|
|
public string RawEventName { get; set; } = string.Empty;
|
|
|
|
public int StudentScore { get; set; }
|
|
|
|
public int EventScore { get; set; }
|
|
|
|
public int RowNumber { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// A row-level problem encountered while parsing rankings.
|
|
/// </summary>
|
|
public class StudentEventRankingIssue
|
|
{
|
|
public int RowNumber { get; set; }
|
|
|
|
public int Rank { get; set; }
|
|
|
|
public string RawStudentName { get; set; } = string.Empty;
|
|
|
|
public string RawEventName { get; set; } = string.Empty;
|
|
|
|
public StudentEventRankingIssueType IssueType { get; set; }
|
|
|
|
public string Message { get; set; } = string.Empty;
|
|
|
|
public string? SuggestedEventName { get; set; }
|
|
|
|
public int? Score { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Types of issues reported while parsing student event rankings.
|
|
/// </summary>
|
|
public enum StudentEventRankingIssueType
|
|
{
|
|
UnmatchedStudent,
|
|
UnmatchedEvent,
|
|
AmbiguousEvent,
|
|
DuplicateEvent,
|
|
DuplicateRank,
|
|
InvalidFormat
|
|
}
|