using Dapper;
using GB5Shared.DTO.ReportOrchestration;
using GB5Shared.Enums.ReportOrchestration;
using GB5Shared.QueryExecutor;
using Microsoft.Extensions.Logging;
namespace FrameworkBLL.ReportOrchestration
{
///
/// Evaluates DataSourceType.Auto by applying MREPORTDATASOURCERULE rows
/// in RULEPRIORITY order. Returns the first matching TARGETDATASOURCE,
/// or the configured default if no rule matches.
///
public sealed class DataSourceRuleEvaluator : IDataSourceRuleEvaluator
{
private readonly IQueryExecutor _queryExecutor;
private readonly ILogger _logger;
public DataSourceRuleEvaluator(
IQueryExecutor queryExecutor,
ILogger logger)
{
_queryExecutor = queryExecutor;
_logger = logger;
}
public async Task EvaluateAsync(
ReportExecutionContext context,
CancellationToken cancellationToken = default)
{
if (context.ReportConfig is null)
return DataSourceType.Oltp;
if (context.ReportConfig.DataSourceType != DataSourceType.Auto)
return context.ReportConfig.DataSourceType;
var rules = await _queryExecutor
.QueryAsync(
context.LoginDTO,
ReportOrchestrationQB.GET_DATASOURCE_RULES,
new { ReportId = context.ReportId },
cancellationToken: cancellationToken)
.ConfigureAwait(false);
int utcHour = DateTime.UtcNow.Hour;
foreach (var rule in rules.OrderBy(r => r.RulePriority))
{
bool matched;
switch (rule.RuleType)
{
// DateRangeDays: route when range exceeds threshold
case 0:
matched = context.DateRangeDays > rule.ThresholdValue;
break;
// EstimatedRows: run the stored estimation query and compare row count
case 1 when !string.IsNullOrWhiteSpace(context.ReportConfig.EstimationQuery):
var dp = new DynamicParameters();
foreach (var kv in context.Parameters)
dp.Add(kv.Key, kv.Value);
var estimatedRows = await _queryExecutor
.ExecuteScalarAsync(
context.LoginDTO,
context.ReportConfig.EstimationQuery,
dp,
cancellationToken: cancellationToken)
.ConfigureAwait(false);
matched = estimatedRows > rule.ThresholdValue;
break;
// TimeOfDay: route when current UTC hour >= threshold
case 2:
matched = utcHour >= rule.ThresholdValue;
break;
// Manual (3) and EstimatedRows without a query — not auto-matched
default:
matched = false;
break;
}
if (matched)
{
var target = (DataSourceType)rule.TargetDataSource;
_logger.LogDebug(
"DataSourceRule matched for ReportId={ReportId}: RuleType={Type} → {Target}",
context.ReportId, rule.RuleType, target);
return target;
}
}
// No rule matched — use configured default (which is Auto at this point,
// so return OLTP as the safe fallback)
_logger.LogDebug(
"No DataSourceRule matched for ReportId={ReportId}; defaulting to OLTP.",
context.ReportId);
return DataSourceType.Oltp;
}
private sealed class DataSourceRule
{
public byte RuleType { get; init; }
public int ThresholdValue { get; init; }
public byte TargetDataSource { get; init; }
public short RulePriority { get; init; }
}
}
}