using System; using System.Threading.Tasks; using System.Text; using Compliance.Domain.Models; using Compliance.DTOs; using Compliance.Infrastructure.Repositories; namespace Compliance.Services.ValidationRules { public class MeasurementSystemValidationRule(IDistributorRepository distributorRepository) : IValidationRule { private readonly IDistributorRepository _distributorRepository = distributorRepository; private const string RULE_NAME = "Measurement System Validation"; // Art. 238 - Maximum calibration periods private const int MAX_CALIBRATION_YEARS_REGULAR = 10; private const int MAX_CALIBRATION_YEARS_ELECTRONIC = 5; private const int MAX_CALIBRATION_YEARS_SMART = 3; // Art. 239 - Accuracy requirements private const decimal MAX_ERROR_PERCENTAGE = 2.5m; private const decimal SMART_METER_ERROR_PERCENTAGE = 1.0m; public int Priority => 1; // High priority as measurements affect other validations public async Task ValidateAsync( BillComplianceRequest request, DistributorInformation distributorInfo, Client clientInfo) { var measurementInfo = await _distributorRepository.GetMeasurementSystemInfoAsync( request.MeterNumber) ?? throw new InvalidOperationException("Measurement system info not found"); var errors = new List(); ValidateCalibration(measurementInfo, errors); ValidateAccuracy(measurementInfo, errors); ValidateSmartMeterRequirements(measurementInfo, errors); ValidateSeals(measurementInfo, errors); ValidateLocation(measurementInfo, errors); return errors.Count == 0 ? ValidationResult.Success(RULE_NAME) : ValidationResult.Failure(RULE_NAME, errors); } private static void ValidateCalibration(MeasurementSystemInfo info, List errors) { var maxYears = info.IsSmartMeter ? MAX_CALIBRATION_YEARS_SMART : info.MeterType.Contains("Electronic", StringComparison.OrdinalIgnoreCase) ? MAX_CALIBRATION_YEARS_ELECTRONIC : MAX_CALIBRATION_YEARS_REGULAR; var calibrationAge = (DateTime.Now - info.LastCalibrationDate).TotalDays / 365.25; if (calibrationAge > maxYears) { errors.Add($"Meter calibration expired. Last calibration: {info.LastCalibrationDate:d}. " + $"Maximum period for {info.MeterType}: {maxYears} years"); } // Validate component calibrations foreach (var component in info.ComponentCalibrations) { var componentAge = (DateTime.Now - component.Value).TotalDays / 365.25; if (componentAge > maxYears) { errors.Add($"Component {component.Key} calibration expired. " + $"Last calibration: {component.Value:d}"); } } } private static void ValidateAccuracy(MeasurementSystemInfo info, List errors) { var maxError = info.IsSmartMeter ? SMART_METER_ERROR_PERCENTAGE : MAX_ERROR_PERCENTAGE; if (info.MaximumError > maxError) { errors.Add($"Meter accuracy ({info.MaximumError}%) exceeds maximum allowed error " + $"({maxError}%) for {(info.IsSmartMeter ? "smart" : "regular")} meters"); } } private static void ValidateSmartMeterRequirements(MeasurementSystemInfo info, List errors) { if (!info.IsSmartMeter) return; var requiredProtocols = new[] { "DLMS/COSEM", "ANSI C12.19" }; foreach (var protocol in requiredProtocols) { if (!info.CommunicationProtocols.Contains(protocol)) { errors.Add($"Smart meter missing required protocol: {protocol}"); } } } private static void ValidateSeals(MeasurementSystemInfo info, List errors) { if (!info.HasSeal) { errors.Add("Meter missing required security seal"); } else if (string.IsNullOrEmpty(info.SealNumber)) { errors.Add("Security seal number not documented"); } } private static void ValidateLocation(MeasurementSystemInfo info, List errors) { if (string.IsNullOrEmpty(info.Location)) { errors.Add("Meter location not documented"); } else if (!info.Location.Contains("accessible", StringComparison.OrdinalIgnoreCase)) { errors.Add("Meter must be installed in an accessible location"); } } } }