using System.Data; using System.Globalization; using MMoneyCoderSharp.Data; internal static class ManualFinancialListCompatibilityAudit { internal static async Task RunAsync( IDataQueryExecutor executor, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(executor); var service = new LegacyManualFinancialScreenService(executor); foreach (var contract in ManualFinancialCutContracts.All.OrderBy( contract => contract.Screen)) { cancellationToken.ThrowIfCancellationRequested(); var table = await executor.ExecuteAsync( DataSourceKind.Oracle, "GRAPH_E_LIST_COUNT_" + contract.Screen, $"SELECT COUNT(*) AS ROW_COUNT FROM {contract.TableName}", cancellationToken).ConfigureAwait(false); if (table.Columns.Count != 1 || table.Rows.Count != 1 || !string.Equals( table.Columns[0].ColumnName, "ROW_COUNT", StringComparison.OrdinalIgnoreCase)) { throw new InvalidDataException( "The GraphE list count query returned an invalid schema."); } var count = Convert.ToInt32( table.Rows[0][0], CultureInfo.InvariantCulture); if (count < 0) { throw new InvalidDataException( "The GraphE list count query returned an invalid count."); } var result = await service.SearchAsync( contract.Screen, query: string.Empty, maximumResults: LegacyManualFinancialScreenService.MaximumResults, cancellationToken: cancellationToken) .ConfigureAwait(false); var accountedRows = checked(result.Items.Count + result.RejectedItemCount); var strictReadyRawRows = result.RawItems.Count(item => item.IsPlayoutReady); var rawDeferredRows = result.RawItems.Count(item => !item.IsPlayoutReady); if (result.Screen != contract.Screen || result.Query.Length != 0 || result.IsTruncated || !result.RawItemsComplete || accountedRows != count || result.RawItems.Count != count || strictReadyRawRows != result.Items.Count || rawDeferredRows != result.RejectedItemCount || result.UnreadableStockNames.Count != result.RejectedItemCount || result.Items.Any(item => item.Record.Identity.Screen != contract.Screen) || result.RawItems.Any(item => item.Record.Identity.Screen != contract.Screen)) { throw new InvalidDataException( "The GraphE full-list compatibility read did not account for every DB row."); } var rawProbe = result.RawItems.FirstOrDefault(item => !item.IsPlayoutReady) ?? result.RawItems.FirstOrDefault(); if (rawProbe is not null) { var fresh = await service.GetRawAsync(rawProbe, cancellationToken) .ConfigureAwait(false); if (fresh.Record.Identity != rawProbe.Record.Identity || fresh.Record.StorageValues.Count != rawProbe.Record.StorageValues.Count) { throw new InvalidDataException( "The GraphE exact raw-row read was not correlated to its list handle."); } } var legacyShapeCompatibleDeferredRows = result.RawItems.Count(item => !item.IsPlayoutReady && IsLegacySceneShapeCompatible(item.Record)); Console.WriteLine( $"GRAPH_E_LIST_COMPAT_AUDIT: screen={contract.Screen} " + $"dbRows={count} rawEditable={result.RawItems.Count} " + $"strictReady={result.Items.Count} " + $"strictDeferred={result.RejectedItemCount} " + $"legacyShapeCompatibleDeferred={legacyShapeCompatibleDeferredRows} " + $"exactRawReads={(rawProbe is null ? 0 : 1)} truncated=false"); } } private static bool IsLegacySceneShapeCompatible(ManualFinancialRawRecord record) => record.Identity.Screen switch { ManualFinancialScreenKind.RevenueComposition => record.StorageValues.Skip(1).All(IsLegacyRevenueSlice), ManualFinancialScreenKind.GrowthMetrics => record.StorageValues.Take(4).All(value => IsLegacyNumberSeries(value, 4)) && HasAtLeastTokens(record.StorageValues[4], 4), ManualFinancialScreenKind.Sales or ManualFinancialScreenKind.OperatingProfit => record.StorageValues.All(IsLegacyQuarter), _ => false }; private static bool IsLegacyRevenueSlice(string value) { var tokens = value.Split('_', StringSplitOptions.None); if (tokens.Length < 2) { return value.Length == 0; } if (tokens[0].Length == 0 || tokens[1] is "" or "-") { return true; } return double.TryParse( tokens[1], NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.CurrentCulture, out var parsed) && double.IsFinite(parsed); } private static bool IsLegacyNumberSeries(string value, int minimumTokens) { var tokens = value.Split('_', StringSplitOptions.None); return tokens.Length >= minimumTokens && tokens.All(token => token.Length == 0 || double.TryParse( token, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.CurrentCulture, out var parsed) && double.IsFinite(parsed)); } private static bool HasAtLeastTokens(string value, int minimumTokens) => value.Split('_', StringSplitOptions.None).Length >= minimumTokens; private static bool IsLegacyQuarter(string value) { var tokens = value.Split('_', StringSplitOptions.None); return tokens.Length >= 2 && (tokens[1].Length == 0 || int.TryParse( tokens[1], NumberStyles.Integer, CultureInfo.CurrentCulture, out _)); } }