feat: complete legacy parity and modernize operator UI
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
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 _));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user