using Core.Entities; using Core.Models; namespace Core.Validation.Rules.BaseRules; /// /// Base class for validation rules that check event counts against thresholds /// Eliminates duplication across TooManyEvents, TooFewEvents, and TooManyRegionalEvents rules /// public abstract class EventCountThresholdRuleBase : IValidationRule { /// /// Get the actual count to validate /// protected abstract int GetCount(StudentEventStatistics statistics); /// /// Get the threshold value from configuration /// protected abstract int GetThreshold(ValidationConfiguration config); /// /// Check if the count violates the threshold /// protected abstract bool ViolatesThreshold(int count, int threshold); /// /// Get the base severity from configuration (can be overridden for critical thresholds) /// protected abstract ValidationSeverity GetBaseSeverity(ValidationConfiguration config); /// /// Optionally check for critical threshold that escalates to Error severity /// Returns null if no critical threshold applies /// protected virtual int? GetCriticalThreshold(ValidationConfiguration config) => null; /// /// Check if count violates critical threshold (for escalation to Error) /// protected virtual bool ViolatesCriticalThreshold(int count, int criticalThreshold) => false; /// /// The validation warning code /// protected abstract string Code { get; } /// /// Build the display message /// protected abstract string GetMessage(int count, int threshold); /// /// Icon identifier for the warning (can be null) /// protected virtual string? IconIdentifier => null; /// /// Build additional metadata beyond the standard fields /// protected virtual Dictionary BuildAdditionalMetadata(StudentEventStatistics statistics, ValidationConfiguration config) { return new Dictionary(); } public ValidationWarning? Validate(StudentEventStatistics entity, ValidationConfiguration config) { var count = GetCount(entity); var threshold = GetThreshold(config); if (!ViolatesThreshold(count, threshold)) return null; // Check for critical threshold escalation var severity = GetBaseSeverity(config); var criticalThreshold = GetCriticalThreshold(config); if (criticalThreshold.HasValue && ViolatesCriticalThreshold(count, criticalThreshold.Value)) { severity = ValidationSeverity.Error; } var metadata = new Dictionary { { "StudentId", entity.Student.Id }, { "StudentName", entity.Student.FirstNameLastName }, { "EventCount", entity.EventCount } }; // Add any additional metadata from derived class foreach (var kvp in BuildAdditionalMetadata(entity, config)) { metadata[kvp.Key] = kvp.Value; } return new ValidationWarning { Code = Code, Message = GetMessage(count, threshold), Severity = severity, Context = ValidationContext.StudentAssignment, IconIdentifier = IconIdentifier, Metadata = metadata }; } public bool AppliesTo(ValidationContext context) => context == ValidationContext.StudentAssignment || context == ValidationContext.StudentRegistration; }