Files
chapter-organizer/VALIDATION-IMPROVEMENTS-PLAN.md
poprhythm 73ad730b38 Add comprehensive validation system with runtime configuration
Implements a flexible validation framework for student rankings, team assignments, and team composition with administrator-configurable rules and thresholds.

Validation System:
- Reflection-based rule discovery eliminates manual registration
- Base classes (RequiredEventTypeRuleBase, EventCountThresholdRuleBase) reduce code duplication by ~250 lines
- 12 validation rules covering event requirements, counts, and team constraints
- Configurable severity levels (Warning/Error) per rule type
- ValidationService with caching for optimal performance
- Rules apply contextually (StudentRanking, StudentAssignment, TeamComposition)

Configuration & Admin UI:
- ValidationSettings admin page for editing thresholds and severity levels
- ChapterSettings admin page for editing chapter information
- Settings stored in Data/appsettings.json for runtime configuration
- JSON config works in both development and production environments
- Auto-creates Data directory and config template on first run

User Experience:
- ValidationWarnings component displays inline warnings/errors
- Integrated in Event Ranking, Registration, and Team Assignment pages
- Color-coded severity indicators (warning yellow, error red)
- Includes "Too Many Regional Events" rule (max 3 recommended)

Technical Improvements:
- Template Method pattern for rule base classes
- Singleton rule instances with lazy initialization
- Configuration loaded via IConfiguration with fallback to defaults
- Safe JSON updates preserve other appsettings sections

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-13 22:15:26 -05:00

575 lines
19 KiB
Markdown

# Validation System Improvements & Rules Engine Evaluation
## Executive Summary
**Current State:** Custom validation system with 10 C# rules across 3 entity types. Clean architecture but hardcoded rule registration, requires recompilation for changes.
**Critical Requirement:** Runtime-editable validation rules for different chapters (from user feedback).
**Recommendation:** **Hybrid approach** - Improve current implementation + add JSON-based configuration storage, avoiding full rules engine adoption to maintain simplicity while enabling runtime customization.
## Analysis Results
### Current Implementation Assessment
**Strengths:**
- Clean generic design (`IValidationRule<TEntity>`)
- Type-safe validation
- Clear separation of concerns
- Context-aware rule execution
- Well-organized directory structure (StudentRankingRules, StudentAssignmentRules, TeamRules)
**Critical Issues:**
1. **Hardcoded rule registration** - Rules manually added in `ValidationService.InitializeRules()` (Open/Closed violation)
2. **Requires recompilation** - Cannot change rules or thresholds without code deployment
3. **Rule duplication** - StudentRankingRules and StudentAssignmentRules have duplicate logic
4. **Non-scalable configuration** - One property per rule severity (6 specific properties)
5. **No rule discovery** - Cannot dynamically load or register rules
6. **Inconsistent context handling** - `ValidateTeam()` doesn't check `AppliesTo(context)`
**Performance Concerns:**
- Rule objects instantiated per ValidationService (no singleton/pooling)
- No short-circuit logic (all rules execute even after critical errors)
- Batch operations materialize entire dictionary in memory
### Rules Engine Library Research
#### Microsoft RulesEngine
[GitHub - microsoft/RulesEngine](https://github.com/microsoft/RulesEngine) | [Documentation](https://microsoft.github.io/RulesEngine/)
**Features:**
- JSON-based rule definitions (runtime editable)
- Lambda expression support in rules
- Global/local parameters (reusable logic)
- Custom actions (v3+)
- Custom type injection
- Flexible storage (Azure Blob, Cosmos DB, files, EF Core, SQL)
- Actively maintained (tutorial from October 2025)
**Example Rule:**
```json
{
"WorkflowName": "StudentValidation",
"Rules": [
{
"RuleName": "RequireRegionalEvent",
"Expression": "input.RankedEvents.Any(e => e.RegionalEvent == true)",
"ErrorMessage": "No Regional Event",
"ErrorType": "Warning"
}
]
}
```
**Pros:**
- ✅ Enables runtime editing without recompilation
- ✅ Proven, maintained library (Microsoft)
- ✅ Reduces custom code maintenance
- ✅ Flexible rule storage options
- ✅ Supports complex expressions
**Cons:**
- ❌ Significant complexity (JSON + lambda expressions as strings)
- ❌ Learning curve for team
- ❌ All rules must be rewritten in JSON
- ❌ Type safety lost (expressions are strings)
- ❌ Debugging more difficult (no compile-time checks)
- ❌ External dependency
#### Alternative: NRules
[GitHub - NRules/NRules](https://github.com/NRules/NRules) | [Website](https://nrules.net/)
**Characteristics:**
- Forward-chaining rules engine based on Rete algorithm
- C# internal DSL for rule authoring
- More complex than RulesEngine
- Better for complex decision trees and rule dependencies
**Assessment:** Overkill for TSA validation use case. Designed for complex business logic scenarios with many interdependent rules.
#### Alternative: FluentValidation
[GitHub - FluentValidation](https://github.com/FluentValidation/FluentValidation) | [NuGet](https://www.nuget.org/packages/fluentvalidation/)
**Characteristics:**
- Validation-specific library (NOT a rules engine)
- Fluent interface for building validation rules
- Strongly-typed C# validation rules
**Assessment:** Not applicable - FluentValidation is for object validation, not business rule execution. Different use case.
### Decision Matrix
| Aspect | Current Implementation | Improve Current + JSON Config | Full RulesEngine Adoption |
|--------|------------------------|------------------------------|--------------------------|
| **Runtime Editing** | ❌ No (requires recompile) | ✅ Yes (configuration only) | ✅ Yes (rules + config) |
| **Simplicity** | ✅ Simple C# rules | ✅ C# rules + JSON config | ❌ Complex JSON + lambdas |
| **Type Safety** | ✅ Full compile-time checks | ✅ Rules are type-safe | ❌ String-based expressions |
| **Maintenance** | ❌ Custom code to maintain | ⚠️ Less custom code | ✅ Library maintained |
| **Flexibility** | ❌ Can't add new rules | ⚠️ Can't add new rules | ✅ Can add new rules |
| **Learning Curve** | ✅ Familiar C# | ✅ Minimal | ❌ Significant |
| **Debugging** | ✅ Easy | ✅ Easy | ❌ Harder (runtime errors) |
| **Dependencies** | ✅ None | ✅ None | ⚠️ External library |
## Recommended Approach: Hybrid Improvement
### Strategy
**Phase 1: Immediate Improvements (No Breaking Changes)**
Improve current implementation to fix code smells and add runtime configurability WITHOUT adopting a rules engine library.
**Phase 2: JSON Configuration Storage**
Move `ValidationConfiguration` to JSON files (Data/validation-config.json) to enable per-chapter customization at runtime.
**Phase 3: Rule Registry Pattern (Optional)**
Add plugin architecture for future extensibility if needed.
### Why This Approach?
1. **Meets critical requirement:** Enables runtime editing of thresholds, severities, and requirements
2. **Maintains simplicity:** Keeps familiar C# rule classes with type safety
3. **Reduces complexity:** Avoids JSON lambda expressions and external dependencies
4. **Pragmatic:** Most chapter-to-chapter differences are in THRESHOLDS (min/max events, severity levels), not rule LOGIC
5. **Incremental:** Can adopt RulesEngine later if rule logic customization becomes critical
### What Can Be Customized at Runtime?
With JSON configuration approach, chapters can customize:
- **Event count thresholds** (MinRecommendedEvents, MaxRecommendedEvents, etc.)
- **Requirement toggles** (RequireRegionalEvent, RequireOnSiteActivity, etc.)
- **Severity levels** (Warning vs Error for each rule type)
This covers ~95% of expected chapter-to-chapter variation without rule engine complexity.
### When to Adopt RulesEngine?
Only adopt Microsoft RulesEngine if you need:
- Different chapters with **fundamentally different rules** (not just thresholds)
- Non-developers authoring new validation rules
- Complex rule dependencies/chaining
- Rule versioning and AB testing
## Implementation Plan
### Phase 1: Core Improvements (No External Dependencies)
#### 1.1 Fix Rule Registration Anti-Pattern
**File:** `Core/Validation/ValidationService.cs`
**Current Problem:**
```csharp
private void InitializeRules()
{
_studentRules.Add(new NoRegionalEventRule());
_studentRules.Add(new NoOnSiteActivityRule());
// ... hardcoded rule instantiation
}
```
**Solution:** Use reflection-based rule discovery or explicit registration API:
```csharp
// Option A: Reflection-based discovery (simple)
private void InitializeRules()
{
// Auto-discover all IValidationRule<Student> implementations
_studentRules = DiscoverRules<Student>();
_teamRules = DiscoverRules<Team>();
_statisticsRules = DiscoverRules<StudentEventStatistics>();
}
private List<IValidationRule<T>> DiscoverRules<T>()
{
var ruleType = typeof(IValidationRule<T>);
return Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract && ruleType.IsAssignableFrom(t))
.Select(t => (IValidationRule<T>)Activator.CreateInstance(t)!)
.ToList();
}
```
**Benefits:**
- Adding new rules just requires creating the class
- No need to modify ValidationService
- Rules automatically discovered
#### 1.2 Eliminate Rule Duplication
**Files:** Student ranking rules vs assignment rules
**Current Problem:** Near-duplicate code between:
- `NoRegionalEventRule` (Student) vs `NoRegionalEventAssignmentRule` (StudentEventStatistics)
- `NoOnSiteActivityRule` (Student) vs `NoOnSiteActivityAssignmentRule` (StudentEventStatistics)
**Solution:** Create abstract base classes:
```csharp
// Core/Validation/Rules/BaseRules/RequiredEventTypeRule.cs
public abstract class RequiredEventTypeRule<TEntity> : IValidationRule<TEntity>
{
protected abstract string EventTypeName { get; }
protected abstract string IconIdentifier { get; }
protected abstract Func<ValidationConfiguration, bool> IsRequired { get; }
protected abstract Func<ValidationConfiguration, ValidationSeverity> GetSeverity { get; }
protected abstract Func<TEntity, bool> HasEventType { get; }
protected abstract ValidationContext[] ApplicableContexts { get; }
public ValidationWarning? Validate(TEntity entity, ValidationConfiguration config)
{
if (!IsRequired(config)) return null;
if (HasEventType(entity)) return null;
return new ValidationWarning { /* ... */ };
}
public bool AppliesTo(ValidationContext context) =>
ApplicableContexts.Contains(context);
}
```
Then specific rules become:
```csharp
public class NoRegionalEventRule : RequiredEventTypeRule<Student>
{
protected override string EventTypeName => "Regional Event";
protected override Func<Student, bool> HasEventType =>
s => s.RankedEvents.Any(e => e.RegionalEvent);
// ... etc
}
```
**Benefits:**
- Eliminates duplication
- Easier to maintain
- Consistent behavior across similar rules
#### 1.3 Fix Context Handling Inconsistency
**File:** `Core/Validation/ValidationService.cs`
**Current Problem:** `ValidateTeam()` doesn't filter by context
**Solution:**
```csharp
public List<ValidationWarning> ValidateTeam(Team team, ValidationContext context = ValidationContext.Team)
{
return _teamRules
.Where(rule => rule.AppliesTo(context)) // ADD THIS
.Select(rule => rule.Validate(team, _config))
.Where(warning => warning != null)
.Cast<ValidationWarning>()
.ToList();
}
```
#### 1.4 Add Rule Singleton Pattern
**File:** `Core/Validation/ValidationService.cs`
**Current:** Rules instantiated per ValidationService instance
**Solution:** Use lazy static singletons:
```csharp
private static readonly Lazy<List<IValidationRule<Student>>> _studentRuleDefinitions =
new(() => DiscoverRules<Student>());
public ValidationService(ValidationConfiguration? config = null)
{
_config = config ?? ValidationConfiguration.Default;
_studentRules = _studentRuleDefinitions.Value;
_teamRules = _teamRuleDefinitions.Value;
_statisticsRules = _statisticsRuleDefinitions.Value;
}
```
**Benefits:**
- Rules instantiated once per application lifetime
- Reduced memory allocation
- Better performance
### Phase 2: JSON Configuration Storage
#### 2.1 Add Configuration Serialization
**File:** `Core/Validation/ValidationConfiguration.cs`
Add methods:
```csharp
public static ValidationConfiguration FromJson(string json)
{
return JsonSerializer.Deserialize<ValidationConfiguration>(json)
?? Default;
}
public static async Task<ValidationConfiguration> LoadFromFileAsync(string path)
{
if (!File.Exists(path)) return Default;
var json = await File.ReadAllTextAsync(path);
return FromJson(json);
}
public async Task SaveToFileAsync(string path)
{
var json = JsonSerializer.Serialize(this, new JsonSerializerOptions
{
WriteIndented = true
});
await File.WriteAllTextAsync(path, json);
}
```
#### 2.2 Create Default Configuration File
**File:** `Data/validation-config.json` (new)
```json
{
"MinRecommendedEvents": 2,
"MaxRecommendedEvents": 4,
"MinCriticalEvents": 1,
"MaxCriticalEvents": 6,
"RequireRegionalEvent": true,
"RequireOnSiteActivity": true,
"RequireIndividualEvent": false,
"RequireTeamCaptain": true,
"NoRegionalEventSeverity": "Warning",
"NoOnSiteActivitySeverity": "Warning",
"NoIndividualEventSeverity": "Warning",
"TeamSizeSeverity": "Warning",
"EventCountSeverity": "Warning",
"MissingCaptainSeverity": "Warning"
}
```
#### 2.3 Update Service Registration
**File:** `WebApp/Program.cs` (lines 176-177)
**Current:**
```csharp
builder.Services.AddScoped<Core.Validation.ValidationService>(sp =>
new Core.Validation.ValidationService(Core.Validation.ValidationConfiguration.Default));
```
**New:**
```csharp
// Load validation configuration from Data directory (or use default)
var validationConfigPath = Path.Combine(
builder.Environment.ContentRootPath,
"Data",
"validation-config.json");
var validationConfig = await Core.Validation.ValidationConfiguration
.LoadFromFileAsync(validationConfigPath);
builder.Services.AddScoped<Core.Validation.ValidationService>(sp =>
new Core.Validation.ValidationService(validationConfig));
```
#### 2.4 Add Configuration UI (Optional)
**File:** `WebApp/Components/Pages/Settings/ValidationSettings.razor` (new)
Create admin page to edit validation configuration with live preview.
### Phase 3: Optional Enhancements
#### 3.1 Add Short-Circuit Logic
```csharp
public List<ValidationWarning> ValidateStudentRankings(
Student student,
ValidationContext context,
bool stopOnFirstError = false)
{
var warnings = new List<ValidationWarning>();
foreach (var rule in _studentRules.Where(r => r.AppliesTo(context)))
{
var warning = rule.Validate(student, _config);
if (warning != null)
{
warnings.Add(warning);
if (stopOnFirstError && warning.Severity == ValidationSeverity.Error)
break;
}
}
return warnings;
}
```
#### 3.2 Add Rule Metadata Interface
```csharp
public interface IValidationRuleMetadata
{
string Name { get; }
string Description { get; }
string Category { get; }
int Priority { get; } // For ordering
}
public interface IValidationRule<TEntity> : IValidationRuleMetadata
{
ValidationWarning? Validate(TEntity entity, ValidationConfiguration config);
bool AppliesTo(ValidationContext context);
}
```
## Alternative: Full RulesEngine Migration Plan
If you decide runtime rule editing (not just configuration) is critical, here's the Microsoft RulesEngine adoption plan:
### Installation
```bash
dotnet add package RulesEngine
```
### Migration Strategy
#### 1. Create Rule JSON Files
**File:** `Data/validation-rules.json`
```json
{
"StudentRankingWorkflow": {
"WorkflowName": "StudentRankingValidation",
"Rules": [
{
"RuleName": "RequireRegionalEvent",
"Enabled": true,
"Expression": "RequireRegionalEvent AND input.RankedEvents.Any(e => e.RegionalEvent == false)",
"RuleExpressionType": "LambdaExpression",
"ErrorMessage": "No Regional Event",
"ErrorType": "Warning",
"SuccessEvent": "NoRegionalEventWarning"
},
{
"RuleName": "RequireOnSiteActivity",
"Enabled": true,
"Expression": "RequireOnSiteActivity AND input.RankedEvents.Any(e => e.OnSiteActivity == false)",
"RuleExpressionType": "LambdaExpression",
"ErrorMessage": "No On-Site Activity",
"ErrorType": "Warning"
}
]
}
}
```
#### 2. Create RulesEngine Wrapper
**File:** `Core/Validation/RulesEngineValidationService.cs` (new)
```csharp
using RulesEngine.Models;
public class RulesEngineValidationService
{
private readonly RulesEngine.RulesEngine _rulesEngine;
private readonly ValidationConfiguration _config;
public RulesEngineValidationService(string rulesJsonPath, ValidationConfiguration config)
{
var rulesJson = File.ReadAllText(rulesJsonPath);
var workflows = JsonSerializer.Deserialize<Workflow[]>(rulesJson);
_rulesEngine = new RulesEngine.RulesEngine(workflows);
_config = config;
}
public async Task<List<ValidationWarning>> ValidateStudentRankingsAsync(Student student)
{
var input = new RuleParameter("input", student);
var configParam = new RuleParameter("config", _config);
var results = await _rulesEngine.ExecuteAllRulesAsync(
"StudentRankingValidation",
input,
configParam);
return results
.Where(r => !r.IsSuccess)
.Select(r => new ValidationWarning
{
Code = r.Rule.RuleName,
Message = r.Rule.ErrorMessage,
Severity = r.Rule.ErrorType == "Error"
? ValidationSeverity.Error
: ValidationSeverity.Warning,
Context = ValidationContext.StudentRanking
})
.ToList();
}
}
```
#### 3. Gradual Migration
- Keep existing ValidationService
- Add RulesEngineValidationService alongside
- Use feature flag to toggle between implementations
- Migrate one context at a time (StudentRanking → StudentAssignment → Team)
## Critical Files
### Current Implementation
1. `Core/Validation/ValidationService.cs` - Main orchestration service
2. `Core/Validation/ValidationConfiguration.cs` - Configuration model
3. `Core/Validation/IValidationRule.cs` - Generic rule interface
4. `Core/Validation/Rules/StudentRankingRules/*.cs` - Student ranking rules (3 files)
5. `Core/Validation/Rules/StudentAssignmentRules/*.cs` - Assignment rules (4 files)
6. `Core/Validation/Rules/TeamRules/*.cs` - Team rules (3 files)
7. `WebApp/Program.cs` - Service registration (line 176)
### For Improvements
8. `Data/validation-config.json` - Runtime configuration file (new)
9. `Core/Validation/Rules/BaseRules/*.cs` - Abstract base rule classes (new)
### For RulesEngine Migration
10. `Data/validation-rules.json` - RulesEngine rule definitions (new)
11. `Core/Validation/RulesEngineValidationService.cs` - Wrapper service (new)
## Recommendations Summary
### Recommended: Hybrid Improvement Approach
**Do This:**
1. ✅ Fix hardcoded rule registration (reflection-based discovery)
2. ✅ Eliminate rule duplication (abstract base classes)
3. ✅ Add JSON configuration storage (`Data/validation-config.json`)
4. ✅ Enable per-chapter threshold/severity customization
5. ✅ Fix context handling inconsistency
6. ✅ Add rule singleton pattern for performance
**Benefits:**
- Enables runtime customization without complexity
- Maintains type safety and simplicity
- No external dependencies
- Easy to debug and maintain
- Covers 95% of expected customization needs
**Estimated Effort:** 4-6 hours
### Not Recommended (Yet): Full RulesEngine Adoption
**Don't Do This Unless:**
- Chapters need fundamentally different rule logic (not just thresholds)
- Non-developers need to author rules
- Complex rule dependencies are required
- A/B testing of rules is needed
**Complexity Cost:**
- All 10 rules rewritten in JSON with lambda strings
- Team must learn RulesEngine syntax and concepts
- Type safety lost (runtime errors vs compile-time)
- Harder debugging (no breakpoints in rule logic)
- External dependency to maintain
## Next Steps
1. **Decision Point:** Confirm hybrid approach or full RulesEngine migration
2. **If Hybrid:** Implement Phase 1 improvements (2-3 hours)
3. **Then:** Add JSON configuration storage (1-2 hours)
4. **Test:** Deploy to test chapter with custom config
5. **Iterate:** Add Phase 3 enhancements if needed
## Research Sources
- [Microsoft RulesEngine - GitHub](https://github.com/microsoft/RulesEngine)
- [RulesEngine Documentation](https://microsoft.github.io/RulesEngine/)
- [Understanding Microsoft RulesEngine (Oct 2025)](https://medium.com/@sunil-bisht/understanding-microsoft-rulesengine-a-gentle-introduction-part-1-ae1585e5e279)
- [NRules - Rules Engine for .NET](https://nrules.net/)
- [Rule Engines In .NET - Yunier's Wiki](https://yunier.dev/post/2023/rule-engines-in-dotnet/)
- [Top 10 Open Source Rules Engines (2025)](https://www.nected.ai/blog/open-source-rules-engine)