feat: complete legacy parity and modernize operator UI
This commit is contained in:
@@ -47,6 +47,7 @@ try
|
||||
|
||||
await VerifyOracleServerVersionAsync(runtime.Executor, cancellation.Token);
|
||||
await VerifyStockSearchAsync(runtime.Executor, cancellation.Token);
|
||||
await VerifyDynamicResultCompatibilityAsync(runtime.Executor, cancellation.Token);
|
||||
await VerifyThemeSelectorAsync(runtime.Executor, cancellation.Token);
|
||||
await VerifyOverseasSelectorsAsync(runtime.Executor, cancellation.Token);
|
||||
await VerifyComparisonWorldSelectorAsync(runtime.Executor, cancellation.Token);
|
||||
@@ -57,7 +58,13 @@ try
|
||||
runtime.ConnectionFactory,
|
||||
cancellation.Token);
|
||||
await VerifyManualFinancialSearchAsync(runtime.Executor, cancellation.Token);
|
||||
await ManualFinancialListCompatibilityAudit.RunAsync(
|
||||
runtime.Executor,
|
||||
cancellation.Token);
|
||||
await NamedPlaylistReadOnlyDbAudit.RunAsync(runtime.Executor, cancellation.Token);
|
||||
await NamedPlaylistOperatorCompatibilityAudit.RunAsync(
|
||||
runtime.Executor,
|
||||
cancellation.Token);
|
||||
await NamedManualRestoreReadOnlyAudit.RunAsync(
|
||||
runtime.Executor,
|
||||
NamedManualRestoreReadOnlyAudit.DefaultTrustedDirectory(),
|
||||
@@ -736,6 +743,9 @@ static async Task VerifyOperatorCatalogSchemaAsync(
|
||||
_ = await themePersistence.SuggestNextCodeAsync(
|
||||
ThemeMarket.Nxt,
|
||||
cancellationToken);
|
||||
await ThemeCatalogStoredEditorCompatibilityAudit.RunAsync(
|
||||
executor,
|
||||
cancellationToken);
|
||||
_ = await new LegacyExpertCatalogPersistenceService(executor)
|
||||
.SuggestNextCodeAsync(cancellationToken);
|
||||
var widthSummary = await ReadMariaDbThemeColumnTypesAsync(
|
||||
@@ -813,6 +823,96 @@ static async Task VerifyStockSearchAsync(
|
||||
$"({string.Join(", ", counts)})");
|
||||
}
|
||||
|
||||
static async Task VerifyDynamicResultCompatibilityAsync(
|
||||
IDataQueryExecutor executor,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (LegacyOperatorCatalogWorkflowController.MaximumCatalogResults !=
|
||||
LegacyThemeSelectionService.MaximumResults ||
|
||||
LegacyOperatorCatalogWorkflowController.MaximumStockResults !=
|
||||
LegacyStockSearchService.MaximumResults ||
|
||||
LegacyManualFinancialWorkflow.MaximumStockResults !=
|
||||
LegacyStockSearchService.MaximumResults ||
|
||||
LegacyManualListsWorkflow.MaximumSearchResults <
|
||||
LegacyStockSearchService.MaximumResults)
|
||||
{
|
||||
throw new DatabaseConfigurationException(
|
||||
"A dynamic operator list is still bounded below its Core compatibility ceiling.");
|
||||
}
|
||||
|
||||
var worldService = new LegacyWorldStockSearchService(executor);
|
||||
var comparison = new LegacyComparisonDataService(
|
||||
new LegacyParityStockSearchService(executor),
|
||||
worldService);
|
||||
Console.WriteLine("DynamicResultCompatibility domestic: BEGIN");
|
||||
var domestic = await comparison.SearchDomesticWithDiagnosticsAsync(
|
||||
string.Empty,
|
||||
LegacyStockSearchService.MaximumResults,
|
||||
cancellationToken);
|
||||
if (domestic.Items.Count != 4_599 ||
|
||||
domestic.IsolatedInvalidRowCount != 0 ||
|
||||
domestic.IsTruncated)
|
||||
{
|
||||
throw new DatabaseConfigurationException(
|
||||
"The UC3 domestic compatibility cardinality changed: expected 4,599 safe rows.");
|
||||
}
|
||||
|
||||
Console.WriteLine("DynamicResultCompatibility world: BEGIN");
|
||||
var coreWorld = await worldService.SearchAsync(
|
||||
string.Empty,
|
||||
LegacyWorldStockSearchService.MaximumResults,
|
||||
cancellationToken);
|
||||
var commaNameCount = coreWorld.Items.Count(item => item.InputName.Contains(','));
|
||||
if (coreWorld.Items.Count != 1_474 ||
|
||||
coreWorld.IsolatedInvalidRowCount != 16 ||
|
||||
commaNameCount != 1)
|
||||
{
|
||||
throw new DatabaseConfigurationException(
|
||||
"The raw UC3 world compatibility split changed before application mapping.");
|
||||
}
|
||||
var world = await comparison.SearchWorldWithDiagnosticsAsync(
|
||||
string.Empty,
|
||||
LegacyWorldStockSearchService.MaximumResults,
|
||||
cancellationToken);
|
||||
var worldSourceRows = checked(world.Items.Count + world.IsolatedInvalidRowCount);
|
||||
var worldActionableRows = world.Items.Count(item => item.CanSelect);
|
||||
Console.WriteLine(
|
||||
"DynamicResultCompatibility world diagnostics: " +
|
||||
$"source={worldSourceRows.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"visible={world.Items.Count.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"actionable={worldActionableRows.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"isolated={world.IsolatedInvalidRowCount.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"deferred={world.DeferredUnsafeRowCount.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"truncated={world.IsTruncated}");
|
||||
if (worldSourceRows != 1_490 ||
|
||||
world.Items.Count != 1_474 ||
|
||||
worldActionableRows != 1_473 ||
|
||||
world.IsolatedInvalidRowCount != 16 ||
|
||||
world.DeferredUnsafeRowCount != 1 ||
|
||||
!world.IsTruncated)
|
||||
{
|
||||
throw new DatabaseConfigurationException(
|
||||
"The UC3 world compatibility cardinality changed: expected 1,490 source rows, " +
|
||||
"1,474 visible rows, 1,473 selectable rows, 16 isolated blank-display rows, " +
|
||||
"and one deferred comma-subject row.");
|
||||
}
|
||||
|
||||
Console.WriteLine(
|
||||
"DynamicResultCompatibility: " +
|
||||
$"domesticSource={domestic.Items.Count.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"domesticVisible={domestic.Items.Count.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"domesticIsolated={domestic.IsolatedInvalidRowCount.ToString(CultureInfo.InvariantCulture)}; " +
|
||||
$"worldSource={worldSourceRows.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"worldVisible={world.Items.Count.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"worldActionable={worldActionableRows.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"worldIsolated={world.IsolatedInvalidRowCount.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"worldDeferred={world.DeferredUnsafeRowCount.ToString(CultureInfo.InvariantCulture)}; " +
|
||||
$"stockCeiling={LegacyStockSearchService.MaximumResults.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"worldCeiling={LegacyWorldStockSearchService.MaximumResults.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"themeCeiling={LegacyThemeSelectionService.MaximumResults.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"viCeiling={LegacyManualListsWorkflow.MaximumSearchResults.ToString(CultureInfo.InvariantCulture)}");
|
||||
}
|
||||
|
||||
static async Task VerifyThemeSelectorAsync(
|
||||
IDataQueryExecutor executor,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -822,22 +922,81 @@ static async Task VerifyThemeSelectorAsync(
|
||||
var themes = await service.SearchAsync(
|
||||
string.Empty,
|
||||
ThemeSession.PreMarket,
|
||||
maximumResults: 100,
|
||||
maximumResults: LegacyThemeSelectionService.MaximumResults,
|
||||
cancellationToken);
|
||||
if (themes.Items.Count == 0)
|
||||
{
|
||||
throw new DatabaseConfigurationException(
|
||||
"The production theme selector returned no unambiguous canonical rows.");
|
||||
}
|
||||
if (themes.IsTruncated)
|
||||
{
|
||||
throw new DatabaseConfigurationException(
|
||||
"The production theme selector exceeded its finite " +
|
||||
$"{LegacyThemeSelectionService.MaximumResults.ToString(CultureInfo.InvariantCulture)}-row safety ceiling.");
|
||||
}
|
||||
var storedNxtSuffixCount = themes.Items.Count(item =>
|
||||
item.Identity.ThemeTitle.Contains("(NXT)", StringComparison.Ordinal));
|
||||
var storedNxtSuffixKrxCount = themes.Items.Count(item =>
|
||||
item.Identity.Market == ThemeMarket.Krx &&
|
||||
item.Identity.ThemeTitle.Contains("(NXT)", StringComparison.Ordinal));
|
||||
var storedNxtSuffixNxtCount = themes.Items.Count(item =>
|
||||
item.Identity.Market == ThemeMarket.Nxt &&
|
||||
item.Identity.ThemeTitle.Contains("(NXT)", StringComparison.Ordinal));
|
||||
Console.WriteLine(
|
||||
"ThemeSelector identity diagnostics: " +
|
||||
$"storedNxtSuffix={storedNxtSuffixCount.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"krx={storedNxtSuffixKrxCount.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"nxt={storedNxtSuffixNxtCount.ToString(CultureInfo.InvariantCulture)}");
|
||||
|
||||
var workflow = new LegacyThemeWorkflow(service);
|
||||
LegacyThemeWorkflowSnapshot workflowSnapshot;
|
||||
try
|
||||
{
|
||||
workflowSnapshot = await workflow.SearchAsync(
|
||||
workflow.CreateInitial(ThemeSession.PreMarket),
|
||||
string.Empty,
|
||||
ThemeSession.PreMarket,
|
||||
cancellationToken);
|
||||
}
|
||||
catch (LegacyThemeWorkflowDataException exception)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"THEME_WORKFLOW_BOUNDARY: " + exception.Message);
|
||||
throw new DatabaseConfigurationException(
|
||||
"The production theme rows passed the data service but failed the operator workflow: " +
|
||||
exception.Message);
|
||||
}
|
||||
if (workflowSnapshot.SearchIsTruncated ||
|
||||
workflowSnapshot.SearchResults.Count != themes.Items.Count)
|
||||
{
|
||||
throw new DatabaseConfigurationException(
|
||||
"The production theme operator workflow did not preserve the complete selector result.");
|
||||
}
|
||||
|
||||
await ThemeSelectorCompatibilityAudit.RunAsync(
|
||||
executor,
|
||||
service,
|
||||
themes,
|
||||
workflow,
|
||||
workflowSnapshot,
|
||||
cancellationToken);
|
||||
|
||||
var selected = themes.Items[0];
|
||||
var preview = await service.PreviewAsync(
|
||||
selected.Identity,
|
||||
maximumItems: 100,
|
||||
cancellationToken);
|
||||
var marketCounts = themes.Items
|
||||
.GroupBy(item => item.Identity.Market)
|
||||
.OrderBy(group => group.Key)
|
||||
.Select(group =>
|
||||
$"{group.Key}={group.Count().ToString(CultureInfo.InvariantCulture)}");
|
||||
Console.WriteLine(
|
||||
$"ThemeSelector: themes={themes.Items.Count.ToString(CultureInfo.InvariantCulture)}, " +
|
||||
$"preview={preview.Items.Count.ToString(CultureInfo.InvariantCulture)}");
|
||||
$"ThemeSelector: themes={themes.Items.Count.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"({string.Join(", ", marketCounts)}), " +
|
||||
$"preview={preview.Items.Count.ToString(CultureInfo.InvariantCulture)}, " +
|
||||
$"truncated={themes.IsTruncated}");
|
||||
}
|
||||
|
||||
static async Task VerifyOverseasSelectorsAsync(
|
||||
@@ -873,7 +1032,9 @@ static async Task VerifyOverseasSelectorsAsync(
|
||||
LegacyOverseasStockSearchService.DefaultMaximumResults,
|
||||
cancellationToken);
|
||||
if (uc5Snapshot.Stock.Status != LegacyOverseasSearchStatus.Ready ||
|
||||
uc5Snapshot.Stock.ResultCount != overseasStocks.Items.Count)
|
||||
uc5Snapshot.Stock.ResultCount != overseasStocks.Items.Count ||
|
||||
uc5Snapshot.Stock.IsolatedInvalidRowCount !=
|
||||
overseasStocks.IsolatedInvalidRowCount)
|
||||
{
|
||||
throw new DatabaseConfigurationException(
|
||||
"The full UC5 overseas-stock workflow rejected current master rows.");
|
||||
@@ -884,11 +1045,21 @@ static async Task VerifyOverseasSelectorsAsync(
|
||||
throw new DatabaseConfigurationException(
|
||||
"The production overseas selectors returned no rows.");
|
||||
}
|
||||
if (industries.Items.Count != 53 ||
|
||||
overseasStocks.Items.Count != 1_474 ||
|
||||
overseasStocks.IsolatedInvalidRowCount != 16 ||
|
||||
!overseasStocks.IsTruncated)
|
||||
{
|
||||
throw new DatabaseConfigurationException(
|
||||
"The UC5 compatibility cardinality changed: expected 53 industries, " +
|
||||
"1,474 selectable US/TW stocks, and 16 isolated blank-name rows.");
|
||||
}
|
||||
|
||||
Console.WriteLine(
|
||||
$"OverseasSelectors: industries={industries.Items.Count.ToString(CultureInfo.InvariantCulture)}, " +
|
||||
$"comparisonStocks={stocks.Items.Count.ToString(CultureInfo.InvariantCulture)}, " +
|
||||
$"uc5Stocks={overseasStocks.Items.Count.ToString(CultureInfo.InvariantCulture)}");
|
||||
$"uc5Stocks={overseasStocks.Items.Count.ToString(CultureInfo.InvariantCulture)}, " +
|
||||
$"isolatedBlankNames={overseasStocks.IsolatedInvalidRowCount.ToString(CultureInfo.InvariantCulture)}");
|
||||
}
|
||||
|
||||
static async Task PrintUc5OverseasStockDiagnosticsAsync(
|
||||
@@ -908,7 +1079,11 @@ static async Task PrintUc5OverseasStockDiagnosticsAsync(
|
||||
""";
|
||||
var spec = new DataQuerySpec(
|
||||
sql,
|
||||
[new DataQueryParameter("row_limit", 501, DbType.Int32)]);
|
||||
[new DataQueryParameter(
|
||||
"row_limit",
|
||||
LegacyOverseasStockSearchService.MaximumResults +
|
||||
LegacyOverseasStockSearchService.MaximumIsolatedRows + 1,
|
||||
DbType.Int32)]);
|
||||
spec.ValidateFor(DataSourceKind.Oracle);
|
||||
var table = await executor.ExecuteAsync(
|
||||
DataSourceKind.Oracle,
|
||||
@@ -1009,13 +1184,40 @@ static async Task VerifyExpertSelectorAsync(
|
||||
"The production expert selector returned no rows.");
|
||||
}
|
||||
|
||||
var preview = await service.PreviewAsync(
|
||||
experts.Items[0].Identity,
|
||||
maximumItems: 100,
|
||||
cancellationToken);
|
||||
if (experts.Items.Count != 6 || experts.IsTruncated)
|
||||
{
|
||||
throw new DatabaseConfigurationException(
|
||||
"The expert compatibility cardinality changed: expected all 6 experts.");
|
||||
}
|
||||
|
||||
var recommendationCount = 0;
|
||||
var missingBuyAmountCount = 0;
|
||||
foreach (var expert in experts.Items)
|
||||
{
|
||||
var preview = await service.PreviewAsync(
|
||||
expert.Identity,
|
||||
maximumItems: 100,
|
||||
cancellationToken);
|
||||
if (preview.IsTruncated)
|
||||
{
|
||||
throw new DatabaseConfigurationException(
|
||||
"An expert preview exceeded the complete compatibility boundary.");
|
||||
}
|
||||
|
||||
recommendationCount += preview.Items.Count;
|
||||
missingBuyAmountCount += preview.Items.Count(item => item.BuyAmount is null);
|
||||
}
|
||||
if (recommendationCount != 32 || missingBuyAmountCount != 5)
|
||||
{
|
||||
throw new DatabaseConfigurationException(
|
||||
"The expert recommendation compatibility cardinality changed: " +
|
||||
"expected 32 rows including 5 blank buy amounts.");
|
||||
}
|
||||
|
||||
Console.WriteLine(
|
||||
$"ExpertSelector: experts={experts.Items.Count.ToString(CultureInfo.InvariantCulture)}, " +
|
||||
$"preview={preview.Items.Count.ToString(CultureInfo.InvariantCulture)}");
|
||||
$"recommendations={recommendationCount.ToString(CultureInfo.InvariantCulture)}, " +
|
||||
$"blankBuyAmounts={missingBuyAmountCount.ToString(CultureInfo.InvariantCulture)}");
|
||||
}
|
||||
|
||||
static async Task VerifyTradingHaltSelectorAsync(
|
||||
|
||||
Reference in New Issue
Block a user