faturas_4docs/Compliance/Services/ValidationRules/ReactiveEnergyValidationRule.cs

113 lines
4.6 KiB
C#

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 ReactiveEnergyValidationRule(IDistributorRepository distributorRepository) : IValidationRule
{
private readonly IDistributorRepository _distributorRepository = distributorRepository;
private const string RULE_NAME = "Reactive Energy Validation";
private const string PEAK_ERROR = "Peak reactive energy charge doesn't match the expected value";
private const string OFF_PEAK_ERROR = "Off-peak reactive energy charge doesn't match the expected value";
private const string POWER_FACTOR_ERROR = "Power factor is below minimum allowed";
private const decimal TOLERANCE = 0.01m;
public int Priority => 3; // Run after demand validation
public async Task<ValidationResult> ValidateAsync(
BillComplianceRequest request,
DistributorInformation distributorInfo,
Client clientInfo)
{
if (!ShouldValidateReactiveEnergy(request.ConsumerGroup, request.Subgroup))
{
return new ValidationResult
{
IsValid = true,
RuleName = RULE_NAME,
Message = string.Empty
};
}
var reactiveInfo = await _distributorRepository.GetReactiveEnergyInformationAsync(
request.DistributorName,
request.ConsumerGroup,
request.Month) ?? throw new InvalidOperationException("Reactive energy information not found");
var isValid = true;
var message = new StringBuilder();
// Validate power factor
if (request.PowerFactor < reactiveInfo.MinimumPowerFactor)
{
isValid = false;
message.AppendLine($"{POWER_FACTOR_ERROR}. Minimum: {reactiveInfo.MinimumPowerFactor:F2}, Found: {request.PowerFactor:F2}");
}
// Calculate and validate peak period charges
var expectedPeakCharge = CalculateReactiveCharge(
request.PeakActiveEnergy,
request.PeakReactiveEnergy,
request.PeakDemandTariff,
reactiveInfo.ReferenceRate,
reactiveInfo.PeakAdjustmentFactor);
if (Math.Abs(request.PeakReactiveCharge - expectedPeakCharge) > TOLERANCE)
{
isValid = false;
message.AppendLine($"{PEAK_ERROR}. Expected: {expectedPeakCharge:F2}, Found: {request.PeakReactiveCharge:F2}");
}
// Calculate and validate off-peak period charges
var expectedOffPeakCharge = CalculateReactiveCharge(
request.OffPeakActiveEnergy,
request.OffPeakReactiveEnergy,
request.OffPeakDemandTariff,
reactiveInfo.ReferenceRate,
reactiveInfo.OffPeakAdjustmentFactor);
if (Math.Abs(request.OffPeakReactiveCharge - expectedOffPeakCharge) > TOLERANCE)
{
isValid = false;
message.AppendLine($"{OFF_PEAK_ERROR}. Expected: {expectedOffPeakCharge:F2}, Found: {request.OffPeakReactiveCharge:F2}");
}
return new ValidationResult
{
IsValid = isValid,
RuleName = RULE_NAME,
Message = message.ToString().TrimEnd()
};
}
private static bool ShouldValidateReactiveEnergy(string consumerGroup, string subgroup)
{
// According to ANEEL, reactive energy charges apply to these groups
return consumerGroup.ToUpper() switch
{
"A" => true, // All A group consumers
"B" when subgroup == "B3" => true, // Commercial/industrial B3 consumers
_ => false
};
}
private static decimal CalculateReactiveCharge(
decimal activeEnergy,
decimal reactiveEnergy,
decimal tariff,
decimal referenceRate,
decimal adjustmentFactor)
{
if (activeEnergy == 0) return 0;
var powerFactor = activeEnergy / (decimal)Math.Sqrt((double)(activeEnergy * activeEnergy + reactiveEnergy * reactiveEnergy));
if (powerFactor >= 0.92m) return 0;
var excessReactive = reactiveEnergy - (activeEnergy * 0.426m); // tan(arcos(0.92))
return excessReactive * tariff * referenceRate * adjustmentFactor;
}
}
}