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; } } } }