From f488d2571f85eae544f17528880bf873c38a2d52 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Mon, 25 May 2026 20:02:43 +0200 Subject: [PATCH] Implement Phase 6R-AD provider billing settlement shell --- .../HyperTwistContractLibrary.cpp | 78 ++- .../HyperTwistHttpSpeechClient.cpp | 13 +- .../HyperTwistMockSpeechClient.cpp | 3 + .../HyperTwistTrainingSubsystem.cpp | 518 +++++++++++++++++- .../HyperTwistRecognitionTypes.h | 409 ++++++++++++++ .../HyperTwistTrainingTypes.h | 3 + ...iderBillingSettlementShellContractTest.cpp | 294 ++++++++++ docs/REPO_LICENSE_TRACKING.md | 22 +- ...IATION_IMPLEMENTATION_PACKET_2026-05-25.md | 177 ++++++ ...CILIATION_PREPARATION_PACKET_2026-05-25.md | 137 +++++ ...PERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md | 14 +- ...NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md | 6 +- .../HyperTwist/FEATURE_REGISTRY.md | 2 + .../HyperTwist/ROADMAP.md | 35 +- 14 files changed, 1679 insertions(+), 32 deletions(-) create mode 100644 UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RADProviderBillingSettlementShellContractTest.cpp create mode 100644 docs/arch/HYPERTWIST_PHASE6R_AD_FIRST_PARTY_PROVIDER_BILLING_SETTLEMENT_AND_INVOICE_RECONCILIATION_IMPLEMENTATION_PACKET_2026-05-25.md create mode 100644 docs/arch/HYPERTWIST_PHASE6R_AD_FIRST_PARTY_PROVIDER_BILLING_SETTLEMENT_AND_INVOICE_RECONCILIATION_PREPARATION_PACKET_2026-05-25.md diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp index 2dcff02..6b06cca 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp @@ -1086,6 +1086,68 @@ namespace HyperTwistContractLibraryInternal return Profile; } + FHyperTwistSpeechProviderBillingSettlementShellProfile + MakeDefaultSpeechProviderBillingSettlementShellProfile(const FString& ProfileId) + { + FHyperTwistSpeechProviderBillingSettlementShellProfile Profile; + Profile.BillingSettlementShellProfileId = ProfileId; + Profile.ShellKind = TEXT("operator-provider-billing-settlement"); + Profile.SettlementSurfaceMode = TEXT("posted-charge-settlement-assessment"); + Profile.InvoiceReconciliationMode = TEXT("estimate-versus-provider-invoice"); + Profile.RefreshActionId = TEXT("refresh-provider-billing-settlement"); + Profile.InvoiceReconciliationActionId = TEXT("inspect-provider-invoice-reconciliation"); + Profile.SettlementReadinessActionId = TEXT("review-provider-settlement-readiness"); + Profile.RouteInspectActionId = TEXT("inspect-provider-route"); + Profile.BudgetInspectActionId = TEXT("review-provider-budget"); + Profile.RetainedSettlementEntryLimit = 12; + Profile.bSupportsSettlementTimeline = true; + Profile.bSupportsInvoiceReconciliation = true; + Profile.bSupportsSettlementReadinessReview = true; + Profile.bSupportsEstimateOnlyFallback = true; + Profile.bPreservesFirstPartySettlementOwnership = true; + + auto AddPanel = [&Profile]( + const TCHAR* PanelId, + const TCHAR* PanelKind, + const TCHAR* AnchorId, + const bool bVisibleByDefault + ) + { + FHyperTwistSpeechShellPanelLayout Panel; + Panel.PanelId = PanelId; + Panel.PanelKind = PanelKind; + Panel.AnchorId = AnchorId; + Panel.bVisibleByDefault = bVisibleByDefault; + Profile.Panels.Add(Panel); + }; + + auto AddAction = [&Profile]( + const TCHAR* ActionId, + const TCHAR* InputBinding, + const TCHAR* SurfaceId + ) + { + FHyperTwistSpeechShellActionBinding ActionBinding; + ActionBinding.ActionId = ActionId; + ActionBinding.InputBinding = InputBinding; + ActionBinding.SurfaceId = SurfaceId; + Profile.ActionBindings.Add(ActionBinding); + }; + + AddPanel(TEXT("settlement-timeline-card"), TEXT("settlement-timeline-card"), TEXT("top-left"), true); + AddPanel(TEXT("invoice-reconciliation-card"), TEXT("invoice-reconciliation-card"), TEXT("top-right"), true); + AddPanel(TEXT("settlement-summary-card"), TEXT("settlement-summary-card"), TEXT("left-stack-below-settlement"), true); + AddPanel(TEXT("settlement-readiness-card"), TEXT("settlement-readiness-card"), TEXT("right-stack-below-reconciliation"), true); + + AddAction(TEXT("refresh-provider-billing-settlement"), TEXT("F8"), TEXT("settlement-timeline-card")); + AddAction(TEXT("inspect-provider-invoice-reconciliation"), TEXT("R"), TEXT("invoice-reconciliation-card")); + AddAction(TEXT("review-provider-settlement-readiness"), TEXT("S"), TEXT("settlement-readiness-card")); + AddAction(TEXT("inspect-provider-route"), TEXT("P"), TEXT("settlement-summary-card")); + AddAction(TEXT("review-provider-budget"), TEXT("B"), TEXT("settlement-readiness-card")); + AddAction(TEXT("close-speech-session"), TEXT("Escape"), TEXT("session-root")); + return Profile; + } + FHyperTwistSpeechModelPayloadDescriptor MakeSpeechModelPayloadDescriptor( const TCHAR* PayloadId, const TCHAR* PayloadKind, @@ -1173,7 +1235,10 @@ namespace HyperTwistContractLibraryInternal TEXT("providerUsageCostSummaryCopy"), TEXT("providerReceiptReviewShell"), TEXT("providerPostedChargeInspection"), - TEXT("providerChargeVarianceReview") + TEXT("providerChargeVarianceReview"), + TEXT("providerBillingSettlementShell"), + TEXT("providerInvoiceReconciliation"), + TEXT("providerSettlementReadinessReview") }; } @@ -2411,6 +2476,12 @@ FHyperTwistSpeechSessionConfig UHyperTwistContractLibrary::MakeSampleSpeechSessi HyperTwistContractLibraryInternal::MakeDefaultSpeechProviderReceiptReviewShellProfile( Config.ProviderReceiptReviewShellProfileId ); + Config.ProviderBillingSettlementShellProfileId = + TEXT("speech-provider-billing-settlement-shell-v1"); + Config.ProviderBillingSettlementShellProfileDefinition = + HyperTwistContractLibraryInternal::MakeDefaultSpeechProviderBillingSettlementShellProfile( + Config.ProviderBillingSettlementShellProfileId + ); Config.PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1"); Config.PayloadCustodyProfileDefinition = HyperTwistContractLibraryInternal::MakeDefaultSpeechPayloadCustodyProfile( @@ -2527,7 +2598,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistContractLibrary::MakeMockSpeechService TEXT("providerUsageCostSummaryCopy"), TEXT("providerReceiptReviewShell"), TEXT("providerPostedChargeInspection"), - TEXT("providerChargeVarianceReview") + TEXT("providerChargeVarianceReview"), + TEXT("providerBillingSettlementShell"), + TEXT("providerInvoiceReconciliation"), + TEXT("providerSettlementReadinessReview") }; Health.SupportedOrchestrationProfiles = { TEXT("python-batch-transcribe-v1") diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp index 4358df5..942b9a0 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp @@ -350,7 +350,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal TEXT("providerUsageCostSummaryCopy"), TEXT("providerReceiptReviewShell"), TEXT("providerPostedChargeInspection"), - TEXT("providerChargeVarianceReview") + TEXT("providerChargeVarianceReview"), + TEXT("providerBillingSettlementShell"), + TEXT("providerInvoiceReconciliation"), + TEXT("providerSettlementReadinessReview") }; Health.SupportedOrchestrationProfiles = { TEXT("python-batch-transcribe-v1") @@ -433,7 +436,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal TEXT("providerUsageCostSummaryCopy"), TEXT("providerReceiptReviewShell"), TEXT("providerPostedChargeInspection"), - TEXT("providerChargeVarianceReview") + TEXT("providerChargeVarianceReview"), + TEXT("providerBillingSettlementShell"), + TEXT("providerInvoiceReconciliation"), + TEXT("providerSettlementReadinessReview") }; Health.SupportedOrchestrationProfiles = { TEXT("python-batch-transcribe-v1") @@ -501,6 +507,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal Health.Capabilities.AddUnique(TEXT("providerReceiptReviewShell")); Health.Capabilities.AddUnique(TEXT("providerPostedChargeInspection")); Health.Capabilities.AddUnique(TEXT("providerChargeVarianceReview")); + Health.Capabilities.AddUnique(TEXT("providerBillingSettlementShell")); + Health.Capabilities.AddUnique(TEXT("providerInvoiceReconciliation")); + Health.Capabilities.AddUnique(TEXT("providerSettlementReadinessReview")); Health.SupportedOrchestrationProfiles.AddUnique(TEXT("python-batch-transcribe-v1")); if (Health.ProviderProfileId.IsEmpty()) { diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp index f0c265b..2ba46a9 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp @@ -155,6 +155,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistMockSpeechClient::GetSpeechServiceHeal Health.Capabilities.AddUnique(TEXT("providerReceiptReviewShell")); Health.Capabilities.AddUnique(TEXT("providerPostedChargeInspection")); Health.Capabilities.AddUnique(TEXT("providerChargeVarianceReview")); + Health.Capabilities.AddUnique(TEXT("providerBillingSettlementShell")); + Health.Capabilities.AddUnique(TEXT("providerInvoiceReconciliation")); + Health.Capabilities.AddUnique(TEXT("providerSettlementReadinessReview")); Health.SupportedOrchestrationProfiles.AddUnique(TEXT("python-batch-transcribe-v1")); return Health; } diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index e74629f..f0ad989 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -2931,6 +2931,11 @@ namespace HyperTwistTrainingSubsystemInternal const FHyperTwistTrainingCompanionSpeechSessionState* PriorState ); + FHyperTwistSpeechProviderBillingSettlementShellState BuildSpeechProviderBillingSettlementShellState( + const FHyperTwistSpeechSessionConfig& SessionConfig, + const FHyperTwistTrainingCompanionSpeechSessionState* PriorState + ); + FHyperTwistSpeechMicrophoneShellProfile BuildDefaultCoachCommandMicrophoneShellProfile(const FString& ProfileId) { FHyperTwistSpeechMicrophoneShellProfile Profile; @@ -3176,6 +3181,68 @@ namespace HyperTwistTrainingSubsystemInternal return Profile; } + FHyperTwistSpeechProviderBillingSettlementShellProfile + BuildDefaultSpeechProviderBillingSettlementShellProfile(const FString& ProfileId) + { + FHyperTwistSpeechProviderBillingSettlementShellProfile Profile; + Profile.BillingSettlementShellProfileId = ProfileId; + Profile.ShellKind = TEXT("operator-provider-billing-settlement"); + Profile.SettlementSurfaceMode = TEXT("posted-charge-settlement-assessment"); + Profile.InvoiceReconciliationMode = TEXT("estimate-versus-provider-invoice"); + Profile.RefreshActionId = TEXT("refresh-provider-billing-settlement"); + Profile.InvoiceReconciliationActionId = TEXT("inspect-provider-invoice-reconciliation"); + Profile.SettlementReadinessActionId = TEXT("review-provider-settlement-readiness"); + Profile.RouteInspectActionId = TEXT("inspect-provider-route"); + Profile.BudgetInspectActionId = TEXT("review-provider-budget"); + Profile.RetainedSettlementEntryLimit = 12; + Profile.bSupportsSettlementTimeline = true; + Profile.bSupportsInvoiceReconciliation = true; + Profile.bSupportsSettlementReadinessReview = true; + Profile.bSupportsEstimateOnlyFallback = true; + Profile.bPreservesFirstPartySettlementOwnership = true; + + auto AddPanel = [&Profile]( + const TCHAR* PanelId, + const TCHAR* PanelKind, + const TCHAR* AnchorId, + const bool bVisibleByDefault + ) + { + FHyperTwistSpeechShellPanelLayout Panel; + Panel.PanelId = PanelId; + Panel.PanelKind = PanelKind; + Panel.AnchorId = AnchorId; + Panel.bVisibleByDefault = bVisibleByDefault; + Profile.Panels.Add(Panel); + }; + + auto AddAction = [&Profile]( + const TCHAR* ActionId, + const TCHAR* InputBinding, + const TCHAR* SurfaceId + ) + { + FHyperTwistSpeechShellActionBinding ActionBinding; + ActionBinding.ActionId = ActionId; + ActionBinding.InputBinding = InputBinding; + ActionBinding.SurfaceId = SurfaceId; + Profile.ActionBindings.Add(ActionBinding); + }; + + AddPanel(TEXT("settlement-timeline-card"), TEXT("settlement-timeline-card"), TEXT("top-left"), true); + AddPanel(TEXT("invoice-reconciliation-card"), TEXT("invoice-reconciliation-card"), TEXT("top-right"), true); + AddPanel(TEXT("settlement-summary-card"), TEXT("settlement-summary-card"), TEXT("left-stack-below-settlement"), true); + AddPanel(TEXT("settlement-readiness-card"), TEXT("settlement-readiness-card"), TEXT("right-stack-below-reconciliation"), true); + + AddAction(TEXT("refresh-provider-billing-settlement"), TEXT("F8"), TEXT("settlement-timeline-card")); + AddAction(TEXT("inspect-provider-invoice-reconciliation"), TEXT("R"), TEXT("invoice-reconciliation-card")); + AddAction(TEXT("review-provider-settlement-readiness"), TEXT("S"), TEXT("settlement-readiness-card")); + AddAction(TEXT("inspect-provider-route"), TEXT("P"), TEXT("settlement-summary-card")); + AddAction(TEXT("review-provider-budget"), TEXT("B"), TEXT("settlement-readiness-card")); + AddAction(TEXT("close-speech-session"), TEXT("Escape"), TEXT("session-root")); + return Profile; + } + FHyperTwistSpeechModelPayloadDescriptor MakeSpeechModelPayloadDescriptor( const TCHAR* PayloadId, const TCHAR* PayloadKind, @@ -3263,7 +3330,10 @@ namespace HyperTwistTrainingSubsystemInternal TEXT("providerUsageCostSummaryCopy"), TEXT("providerReceiptReviewShell"), TEXT("providerPostedChargeInspection"), - TEXT("providerChargeVarianceReview") + TEXT("providerChargeVarianceReview"), + TEXT("providerBillingSettlementShell"), + TEXT("providerInvoiceReconciliation"), + TEXT("providerSettlementReadinessReview") }; } @@ -3958,6 +4028,26 @@ namespace HyperTwistTrainingSubsystemInternal } SessionConfig.ProviderReceiptReviewShellProfileId = SessionConfig.ProviderReceiptReviewShellProfileDefinition.ReceiptReviewShellProfileId; + if (SessionConfig.ProviderBillingSettlementShellProfileId.IsEmpty() + && SessionConfig.ProviderBillingSettlementShellProfileDefinition.IsStructurallyValid()) + { + SessionConfig.ProviderBillingSettlementShellProfileId = + SessionConfig.ProviderBillingSettlementShellProfileDefinition.BillingSettlementShellProfileId; + } + if (SessionConfig.ProviderBillingSettlementShellProfileId.IsEmpty()) + { + SessionConfig.ProviderBillingSettlementShellProfileId = + TEXT("speech-provider-billing-settlement-shell-v1"); + } + if (!SessionConfig.ProviderBillingSettlementShellProfileDefinition.IsStructurallyValid()) + { + SessionConfig.ProviderBillingSettlementShellProfileDefinition = + BuildDefaultSpeechProviderBillingSettlementShellProfile( + SessionConfig.ProviderBillingSettlementShellProfileId + ); + } + SessionConfig.ProviderBillingSettlementShellProfileId = + SessionConfig.ProviderBillingSettlementShellProfileDefinition.BillingSettlementShellProfileId; } void ApplySpeechProviderProfileHealthDefaults( @@ -4296,6 +4386,9 @@ namespace HyperTwistTrainingSubsystemInternal Health.Capabilities.AddUnique(TEXT("providerReceiptReviewShell")); Health.Capabilities.AddUnique(TEXT("providerPostedChargeInspection")); Health.Capabilities.AddUnique(TEXT("providerChargeVarianceReview")); + Health.Capabilities.AddUnique(TEXT("providerBillingSettlementShell")); + Health.Capabilities.AddUnique(TEXT("providerInvoiceReconciliation")); + Health.Capabilities.AddUnique(TEXT("providerSettlementReadinessReview")); } bool IsSpeechPermissionError(const FString& ErrorText) @@ -5469,6 +5562,361 @@ namespace HyperTwistTrainingSubsystemInternal } } + FString BuildSpeechProviderBillingSettlementSourceKind( + const FHyperTwistSpeechProviderReceiptReviewEntry& ReceiptEntry + ) + { + if (ReceiptEntry.SourceKind.Contains(TEXT("fallback"), ESearchCase::IgnoreCase)) + { + return TEXT("receipt-review-fallback-health"); + } + + if (ReceiptEntry.bHasProviderPostedCharge) + { + return TEXT("receipt-review-derived-posted-charge"); + } + + if (ReceiptEntry.bEstimateOnly) + { + return TEXT("receipt-review-derived-estimate-only"); + } + + return TEXT("receipt-review-derived-mixed"); + } + + FHyperTwistSpeechProviderBillingSettlementEntry BuildSpeechProviderBillingSettlementEntry( + const FHyperTwistSpeechProviderReceiptReviewEntry& ReceiptEntry, + const FString& SettlementWindowId + ) + { + FHyperTwistSpeechProviderBillingSettlementEntry Entry; + Entry.SettlementEntryId = FString::Printf( + TEXT("speech-provider-settlement-entry/%s"), + *ReceiptEntry.ReceiptEntryId + ); + Entry.SourceKind = BuildSpeechProviderBillingSettlementSourceKind(ReceiptEntry); + Entry.ReceiptEntryId = ReceiptEntry.ReceiptEntryId; + Entry.HistoryEntryId = ReceiptEntry.HistoryEntryId; + Entry.ProviderProfileId = ReceiptEntry.ProviderProfileId; + Entry.ServiceLaneId = ReceiptEntry.ServiceLaneId; + Entry.PricingSourceId = ReceiptEntry.PricingSourceId; + Entry.ChargeWindowId = ReceiptEntry.ChargeWindowId; + Entry.SettlementWindowId = SettlementWindowId.IsEmpty() + ? TEXT("current-session") + : SettlementWindowId; + Entry.CapturedAtUtc = ReceiptEntry.CapturedAtUtc; + Entry.EstimatedCostUsd = ReceiptEntry.EstimatedCostUsd; + Entry.ReportedCostUsd = ReceiptEntry.ReportedCostUsd; + Entry.SettlementDeltaUsd = ReceiptEntry.AbsoluteVarianceUsd; + Entry.bHasProviderPostedCharge = ReceiptEntry.bHasProviderPostedCharge; + Entry.bRequiresManualReview = + ReceiptEntry.bHasProviderPostedCharge && !ReceiptEntry.bVarianceWithinTolerance; + Entry.bInvoiceReconciliationReady = + ReceiptEntry.bHasProviderPostedCharge && !Entry.bRequiresManualReview; + Entry.bSettlementBlocked = !Entry.bInvoiceReconciliationReady; + + if (!ReceiptEntry.bHasProviderPostedCharge) + { + Entry.SettlementStateId = TEXT("awaiting-provider-posted-charge"); + Entry.InvoiceReconciliationStateId = TEXT("invoice-pending"); + } + else if (Entry.bRequiresManualReview) + { + Entry.SettlementStateId = TEXT("variance-review-required"); + Entry.InvoiceReconciliationStateId = TEXT("invoice-variance-review-required"); + } + else + { + Entry.SettlementStateId = TEXT("ready-for-settlement-review"); + Entry.InvoiceReconciliationStateId = TEXT("invoice-ready-for-reconciliation"); + } + + return Entry; + } + + void AddSpeechProviderBillingSettlementIssue( + FHyperTwistSpeechProviderBillingSettlementShellState& ShellState, + const FHyperTwistSpeechProviderBillingSettlementIssue& Issue + ) + { + if (!Issue.IsStructurallyValid()) + { + return; + } + + ShellState.Issues.Add(Issue); + if (!ShellState.ActiveIssue.IsStructurallyValid()) + { + ShellState.ActiveIssue = Issue; + } + } + + void ApplySpeechProviderBillingSettlementPosture( + const FHyperTwistSpeechProviderBillingSettlementShellProfile& Profile, + const FHyperTwistTrainingCompanionSpeechSessionState& SessionState, + FHyperTwistSpeechProviderBillingSettlementShellState& ShellState + ) + { + ShellState.ActiveProviderProfileId = + !SessionState.ProviderReceiptReviewShellState.ActiveProviderProfileId.IsEmpty() + ? SessionState.ProviderReceiptReviewShellState.ActiveProviderProfileId + : (!SessionState.SessionConfig.ProviderProfileDefinition.ProviderProfileId.IsEmpty() + ? SessionState.SessionConfig.ProviderProfileDefinition.ProviderProfileId + : TEXT("speech-provider/local-http-sidecar-profile-v1")); + ShellState.ActiveProviderDisplayLabel = + !SessionState.ProviderReceiptReviewShellState.ActiveProviderDisplayLabel.IsEmpty() + ? SessionState.ProviderReceiptReviewShellState.ActiveProviderDisplayLabel + : (!SessionState.SessionConfig.ProviderProfileDefinition.DisplayLabel.IsEmpty() + ? SessionState.SessionConfig.ProviderProfileDefinition.DisplayLabel + : TEXT("Local HTTP Speech Sidecar")); + ShellState.ActiveServiceLaneId = + !SessionState.ProviderReceiptReviewShellState.ActiveServiceLaneId.IsEmpty() + ? SessionState.ProviderReceiptReviewShellState.ActiveServiceLaneId + : (!SessionState.SessionConfig.OrchestrationProfile.ServiceLaneId.IsEmpty() + ? SessionState.SessionConfig.OrchestrationProfile.ServiceLaneId + : TEXT("speech/python-service")); + ShellState.SelectedSettlementWindowId = + !SessionState.ProviderReceiptReviewShellState.SelectedChargeWindowId.IsEmpty() + ? SessionState.ProviderReceiptReviewShellState.SelectedChargeWindowId + : TEXT("current-session"); + ShellState.SettlementSurfaceModeId = Profile.SettlementSurfaceMode; + ShellState.InvoiceReconciliationModeId = Profile.InvoiceReconciliationMode; + ShellState.bSettlementTimelineVisible = Profile.bSupportsSettlementTimeline; + ShellState.bInvoiceReconciliationVisible = Profile.bSupportsInvoiceReconciliation; + ShellState.bSettlementReadinessVisible = Profile.bSupportsSettlementReadinessReview; + ShellState.SettlementEntries.Reset(); + ShellState.Issues.Reset(); + ShellState.ActiveIssue = FHyperTwistSpeechProviderBillingSettlementIssue(); + ShellState.SettlementEntryCount = 0; + ShellState.ProviderPostedChargeCount = 0; + ShellState.ReconciliationReadyEntryCount = 0; + ShellState.EstimateOnlyEntryCount = 0; + ShellState.ManualReviewEntryCount = 0; + ShellState.BlockedSettlementEntryCount = 0; + ShellState.TotalEstimatedCostUsd = 0.0f; + ShellState.TotalReportedCostUsd = 0.0f; + ShellState.TotalSettlementDeltaUsd = 0.0f; + ShellState.HighestSettlementDeltaUsd = 0.0f; + ShellState.LatestSettlementEntryId.Reset(); + ShellState.bSettlementSummaryReady = false; + ShellState.bInvoiceReconciliationReady = false; + ShellState.bSettlementReadinessReviewReady = false; + + const FHyperTwistSpeechProviderReceiptReviewShellState& ReceiptState = + SessionState.ProviderReceiptReviewShellState; + if (!ReceiptState.IsStructurallyValid() || ReceiptState.ReceiptEntries.Num() <= 0) + { + FHyperTwistSpeechProviderBillingSettlementIssue Issue; + Issue.IssueId = TEXT("provider-billing-settlement-receipt-review-missing"); + Issue.IssueKind = TEXT("no-receipt-review"); + Issue.StatusLine = TEXT("Provider billing settlement is waiting for receipt review."); + Issue.DetailLine = TEXT("Refresh the retained provider receipt-review shell before settlement posture begins."); + Issue.RecommendedActionId = Profile.RefreshActionId; + Issue.bRequiresOperatorAttention = false; + AddSpeechProviderBillingSettlementIssue(ShellState, Issue); + ShellState.StatusLine = Issue.StatusLine; + ShellState.DetailLine = Issue.DetailLine; + ShellState.ActiveIssueCount = ShellState.Issues.Num(); + return; + } + + const int32 SettlementLimit = FMath::Max(1, Profile.RetainedSettlementEntryLimit); + const int32 StartIndex = FMath::Max(0, ReceiptState.ReceiptEntries.Num() - SettlementLimit); + for (int32 EntryIndex = StartIndex; EntryIndex < ReceiptState.ReceiptEntries.Num(); ++EntryIndex) + { + const FHyperTwistSpeechProviderReceiptReviewEntry& ReceiptEntry = + ReceiptState.ReceiptEntries[EntryIndex]; + if (!ReceiptEntry.IsStructurallyValid()) + { + continue; + } + + const FHyperTwistSpeechProviderBillingSettlementEntry Entry = + BuildSpeechProviderBillingSettlementEntry( + ReceiptEntry, + ShellState.SelectedSettlementWindowId + ); + ShellState.SettlementEntries.Add(Entry); + ShellState.LatestSettlementEntryId = Entry.SettlementEntryId; + ShellState.LatestSourceKind = Entry.SourceKind; + ShellState.TotalEstimatedCostUsd += Entry.EstimatedCostUsd; + ShellState.TotalReportedCostUsd += Entry.ReportedCostUsd; + ShellState.TotalSettlementDeltaUsd += Entry.SettlementDeltaUsd; + ShellState.HighestSettlementDeltaUsd = + FMath::Max(ShellState.HighestSettlementDeltaUsd, Entry.SettlementDeltaUsd); + if (Entry.bHasProviderPostedCharge) + { + ++ShellState.ProviderPostedChargeCount; + } + if (Entry.bInvoiceReconciliationReady) + { + ++ShellState.ReconciliationReadyEntryCount; + } + if (ReceiptEntry.bEstimateOnly) + { + ++ShellState.EstimateOnlyEntryCount; + } + if (Entry.bRequiresManualReview) + { + ++ShellState.ManualReviewEntryCount; + } + if (Entry.bSettlementBlocked) + { + ++ShellState.BlockedSettlementEntryCount; + } + } + + ShellState.SettlementEntryCount = ShellState.SettlementEntries.Num(); + ShellState.bSettlementSummaryReady = ShellState.SettlementEntryCount > 0; + ShellState.bInvoiceReconciliationReady = + Profile.bSupportsInvoiceReconciliation + && ShellState.ReconciliationReadyEntryCount > 0; + ShellState.bSettlementReadinessReviewReady = + Profile.bSupportsSettlementReadinessReview + && ShellState.SettlementEntryCount > 0; + + if (ReceiptState.ActiveIssue.IsStructurallyValid() + && (ReceiptState.ActiveIssue.IssueKind.Equals(TEXT("route-degraded"), ESearchCase::IgnoreCase) + || ReceiptState.ActiveIssue.IssueKind.Equals(TEXT("hard-blocked"), ESearchCase::IgnoreCase) + || ReceiptState.ActiveIssue.IssueKind.Equals(TEXT("rate-limited"), ESearchCase::IgnoreCase) + || ReceiptState.ActiveIssue.IssueKind.Equals(TEXT("no-history"), ESearchCase::IgnoreCase))) + { + FHyperTwistSpeechProviderBillingSettlementIssue Issue; + Issue.IssueId = FString::Printf( + TEXT("provider-billing-settlement-%s"), + *ReceiptState.ActiveIssue.IssueKind + ); + Issue.IssueKind = ReceiptState.ActiveIssue.IssueKind; + Issue.StatusLine = ReceiptState.ActiveIssue.StatusLine; + Issue.DetailLine = ReceiptState.ActiveIssue.DetailLine; + Issue.RecommendedActionId = ReceiptState.ActiveIssue.RecommendedActionId; + Issue.bRequiresOperatorAttention = + ReceiptState.ActiveIssue.bRequiresOperatorAttention; + AddSpeechProviderBillingSettlementIssue(ShellState, Issue); + } + else if (ShellState.ManualReviewEntryCount > 0) + { + FHyperTwistSpeechProviderBillingSettlementIssue Issue; + Issue.IssueId = TEXT("provider-billing-settlement-variance-review-required"); + Issue.IssueKind = TEXT("settlement-variance-review-required"); + Issue.StatusLine = TEXT("Settlement variance requires review before reconciliation."); + Issue.DetailLine = TEXT("At least one posted provider charge still diverges from the first-party estimate beyond the bounded receipt-review tolerance."); + Issue.RecommendedActionId = Profile.SettlementReadinessActionId; + Issue.bRequiresOperatorAttention = true; + AddSpeechProviderBillingSettlementIssue(ShellState, Issue); + } + else if (ShellState.ProviderPostedChargeCount <= 0) + { + FHyperTwistSpeechProviderBillingSettlementIssue Issue; + Issue.IssueId = TEXT("provider-billing-settlement-charge-pending"); + Issue.IssueKind = TEXT("provider-charge-pending"); + Issue.StatusLine = TEXT("Provider billing settlement is waiting for posted charges."); + Issue.DetailLine = TEXT("Settlement posture is bounded and ready, but the retained receipt review still shows estimate-only provider cost posture."); + Issue.RecommendedActionId = Profile.InvoiceReconciliationActionId; + Issue.bRequiresOperatorAttention = false; + AddSpeechProviderBillingSettlementIssue(ShellState, Issue); + } + + if (ShellState.ActiveIssue.IsStructurallyValid()) + { + ShellState.StatusLine = ShellState.ActiveIssue.StatusLine; + ShellState.DetailLine = ShellState.ActiveIssue.DetailLine; + } + else if (ShellState.ReconciliationReadyEntryCount > 0) + { + ShellState.StatusLine = + TEXT("Provider billing settlement is ready for bounded reconciliation."); + ShellState.DetailLine = FString::Printf( + TEXT("%d retained posted charges for %s are within the current first-party tolerance and ready for invoice reconciliation review."), + ShellState.ReconciliationReadyEntryCount, + *ShellState.ActiveProviderDisplayLabel + ); + } + else + { + ShellState.StatusLine = + TEXT("Provider billing settlement remains bounded above receipt review."); + ShellState.DetailLine = FString::Printf( + TEXT("%d retained settlement entries are available for %s, but a reconciled provider invoice posture is not ready yet."), + ShellState.SettlementEntryCount, + *ShellState.ActiveProviderDisplayLabel + ); + } + + ShellState.SettlementSummaryText = FString::Printf( + TEXT("providerProfileId=%s\nserviceLaneId=%s\nsettlementEntryCount=%d\nproviderPostedChargeCount=%d\n") + TEXT("reconciliationReadyEntryCount=%d\nestimateOnlyEntryCount=%d\nmanualReviewEntryCount=%d\n") + TEXT("blockedSettlementEntryCount=%d\ntotalEstimatedCostUsd=%.2f\ntotalReportedCostUsd=%.2f\n") + TEXT("totalSettlementDeltaUsd=%.2f\nhighestSettlementDeltaUsd=%.2f\nlatestSettlementEntryId=%s\n") + TEXT("latestSourceKind=%s\nlastError=%s"), + *ShellState.ActiveProviderProfileId, + *ShellState.ActiveServiceLaneId, + ShellState.SettlementEntryCount, + ShellState.ProviderPostedChargeCount, + ShellState.ReconciliationReadyEntryCount, + ShellState.EstimateOnlyEntryCount, + ShellState.ManualReviewEntryCount, + ShellState.BlockedSettlementEntryCount, + ShellState.TotalEstimatedCostUsd, + ShellState.TotalReportedCostUsd, + ShellState.TotalSettlementDeltaUsd, + ShellState.HighestSettlementDeltaUsd, + *ShellState.LatestSettlementEntryId, + *ShellState.LatestSourceKind, + *SessionState.LastError + ); + ShellState.ActiveIssueCount = ShellState.Issues.Num(); + } + + void SynchronizeSpeechProviderBillingSettlementState( + FHyperTwistTrainingCompanionSpeechSessionState& SessionState + ) + { + FHyperTwistSpeechSessionConfig EffectiveConfig = SessionState.SessionConfig; + ApplySpeechUsageCostDefaults(EffectiveConfig); + if (!SessionState.ProviderReceiptReviewShellState.IsStructurallyValid()) + { + SessionState.ProviderReceiptReviewShellState = + BuildSpeechProviderReceiptReviewShellState(EffectiveConfig, &SessionState); + } + + const FHyperTwistSpeechProviderBillingSettlementShellProfile Profile = + EffectiveConfig.ProviderBillingSettlementShellProfileDefinition.IsStructurallyValid() + ? EffectiveConfig.ProviderBillingSettlementShellProfileDefinition + : BuildDefaultSpeechProviderBillingSettlementShellProfile( + !EffectiveConfig.ProviderBillingSettlementShellProfileId.IsEmpty() + ? EffectiveConfig.ProviderBillingSettlementShellProfileId + : TEXT("speech-provider-billing-settlement-shell-v1") + ); + + if (!SessionState.ProviderBillingSettlementShellState.IsStructurallyValid()) + { + SessionState.ProviderBillingSettlementShellState = + BuildSpeechProviderBillingSettlementShellState(EffectiveConfig, &SessionState); + } + + FHyperTwistSpeechProviderBillingSettlementShellState& ShellState = + SessionState.ProviderBillingSettlementShellState; + ShellState.BillingSettlementShellProfileId = Profile.BillingSettlementShellProfileId; + ShellState.AvailableActionIds.Reset(); + for (const FHyperTwistSpeechShellActionBinding& ActionBinding : Profile.ActionBindings) + { + ShellState.AvailableActionIds.AddUnique(ActionBinding.ActionId); + } + + ApplySpeechProviderBillingSettlementPosture(Profile, SessionState, ShellState); + + ShellState.AvailableActionIds.AddUnique(Profile.RefreshActionId); + ShellState.AvailableActionIds.AddUnique(Profile.InvoiceReconciliationActionId); + ShellState.AvailableActionIds.AddUnique(Profile.SettlementReadinessActionId); + ShellState.AvailableActionIds.AddUnique(Profile.RouteInspectActionId); + ShellState.AvailableActionIds.AddUnique(Profile.BudgetInspectActionId); + if (ShellState.ActiveIssue.IsStructurallyValid()) + { + ShellState.AvailableActionIds.AddUnique(ShellState.ActiveIssue.RecommendedActionId); + } + } + FHyperTwistSpeechMicrophoneShellState BuildCoachCommandMicrophoneShellState( const FHyperTwistSpeechSessionConfig& SessionConfig, const FHyperTwistTrainingCompanionSpeechSessionState* PriorState, @@ -5722,6 +6170,57 @@ namespace HyperTwistTrainingSubsystemInternal return ShellState; } + FHyperTwistSpeechProviderBillingSettlementShellState BuildSpeechProviderBillingSettlementShellState( + const FHyperTwistSpeechSessionConfig& SessionConfig, + const FHyperTwistTrainingCompanionSpeechSessionState* PriorState + ) + { + const FHyperTwistSpeechProviderBillingSettlementShellProfile Profile = + SessionConfig.ProviderBillingSettlementShellProfileDefinition.IsStructurallyValid() + ? SessionConfig.ProviderBillingSettlementShellProfileDefinition + : BuildDefaultSpeechProviderBillingSettlementShellProfile( + !SessionConfig.ProviderBillingSettlementShellProfileId.IsEmpty() + ? SessionConfig.ProviderBillingSettlementShellProfileId + : TEXT("speech-provider-billing-settlement-shell-v1") + ); + + FHyperTwistSpeechProviderBillingSettlementShellState ShellState; + ShellState.BillingSettlementShellProfileId = Profile.BillingSettlementShellProfileId; + ShellState.ActiveProviderProfileId = SessionConfig.ProviderProfileDefinition.IsStructurallyValid() + ? SessionConfig.ProviderProfileDefinition.ProviderProfileId + : TEXT("speech-provider/local-http-sidecar-profile-v1"); + ShellState.ActiveProviderDisplayLabel = SessionConfig.ProviderProfileDefinition.IsStructurallyValid() + ? SessionConfig.ProviderProfileDefinition.DisplayLabel + : TEXT("Local HTTP Speech Sidecar"); + ShellState.ActiveServiceLaneId = !SessionConfig.OrchestrationProfile.ServiceLaneId.IsEmpty() + ? SessionConfig.OrchestrationProfile.ServiceLaneId + : TEXT("speech/python-service"); + ShellState.SelectedSettlementWindowId = !SessionConfig.CostEventTemplate.AggregationWindowId.IsEmpty() + ? SessionConfig.CostEventTemplate.AggregationWindowId + : TEXT("current-session"); + ShellState.SettlementSurfaceModeId = Profile.SettlementSurfaceMode; + ShellState.InvoiceReconciliationModeId = Profile.InvoiceReconciliationMode; + ShellState.bSettlementTimelineVisible = Profile.bSupportsSettlementTimeline; + ShellState.bInvoiceReconciliationVisible = Profile.bSupportsInvoiceReconciliation; + ShellState.bSettlementReadinessVisible = Profile.bSupportsSettlementReadinessReview; + for (const FHyperTwistSpeechShellActionBinding& ActionBinding : Profile.ActionBindings) + { + ShellState.AvailableActionIds.AddUnique(ActionBinding.ActionId); + } + + if (PriorState != nullptr + && PriorState->ProviderBillingSettlementShellState.BillingSettlementShellProfileId + == Profile.BillingSettlementShellProfileId) + { + ShellState = PriorState->ProviderBillingSettlementShellState; + ShellState.BillingSettlementShellProfileId = Profile.BillingSettlementShellProfileId; + ShellState.SettlementSurfaceModeId = Profile.SettlementSurfaceMode; + ShellState.InvoiceReconciliationModeId = Profile.InvoiceReconciliationMode; + } + + return ShellState; + } + FHyperTwistVisionCorrectionState BuildClassicCubeCorrectionState( const FHyperTwistVisionSessionConfig& SessionConfig, const FHyperTwistVisionReconstructionSession& ReconstructionSession, @@ -11101,6 +11600,11 @@ bool UHyperTwistTrainingSubsystem::OpenActiveCompanionSpeechSession(FString& Out SessionConfig, nullptr ); + ActiveCompanionSpeechSessionState.ProviderBillingSettlementShellState = + HyperTwistTrainingSubsystemInternal::BuildSpeechProviderBillingSettlementShellState( + SessionConfig, + nullptr + ); if (ActiveCompanionSpeechSessionState.bSessionOpen && ActiveCompanionSpeechSessionState.ActiveSessionId != SessionConfig.SessionId) @@ -11181,6 +11685,9 @@ FHyperTwistSpeechTranscriptResult UHyperTwistTrainingSubsystem::SubmitActiveComp HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderReceiptReviewState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderBillingSettlementState( + ActiveCompanionSpeechSessionState + ); return Result; } @@ -11361,6 +11868,9 @@ FHyperTwistSpeechTranscriptResult UHyperTwistTrainingSubsystem::SubmitActiveComp HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderReceiptReviewState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderBillingSettlementState( + ActiveCompanionSpeechSessionState + ); return Result; } @@ -12113,6 +12623,9 @@ void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealth() HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderReceiptReviewState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderBillingSettlementState( + ActiveCompanionSpeechSessionState + ); return; } @@ -12147,6 +12660,9 @@ void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealth() HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderReceiptReviewState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderBillingSettlementState( + ActiveCompanionSpeechSessionState + ); } void UHyperTwistTrainingSubsystem::AppendRecognitionReplayEvent( diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h index 969e381..d697668 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h @@ -3637,6 +3637,405 @@ struct FHyperTwistSpeechProviderReceiptReviewShellState } }; +USTRUCT(BlueprintType) +struct FHyperTwistSpeechProviderBillingSettlementShellProfile +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString BillingSettlementShellProfileId = TEXT("speech-provider-billing-settlement-shell-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ShellKind = TEXT("operator-provider-billing-settlement"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SettlementSurfaceMode = TEXT("posted-charge-settlement-assessment"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString InvoiceReconciliationMode = TEXT("estimate-versus-provider-invoice"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RefreshActionId = TEXT("refresh-provider-billing-settlement"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString InvoiceReconciliationActionId = TEXT("inspect-provider-invoice-reconciliation"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SettlementReadinessActionId = TEXT("review-provider-settlement-readiness"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RouteInspectActionId = TEXT("inspect-provider-route"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString BudgetInspectActionId = TEXT("review-provider-budget"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RetainedSettlementEntryLimit = 12; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsSettlementTimeline = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsInvoiceReconciliation = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsSettlementReadinessReview = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsEstimateOnlyFallback = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bPreservesFirstPartySettlementOwnership = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray Panels; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ActionBindings; + + bool IsStructurallyValid() const + { + if (BillingSettlementShellProfileId.IsEmpty() + || ShellKind.IsEmpty() + || SettlementSurfaceMode.IsEmpty() + || InvoiceReconciliationMode.IsEmpty() + || RefreshActionId.IsEmpty() + || InvoiceReconciliationActionId.IsEmpty() + || SettlementReadinessActionId.IsEmpty() + || RouteInspectActionId.IsEmpty() + || BudgetInspectActionId.IsEmpty() + || RetainedSettlementEntryLimit <= 0 + || Panels.Num() <= 0 + || ActionBindings.Num() <= 0) + { + return false; + } + + for (const FHyperTwistSpeechShellPanelLayout& Panel : Panels) + { + if (!Panel.IsStructurallyValid()) + { + return false; + } + } + + for (const FHyperTwistSpeechShellActionBinding& ActionBinding : ActionBindings) + { + if (!ActionBinding.IsStructurallyValid()) + { + return false; + } + } + + return true; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistSpeechProviderBillingSettlementEntry +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SettlementEntryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SourceKind = TEXT("receipt-review-derived-estimate-only"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ReceiptEntryId = TEXT("speech-provider-receipt-entry/current-session"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HistoryEntryId = TEXT("speech-history-entry/current-session"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ProviderProfileId = TEXT("speech-provider/local-http-sidecar-profile-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ServiceLaneId = TEXT("speech/python-service"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PricingSourceId = TEXT("first-party-estimate"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ChargeWindowId = TEXT("current-session"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SettlementWindowId = TEXT("current-session"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CapturedAtUtc = TEXT("2026-05-25T00:00:00Z"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float EstimatedCostUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float ReportedCostUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float SettlementDeltaUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SettlementStateId = TEXT("awaiting-provider-posted-charge"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString InvoiceReconciliationStateId = TEXT("invoice-pending"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasProviderPostedCharge = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bInvoiceReconciliationReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRequiresManualReview = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSettlementBlocked = true; + + bool IsStructurallyValid() const + { + return !SettlementEntryId.IsEmpty() + && !SourceKind.IsEmpty() + && !ReceiptEntryId.IsEmpty() + && !HistoryEntryId.IsEmpty() + && !ProviderProfileId.IsEmpty() + && !ServiceLaneId.IsEmpty() + && !PricingSourceId.IsEmpty() + && !ChargeWindowId.IsEmpty() + && !SettlementWindowId.IsEmpty() + && !CapturedAtUtc.IsEmpty() + && EstimatedCostUsd >= 0.0f + && ReportedCostUsd >= 0.0f + && SettlementDeltaUsd >= 0.0f + && !SettlementStateId.IsEmpty() + && !InvoiceReconciliationStateId.IsEmpty(); + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistSpeechProviderBillingSettlementIssue +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString IssueId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString IssueKind; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RecommendedActionId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRequiresOperatorAttention = true; + + bool IsStructurallyValid() const + { + return !IssueId.IsEmpty() + && !IssueKind.IsEmpty() + && !StatusLine.IsEmpty() + && !DetailLine.IsEmpty() + && !RecommendedActionId.IsEmpty(); + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistSpeechProviderBillingSettlementShellState +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString BillingSettlementShellProfileId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActiveProviderProfileId = TEXT("speech-provider/local-http-sidecar-profile-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActiveProviderDisplayLabel = TEXT("Local HTTP Speech Sidecar"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActiveServiceLaneId = TEXT("speech/python-service"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SelectedSettlementWindowId = TEXT("current-session"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SettlementSurfaceModeId = TEXT("posted-charge-settlement-assessment"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString InvoiceReconciliationModeId = TEXT("estimate-versus-provider-invoice"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LatestSettlementEntryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LatestSourceKind = TEXT("receipt-review-derived-estimate-only"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine = TEXT("Provider billing settlement posture is ready for bounded inspection."); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine = TEXT("First-party settlement posture remains bounded above retained receipt review and does not widen into payment execution."); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SettlementSummaryText; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 SettlementEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ProviderPostedChargeCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ReconciliationReadyEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 EstimateOnlyEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ManualReviewEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 BlockedSettlementEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float TotalEstimatedCostUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float TotalReportedCostUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float TotalSettlementDeltaUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float HighestSettlementDeltaUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSettlementTimelineVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bInvoiceReconciliationVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSettlementReadinessVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSettlementSummaryReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bInvoiceReconciliationReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSettlementReadinessReviewReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ActiveIssueCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechProviderBillingSettlementIssue ActiveIssue; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray Issues; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray SettlementEntries; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray AvailableActionIds; + + bool IsStructurallyValid() const + { + if (BillingSettlementShellProfileId.IsEmpty() + || ActiveProviderProfileId.IsEmpty() + || ActiveProviderDisplayLabel.IsEmpty() + || ActiveServiceLaneId.IsEmpty() + || SelectedSettlementWindowId.IsEmpty() + || SettlementSurfaceModeId.IsEmpty() + || InvoiceReconciliationModeId.IsEmpty() + || LatestSourceKind.IsEmpty() + || StatusLine.IsEmpty() + || DetailLine.IsEmpty() + || SettlementEntryCount < 0 + || ProviderPostedChargeCount < 0 + || ReconciliationReadyEntryCount < 0 + || EstimateOnlyEntryCount < 0 + || ManualReviewEntryCount < 0 + || BlockedSettlementEntryCount < 0 + || TotalEstimatedCostUsd < 0.0f + || TotalReportedCostUsd < 0.0f + || TotalSettlementDeltaUsd < 0.0f + || HighestSettlementDeltaUsd < 0.0f + || ActiveIssueCount < 0 + || AvailableActionIds.Num() <= 0) + { + return false; + } + + if (SettlementEntryCount != SettlementEntries.Num() + || ProviderPostedChargeCount > SettlementEntryCount + || ReconciliationReadyEntryCount > SettlementEntryCount + || EstimateOnlyEntryCount > SettlementEntryCount + || ManualReviewEntryCount > SettlementEntryCount + || BlockedSettlementEntryCount > SettlementEntryCount) + { + return false; + } + + if (SettlementEntryCount > 0 && LatestSettlementEntryId.IsEmpty()) + { + return false; + } + + if (bSettlementSummaryReady && SettlementSummaryText.IsEmpty()) + { + return false; + } + + if (ActiveIssueCount != Issues.Num()) + { + return false; + } + + if (ActiveIssueCount > 0 && !ActiveIssue.IsStructurallyValid()) + { + return false; + } + + for (const FHyperTwistSpeechProviderBillingSettlementIssue& Issue : Issues) + { + if (!Issue.IsStructurallyValid()) + { + return false; + } + } + + for (const FHyperTwistSpeechProviderBillingSettlementEntry& Entry : SettlementEntries) + { + if (!Entry.IsStructurallyValid()) + { + return false; + } + } + + for (const FString& ActionId : AvailableActionIds) + { + if (ActionId.IsEmpty()) + { + return false; + } + } + + return true; + } +}; + USTRUCT(BlueprintType) struct FHyperTwistSpeechSessionConfig { @@ -3730,6 +4129,14 @@ struct FHyperTwistSpeechSessionConfig FHyperTwistSpeechProviderReceiptReviewShellProfile ProviderReceiptReviewShellProfileDefinition; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ProviderBillingSettlementShellProfileId = + TEXT("speech-provider-billing-settlement-shell-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechProviderBillingSettlementShellProfile + ProviderBillingSettlementShellProfileDefinition; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1"); @@ -3782,6 +4189,8 @@ struct FHyperTwistSpeechSessionConfig || !UsageCostHistoryExportShellProfileDefinition.IsStructurallyValid() || ProviderReceiptReviewShellProfileId.IsEmpty() || !ProviderReceiptReviewShellProfileDefinition.IsStructurallyValid() + || ProviderBillingSettlementShellProfileId.IsEmpty() + || !ProviderBillingSettlementShellProfileDefinition.IsStructurallyValid() || PayloadCustodyProfileId.IsEmpty() || !PayloadCustodyProfileDefinition.IsStructurallyValid() || RequiredModelPayloads.Num() <= 0 diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 4fcd2df..25e440f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -1949,6 +1949,9 @@ struct FHyperTwistTrainingCompanionSpeechSessionState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistSpeechProviderReceiptReviewShellState ProviderReceiptReviewShellState; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechProviderBillingSettlementShellState ProviderBillingSettlementShellState; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistSpeechTranscriptResult LastTranscriptResult; }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RADProviderBillingSettlementShellContractTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RADProviderBillingSettlementShellContractTest.cpp new file mode 100644 index 0000000..3e49146 --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RADProviderBillingSettlementShellContractTest.cpp @@ -0,0 +1,294 @@ +// Copyright HyperTwist, Inc. All Rights Reserved. + +#include "Misc/AutomationTest.h" + +#include "Engine/GameInstance.h" +#include "HyperTwistRecognition/HyperTwistSpeechClient.h" +#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h" +#include "UObject/UnrealType.h" + +#if WITH_AUTOMATION_TESTS + +namespace HyperTwistWhisperCppPhase6RADTestInternal +{ + FHyperTwistTrainingDeck MakeSpeechDeck() + { + FHyperTwistTrainingDeck Deck; + Deck.DeckId = TEXT("phase6r-ad/provider-billing-settlement"); + Deck.Title = TEXT("Phase 6R-AD Provider Billing Settlement"); + Deck.DeliveryModes = { + EHyperTwistTrainingDeliveryMode::CoachReviewed + }; + + FHyperTwistTrainingCase TrainingCase; + TrainingCase.CaseId = TEXT("phase6r-ad-case"); + TrainingCase.PuzzleId = TEXT("cube/3x3x3"); + TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Sequence; + TrainingCase.PromptLabel = TEXT("Phase 6R-AD Coach Speech"); + TrainingCase.AllowedDeliveryModes = { + EHyperTwistTrainingDeliveryMode::CoachReviewed + }; + Deck.Cases = {TrainingCase}; + return Deck; + } + + void ForceSpeechClientKind(UHyperTwistTrainingSubsystem* TrainingSubsystem, const FString& ClientKind) + { + if (TrainingSubsystem == nullptr) + { + return; + } + + if (FStrProperty* SpeechClientKindProperty = FindFProperty( + UHyperTwistTrainingSubsystem::StaticClass(), + TEXT("CompanionSpeechClientKind") + )) + { + SpeechClientKindProperty->SetPropertyValue_InContainer(TrainingSubsystem, ClientKind); + } + } + + UHyperTwistTrainingSubsystem* MakeSpeechSubsystem(const FString& SessionId, const FString& ClientKind) + { + UGameInstance* GameInstance = NewObject(GetTransientPackage()); + if (GameInstance == nullptr) + { + return nullptr; + } + + UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject(GameInstance); + if (TrainingSubsystem == nullptr) + { + return nullptr; + } + + ForceSpeechClientKind(TrainingSubsystem, ClientKind); + const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck( + MakeSpeechDeck(), + TEXT("phase6r-ad-user"), + SessionId, + EHyperTwistTrainingDeliveryMode::CoachReviewed + ); + return RunState.IsStructurallyValid() ? TrainingSubsystem : nullptr; + } + + UHyperTwistHttpSpeechClient* ResolveHttpSpeechClient(UHyperTwistTrainingSubsystem* TrainingSubsystem) + { + if (TrainingSubsystem == nullptr) + { + return nullptr; + } + + FString CloseError; + TrainingSubsystem->CloseActiveCompanionSpeechSession(CloseError); + + if (FObjectPropertyBase* ClientProperty = FindFProperty( + UHyperTwistTrainingSubsystem::StaticClass(), + TEXT("ActiveCompanionSpeechClientObject") + )) + { + return Cast( + ClientProperty->GetObjectPropertyValue_InContainer(TrainingSubsystem) + ); + } + + return nullptr; + } + + FHyperTwistSpeechUtteranceEnvelope MakeUtterance() + { + FHyperTwistSpeechUtteranceEnvelope Utterance; + Utterance.AudioRef = TEXT("ready-coach"); + Utterance.SpeechStartMs = 120; + Utterance.SpeechEndMs = 840; + Utterance.SilenceGapMs = 240; + return Utterance; + } + + bool ContainsPanelId( + const TArray& Panels, + const FString& ExpectedPanelId + ) + { + for (const FHyperTwistSpeechShellPanelLayout& Panel : Panels) + { + if (Panel.PanelId == ExpectedPanelId) + { + return true; + } + } + + return false; + } + + bool ContainsActionId( + const TArray& ActionBindings, + const FString& ExpectedActionId + ) + { + for (const FHyperTwistSpeechShellActionBinding& ActionBinding : ActionBindings) + { + if (ActionBinding.ActionId == ExpectedActionId) + { + return true; + } + } + + return false; + } +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RADProviderBillingSettlementSessionConfigTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AD.ProviderBillingSettlementSessionConfig", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RADProviderBillingSettlementSessionConfigTest::RunTest( + const FString& Parameters +) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RADTestInternal::MakeSpeechSubsystem( + TEXT("phase6r-ad-config-session"), + TEXT("mock") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AD."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + FString OpenError; + TestTrue(TEXT("The mock billing-settlement route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestTrue(TEXT("Opening the mock billing-settlement speech session must not report an error."), OpenError.IsEmpty()); + + const FHyperTwistSpeechSessionConfig& Config = + TrainingSubsystem->GetActiveCompanionSpeechSessionState().SessionConfig; + + TestTrue(TEXT("The billing-settlement session config must remain structurally valid."), Config.IsStructurallyValid()); + TestEqual(TEXT("The billing-settlement shell profile id must be present."), Config.ProviderBillingSettlementShellProfileId, TEXT("speech-provider-billing-settlement-shell-v1")); + TestTrue(TEXT("The billing-settlement shell profile must remain structurally valid."), Config.ProviderBillingSettlementShellProfileDefinition.IsStructurallyValid()); + TestEqual(TEXT("The billing-settlement shell must preserve the operator-facing shell kind."), Config.ProviderBillingSettlementShellProfileDefinition.ShellKind, TEXT("operator-provider-billing-settlement")); + TestEqual(TEXT("The billing-settlement shell must preserve the invoice reconciliation action id."), Config.ProviderBillingSettlementShellProfileDefinition.InvoiceReconciliationActionId, TEXT("inspect-provider-invoice-reconciliation")); + TestEqual(TEXT("The billing-settlement shell must preserve the readiness review action id."), Config.ProviderBillingSettlementShellProfileDefinition.SettlementReadinessActionId, TEXT("review-provider-settlement-readiness")); + TestTrue(TEXT("The billing-settlement shell must include the settlement timeline panel."), HyperTwistWhisperCppPhase6RADTestInternal::ContainsPanelId(Config.ProviderBillingSettlementShellProfileDefinition.Panels, TEXT("settlement-timeline-card"))); + TestTrue(TEXT("The billing-settlement shell must include the invoice reconciliation action."), HyperTwistWhisperCppPhase6RADTestInternal::ContainsActionId(Config.ProviderBillingSettlementShellProfileDefinition.ActionBindings, TEXT("inspect-provider-invoice-reconciliation"))); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RADProviderBillingSettlementShellStateTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AD.ProviderBillingSettlementShellState", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RADProviderBillingSettlementShellStateTest::RunTest( + const FString& Parameters +) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RADTestInternal::MakeSpeechSubsystem( + TEXT("phase6r-ad-shell-session"), + TEXT("mock") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AD."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + FString OpenError; + TestTrue(TEXT("The mock billing-settlement route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestTrue(TEXT("Opening the mock billing-settlement speech session must not report an error."), OpenError.IsEmpty()); + + const FHyperTwistSpeechTranscriptResult Result = + TrainingSubsystem->SubmitActiveCompanionSpeechUtterance( + HyperTwistWhisperCppPhase6RADTestInternal::MakeUtterance() + ); + TestTrue(TEXT("The mock billing-settlement utterance must produce a final transcript."), Result.bIsFinal); + TestTrue(TEXT("The mock billing-settlement utterance must remain structurally valid."), Result.IsStructurallyValid()); + + const FHyperTwistTrainingCompanionSpeechSessionState SessionState = + TrainingSubsystem->GetActiveCompanionSpeechSessionState(); + const FHyperTwistSpeechServiceHealth& Health = SessionState.ServiceHealth; + const FHyperTwistSpeechProviderBillingSettlementShellState& Settlement = + SessionState.ProviderBillingSettlementShellState; + + TestTrue(TEXT("The service health must expose the billing-settlement shell."), Health.Capabilities.Contains(TEXT("providerBillingSettlementShell"))); + TestTrue(TEXT("The service health must expose invoice reconciliation."), Health.Capabilities.Contains(TEXT("providerInvoiceReconciliation"))); + TestTrue(TEXT("The service health must expose settlement readiness review."), Health.Capabilities.Contains(TEXT("providerSettlementReadinessReview"))); + + TestTrue(TEXT("The billing-settlement shell state must remain structurally valid."), Settlement.IsStructurallyValid()); + TestEqual(TEXT("The billing-settlement shell must preserve the mock provider profile."), Settlement.ActiveProviderProfileId, TEXT("speech-provider/mock-offline-profile-v1")); + TestEqual(TEXT("The billing-settlement shell must retain exactly two bounded settlement entries after open and submit."), Settlement.SettlementEntryCount, 2); + TestEqual(TEXT("The billing-settlement shell must preserve the latest source kind."), Settlement.LatestSourceKind, TEXT("receipt-review-derived-estimate-only")); + TestTrue(TEXT("The billing-settlement shell must preserve summary readiness."), Settlement.bSettlementSummaryReady); + TestFalse(TEXT("The billing-settlement shell must not mark invoice reconciliation ready before provider-posted charges appear."), Settlement.bInvoiceReconciliationReady); + TestTrue(TEXT("The billing-settlement shell must preserve readiness-review visibility."), Settlement.bSettlementReadinessReviewReady); + TestTrue(TEXT("The billing-settlement shell must preserve the invoice reconciliation action."), Settlement.AvailableActionIds.Contains(TEXT("inspect-provider-invoice-reconciliation"))); + TestTrue(TEXT("The billing-settlement shell must preserve the readiness review action."), Settlement.AvailableActionIds.Contains(TEXT("review-provider-settlement-readiness"))); + TestEqual(TEXT("The billing-settlement shell must preserve zero provider-posted charges in the mock route."), Settlement.ProviderPostedChargeCount, 0); + TestEqual(TEXT("The billing-settlement shell must preserve zero reconciliation-ready entries in the mock route."), Settlement.ReconciliationReadyEntryCount, 0); + TestEqual(TEXT("The billing-settlement shell must preserve the estimate-only entry count."), Settlement.EstimateOnlyEntryCount, 2); + TestEqual(TEXT("The billing-settlement shell must preserve the blocked-settlement entry count."), Settlement.BlockedSettlementEntryCount, 2); + TestTrue(TEXT("The billing-settlement shell must accumulate the mock estimated cost."), FMath::IsNearlyEqual(Settlement.TotalEstimatedCostUsd, 0.02f)); + TestTrue(TEXT("The billing-settlement shell must preserve zero settlement delta while no provider charge is posted."), FMath::IsNearlyEqual(Settlement.TotalSettlementDeltaUsd, 0.0f)); + TestEqual(TEXT("The billing-settlement shell must surface the pending provider-charge issue."), Settlement.ActiveIssue.IssueKind, TEXT("provider-charge-pending")); + TestTrue(TEXT("The billing-settlement summary text must preserve the bounded reconciliation-ready count."), Settlement.SettlementSummaryText.Contains(TEXT("reconciliationReadyEntryCount=0"))); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RADProviderBackedBillingSettlementFallbackTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AD.ProviderBackedBillingSettlementFallback", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RADProviderBackedBillingSettlementFallbackTest::RunTest( + const FString& Parameters +) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RADTestInternal::MakeSpeechSubsystem( + TEXT("phase6r-ad-provider-session"), + TEXT("http-sidecar") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AD."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + UHyperTwistHttpSpeechClient* HttpSpeechClient = + HyperTwistWhisperCppPhase6RADTestInternal::ResolveHttpSpeechClient(TrainingSubsystem); + TestNotNull(TEXT("The provider-backed speech client must be available for the billing-settlement fallback test."), HttpSpeechClient); + if (HttpSpeechClient == nullptr) + { + return false; + } + + HttpSpeechClient->ServiceBaseUrl.Reset(); + + FString OpenError; + TestFalse(TEXT("The provider-backed billing-settlement path must fail cleanly when no provider endpoint is configured."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestEqual(TEXT("The provider-backed billing-settlement path must report the missing service base URL."), OpenError, TEXT("service-base-url-missing")); + + const FHyperTwistTrainingCompanionSpeechSessionState SessionState = + TrainingSubsystem->GetActiveCompanionSpeechSessionState(); + const FHyperTwistSpeechProviderBillingSettlementShellState& Settlement = + SessionState.ProviderBillingSettlementShellState; + + TestTrue(TEXT("The provider-backed billing-settlement fallback must remain structurally valid."), Settlement.IsStructurallyValid()); + TestEqual(TEXT("The fallback billing-settlement shell must preserve the sidecar profile id."), Settlement.ActiveProviderProfileId, TEXT("speech-provider/local-http-sidecar-profile-v1")); + TestEqual(TEXT("The fallback billing-settlement shell must preserve the fallback source kind."), Settlement.LatestSourceKind, TEXT("receipt-review-fallback-health")); + TestEqual(TEXT("The fallback billing-settlement shell must preserve one settlement entry."), Settlement.SettlementEntryCount, 1); + TestEqual(TEXT("The fallback billing-settlement shell must surface one active issue."), Settlement.ActiveIssueCount, 1); + TestEqual(TEXT("The fallback billing-settlement shell must surface the route-degraded issue kind."), Settlement.ActiveIssue.IssueKind, TEXT("route-degraded")); + TestTrue(TEXT("The fallback billing-settlement shell must still expose summary readiness for bounded inspection output."), Settlement.bSettlementSummaryReady); + TestFalse(TEXT("The fallback billing-settlement shell must preserve invoice reconciliation as not ready."), Settlement.bInvoiceReconciliationReady); + TestTrue(TEXT("The fallback billing-settlement summary text must preserve the missing-endpoint reason."), Settlement.SettlementSummaryText.Contains(TEXT("service-base-url-missing"))); + return true; +} + +#endif diff --git a/docs/REPO_LICENSE_TRACKING.md b/docs/REPO_LICENSE_TRACKING.md index ee95ba4..23de480 100644 --- a/docs/REPO_LICENSE_TRACKING.md +++ b/docs/REPO_LICENSE_TRACKING.md @@ -203,8 +203,12 @@ Status update on `2026-05-21`: inspection control pass is now consumed - the bounded first-party `Phase 6R-AC` provider receipt review and posted-charge inspection packet is now landed in current code -- the current next bounded move is a source-backed `Phase 6R-AD` first-party provider billing - settlement and invoice reconciliation preparation/control pass, not a new restrictive packet by +- the generic source-backed `Phase 6R-AD` first-party provider billing settlement and invoice + reconciliation control pass is now consumed +- the bounded first-party `Phase 6R-AD` provider billing settlement and invoice reconciliation + packet is now landed in current code +- the current next bounded move is a source-backed `Phase 6R-AE` first-party provider settlement + exception and external-portal handoff preparation/control pass, not a new restrictive packet by default - use the repo-row README census, the portfolio standing refresh backfill, and the `2R-A` ownership contract for the current queue after that correction @@ -354,8 +358,8 @@ Current practical interpretation: - the landed `PostHog/posthog` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_D_POSTHOG_CONTROL_PLANE_IMPLEMENTATION_2026-05-13.md` - the landed `screenpipe/screenpipe` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_E_SCREENPIPE_CAPTURE_HISTORY_IMPLEMENTATION_2026-05-13.md` - the landed `remotion-dev/remotion` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_F_REMOTION_MEDIA_EXPORT_IMPLEMENTATION_2026-05-13.md` -- the next bounded move is a source-backed `Phase 6R-AD` first-party provider billing settlement - and invoice reconciliation preparation/control pass +- the next bounded move is a source-backed `Phase 6R-AE` first-party provider settlement + exception and external-portal handoff preparation/control pass Companion docs: @@ -1621,11 +1625,15 @@ Approved working posture: - the next bounded first-party implementation slice is now landed as normalized usage/cost event-model metadata above the existing transcript-session, shell, payload-custody, provider-custody, and provider-routing seams +- the next bounded first-party implementation slice is now landed as provider billing settlement + and invoice reconciliation shell metadata above the existing transcript-session, shell, + payload-custody, provider-custody, provider-routing, usage/cost, history/export, and + receipt-review seams - the top-level provider/session contract remains first-party HyperTwist-owned and provider-neutral; this row does not own that lane -- keep operator-facing provider dashboards, real device-permission workflow, native - capture-route ownership, actual payload shipping, and future voice-asset review separate from - the code-license judgment +- keep provider settlement exception/handoff, real device-permission workflow, native + capture-route ownership, actual payment execution, provider-portal ownership, actual payload + shipping, and future voice-asset review separate from the code-license judgment - treat `SYSTRAN/faster-whisper` as the landed complementary Python-orchestration donor rather than widening `whisper.cpp` into the runtime core diff --git a/docs/arch/HYPERTWIST_PHASE6R_AD_FIRST_PARTY_PROVIDER_BILLING_SETTLEMENT_AND_INVOICE_RECONCILIATION_IMPLEMENTATION_PACKET_2026-05-25.md b/docs/arch/HYPERTWIST_PHASE6R_AD_FIRST_PARTY_PROVIDER_BILLING_SETTLEMENT_AND_INVOICE_RECONCILIATION_IMPLEMENTATION_PACKET_2026-05-25.md new file mode 100644 index 0000000..b0180a2 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_AD_FIRST_PARTY_PROVIDER_BILLING_SETTLEMENT_AND_INVOICE_RECONCILIATION_IMPLEMENTATION_PACKET_2026-05-25.md @@ -0,0 +1,177 @@ +# HyperTwist Phase 6R-AD first-party provider billing settlement and invoice reconciliation implementation packet + +Created on `2026-05-25` + +## Status + +- first-party HyperTwist packet +- bounded `Phase 6R-AD` implementation slice + +## Purpose + +This packet lands the next bounded first-party slice above the landed speech +session, microphone-shell, payload-custody, provider-custody, +provider-routing, normalized usage/cost event, operator-facing +usage/cost dashboard, provider usage/cost history/export, and provider +receipt-review seams. + +The landed slice is: + +- first-party provider billing settlement and invoice reconciliation shell + +It is not: + +- an actual payment execution packet +- a provider-portal ownership packet +- an external billing settlement packet +- an external invoice remittance packet +- an actual payload-shipping packet +- a real device-permission workflow packet +- a native audio-device route ownership packet +- a broad assistant-platform packet + +## Current authority basis + +This implementation packet stands on: + +- `docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md` +- `docs/arch/HYPERTWIST_PHASE6R_AD_FIRST_PARTY_PROVIDER_BILLING_SETTLEMENT_AND_INVOICE_RECONCILIATION_PREPARATION_PACKET_2026-05-25.md` +- `docs/arch/HYPERTWIST_PHASE6R_AC_FIRST_PARTY_PROVIDER_RECEIPT_REVIEW_AND_POSTED_CHARGE_INSPECTION_IMPLEMENTATION_PACKET_2026-05-25.md` + +The preserved owners do not change: + +- first-party HyperTwist remains the top-level provider/session owner +- first-party HyperTwist remains the usage/cost governance owner +- first-party HyperTwist remains the settlement and reconciliation shell owner +- `ggml-org/whisper.cpp` remains bounded to offline STT donor seams +- `SYSTRAN/faster-whisper` remains bounded to complementary Python + orchestration/service-lane seams + +## Landed scope + +The current code now owns a bounded settlement seam through: + +- retained recognition contract types for: + - `FHyperTwistSpeechProviderBillingSettlementShellProfile` + - `FHyperTwistSpeechProviderBillingSettlementEntry` + - `FHyperTwistSpeechProviderBillingSettlementIssue` + - `FHyperTwistSpeechProviderBillingSettlementShellState` + - expanded `FHyperTwistSpeechSessionConfig` + - expanded `FHyperTwistTrainingCompanionSpeechSessionState` +- contract-library defaults for: + - settlement shell profile ids and definitions + - settlement panel ids and action bindings + - service-health capability exposure +- active companion session-config, service-health, and derived settlement shell + composition in: + - `UHyperTwistTrainingSubsystem` +- direct speech-client health capability exposure in: + - `UHyperTwistHttpSpeechClient` + - `UHyperTwistMockSpeechClient` +- focused automation coverage in: + - `HyperTwistWhisperCppPhase6RADProviderBillingSettlementShellContractTest.cpp` + +## Why this is still intentionally bounded + +This packet lands the settlement and invoice reconciliation shell family only. + +Still deferred: + +- provider settlement exception and external-portal handoff shell surfaces +- actual payment execution or provider-portal ownership +- actual downloadable model/payload shipping +- real device-permission workflow +- native audio-device route ownership beyond bounded shell posture +- broad assistant-platform scope + +## Validation + +Build validation: + +- `UnrealHyperTwistEditor Win64 Development` + +Focused automation validation: + +- `HyperTwist.Permissive.WhisperCpp.Phase6R.AD` `3/3` + +Regression automation validation: + +- `HyperTwist.Permissive.WhisperCpp.Phase6R.AC` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.AB` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.AA` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.Z` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.Y` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.X` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.W` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.V` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.U` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.E` `3/3` +- `HyperTwist.Permissive.FasterWhisper.Phase6R.F` `3/3` +- `HyperTwist.Permissive.Piper.Phase6R.G` `3/3` +- `HyperTwist.Permissive.Coqui.Phase6R.H` `3/3` +- `HyperTwist.CleanRoom.CubeDesk` `14/14` + +One in-loop `Phase 6R-AA` regression run crashed once while materializing the +existing training catalog fixture, but the immediate isolated rerun passed +cleanly and the retained named regression log/report ended in success. No code +change was required for that transient rerun. + +Non-blocking warnings stayed limited to the existing Unreal headless/editor +noise, the pre-existing plugin dependency warning on `UnrealMCP`, and the +known `http:/speech/health` and `http:/vision/health` hostname-resolution +warnings in provider-backed automation. + +## Queue effect + +This packet consumes the current `Phase 6R-AD` implementation slice. + +`ggml-org/whisper.cpp` remains only partially incorporated: + +- landed now: + - speech transcript session boundary + - live microphone shell boundary + - device-permission and capture-route readiness shell posture + - downloadable model and payload custody boundary + - first-party provider-profile and BYOK custody boundary + - first-party provider routing and workflow-policy boundary + - first-party normalized usage/cost event-model boundary + - first-party operator-facing provider usage/cost dashboard shell boundary + - first-party provider usage/cost history/export shell boundary + - first-party provider receipt review and posted-charge inspection shell + boundary + - first-party provider billing settlement and invoice reconciliation shell + boundary for: + - settlement shell profile ids and definitions + - derived settlement entry and issue/state posture + - companion-session settlement composition + - capability exposure for: + - `providerBillingSettlementShell` + - `providerInvoiceReconciliation` + - `providerSettlementReadinessReview` +- still deferred: + - provider settlement exception and external-portal handoff shell surfaces + - actual payment execution or provider-portal ownership + - actual downloadable model/payload shipping + - real device-permission workflow + - native audio-device route ownership beyond bounded shell posture + - broad assistant-platform scope + +The next clean move is: + +- a source-backed `Phase 6R-AE` first-party provider settlement exception and + external-portal handoff preparation/control pass + +Keep the future sequencing guards visible: + +- first-party HyperTwist + - keep the top-level provider/session contract, routing/policy lane, + usage/cost governance lane, dashboard shell lane, history/export shell + lane, receipt-review shell lane, and settlement shell lane first-party +- `ggml-org/whisper.cpp` + - keep code-license judgments separate from actual model or payload shipping + review in any follow-on packet +- `SYSTRAN/faster-whisper` + - keep the row complementary to the landed provider/session, custody, + routing, usage/cost, dashboard, history/export, receipt-review, and + settlement seams rather than widening it into payment execution or portal + ownership diff --git a/docs/arch/HYPERTWIST_PHASE6R_AD_FIRST_PARTY_PROVIDER_BILLING_SETTLEMENT_AND_INVOICE_RECONCILIATION_PREPARATION_PACKET_2026-05-25.md b/docs/arch/HYPERTWIST_PHASE6R_AD_FIRST_PARTY_PROVIDER_BILLING_SETTLEMENT_AND_INVOICE_RECONCILIATION_PREPARATION_PACKET_2026-05-25.md new file mode 100644 index 0000000..1c332ab --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_AD_FIRST_PARTY_PROVIDER_BILLING_SETTLEMENT_AND_INVOICE_RECONCILIATION_PREPARATION_PACKET_2026-05-25.md @@ -0,0 +1,137 @@ +# HyperTwist Phase 6R-AD first-party provider billing settlement and invoice reconciliation preparation packet + +Created on `2026-05-25` + +## Status + +- first-party HyperTwist packet +- source-backed `Phase 6R-AD` preparation/control slice + +## Purpose + +This packet scopes the next bounded first-party slice above the landed speech +session, microphone-shell, payload-custody, provider-custody, +provider-routing, normalized usage/cost event, operator-facing +usage/cost dashboard, provider usage/cost history/export, and provider +receipt-review seams. + +The granted slice is: + +- first-party provider billing settlement and invoice reconciliation shell only + +It is not: + +- an actual payment execution packet +- a provider-portal ownership packet +- an external billing settlement packet +- an external invoice remittance packet +- an actual payload-shipping packet +- a real device-permission workflow packet +- a native audio-device route ownership packet +- a broad assistant-platform packet + +## Current authority basis + +This control pass stands on: + +- `docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md` +- `docs/arch/HYPERTWIST_PHASE6R_AC_FIRST_PARTY_PROVIDER_RECEIPT_REVIEW_AND_POSTED_CHARGE_INSPECTION_IMPLEMENTATION_PACKET_2026-05-25.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md` + +The preserved owners do not change here: + +- first-party HyperTwist remains the top-level provider/session owner +- first-party HyperTwist remains the usage/cost governance owner +- first-party HyperTwist remains the settlement and reconciliation shell owner +- `ggml-org/whisper.cpp` remains bounded to offline STT donor seams +- `SYSTRAN/faster-whisper` remains bounded to complementary Python + orchestration/service-lane seams + +## Granted family + +The bounded `Phase 6R-AD` slice may land: + +1. first-party settlement shell profile ids and definitions +2. derived settlement entry, issue, and shell-state record shapes +3. session-config and companion-session-state expansion for those shapes +4. bounded capability exposure for: + - `providerBillingSettlementShell` + - `providerInvoiceReconciliation` + - `providerSettlementReadinessReview` +5. focused automation for estimate-only settlement posture and provider-backed + fallback settlement posture + +The packet must stay out of: + +- actual payment execution +- provider-portal ownership +- external settlement ownership +- external invoice-remittance ownership +- actual payload download or redistribution ownership +- real device-permission workflow ownership +- native audio-device route ownership + +## Proposed implementation shape + +Land the narrower first-party boundary through: + +- retained recognition contract types for: + - provider billing settlement shell profile + - provider billing settlement entry + - provider billing settlement issue + - provider billing settlement shell state + - expanded speech session config + - expanded companion speech session state +- contract-library defaults for: + - settlement shell profile ids and definitions + - settlement action bindings and panel ids + - service-health capability exposure +- training-subsystem composition for: + - bounded settlement posture derived from retained receipt-review snapshots + - invoice reconciliation readiness posture + - settlement readiness review posture + - route-degraded and provider-charge-pending issue posture +- direct speech-client health capability exposure for: + - `providerBillingSettlementShell` + - `providerInvoiceReconciliation` + - `providerSettlementReadinessReview` + +## Validation target + +Validate with: + +- Unreal build for `UnrealHyperTwistEditor Win64 Development` +- focused automation: + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AD` +- regressions: + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AC` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AB` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AA` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.Z` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.Y` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.X` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.W` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.V` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.U` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.E` + - `HyperTwist.Permissive.FasterWhisper.Phase6R.F` + - `HyperTwist.Permissive.Piper.Phase6R.G` + - `HyperTwist.Permissive.Coqui.Phase6R.H` + - `HyperTwist.CleanRoom.CubeDesk` + +## Queue effect + +If this packet lands cleanly, the next bounded move becomes: + +- a source-backed `Phase 6R-AE` first-party provider settlement exception and + external-portal handoff preparation/control pass + +Keep the narrow sequencing guards visible: + +- keep settlement and reconciliation shell ownership separate from actual + payment execution +- keep settlement exception handling and external-portal handoff separate from + this packet +- keep actual payload shipping separate from usage/cost governance +- keep real device-permission workflow and native capture-route ownership + outside this packet diff --git a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md index 0a26672..9bc33ff 100644 --- a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md +++ b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md @@ -180,15 +180,19 @@ The next bounded move is now: posted-charge inspection control pass is now consumed 32. the bounded first-party `Phase 6R-AC` provider receipt review and posted-charge inspection packet is now landed in current code -33. the next bounded move is a source-backed `Phase 6R-AD` first-party provider billing - settlement and invoice reconciliation preparation/control pass -34. keep the speech-lane guard visible: +33. the generic source-backed `Phase 6R-AD` first-party provider billing settlement and + invoice reconciliation control pass is now consumed +34. the bounded first-party `Phase 6R-AD` provider billing settlement and invoice + reconciliation packet is now landed in current code +35. the next bounded move is a source-backed `Phase 6R-AE` first-party provider settlement + exception and external-portal handoff preparation/control pass +36. keep the speech-lane guard visible: - keep code-license judgments separate from model, voice, and payload-license review -35. keep the provider-neutral speech-lane guard visible: +37. keep the provider-neutral speech-lane guard visible: - first-party HyperTwist owns the top-level provider/session contract - `whisper.cpp` and `faster-whisper` may win narrower donor slices without inheriting that lane, the provider routing/policy lane, or the usage/cost governance lane -36. keep the `MagicTile` guard visible: +38. keep the `MagicTile` guard visible: - keep broad non-Euclidean interaction or WinForms/OpenTK host-shell ownership closed by default unless a narrower first-party gap is proven above the landed macro-remapping seam diff --git a/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md b/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md index 2678f78..50fe686 100644 --- a/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md +++ b/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md @@ -220,8 +220,10 @@ Current landed ordering status: first-party implementation anchor - provider receipt review and posted-charge inspection now also have a bounded first-party implementation anchor -- provider billing settlement and invoice reconciliation remain the next - bounded first-party gap above the landed receipt-review shell +- provider billing settlement and invoice reconciliation now also have a + bounded first-party implementation anchor +- provider settlement exception and external-portal handoff remain the next + bounded first-party gap above the landed settlement shell - actual payment execution or provider-portal ownership remains later first-party work - provider-specific adapters remain later work diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md index d792f21..6b4918c 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -129,6 +129,7 @@ Do not collapse those three tiers into one undifferentiated "features" voice. | First-party operator-facing provider usage/cost dashboard shell | Implemented now | landed first-party `Phase 6R-AA` | First-party dashboard shell profiles, dashboard issue/state objects, quota escalation banners, route diagnostics, and operator-facing panel/action contracts are now live above the landed normalized usage/cost seam. | | First-party provider usage/cost history/export shell | Implemented now | landed first-party `Phase 6R-AB` | First-party rolling history entries, export issue/state objects, summary export preview/copy posture, and operator-facing panel/action contracts are now live above the landed provider usage/cost dashboard seam. | | First-party provider receipt review and posted-charge inspection shell | Implemented now | landed first-party `Phase 6R-AC` | First-party receipt-review shell profiles, derived receipt-review entries, charge-pending and variance issue posture, and bounded inspection capability exposure are now live above the landed provider usage/cost history/export seam. | +| First-party provider billing settlement and invoice reconciliation shell | Implemented now | landed first-party `Phase 6R-AD` | First-party settlement shell profiles, derived settlement entries, invoice-reconciliation posture, settlement-readiness issue state, and bounded settlement capability exposure are now live above the landed provider receipt-review seam. | | Analytics/reporting surfaces | Implemented now | landed analytics/reporting packets | Reporting is real, but bounded to accepted retained slices. | | Browser/spatial/media adjunct surfaces | Implemented now | landed `three.js`, `react-three-fiber`, `xr`, `model-viewer`, `remotion` packets | These are implemented bounded families, not proof of unlimited browser-shell parity. | @@ -227,6 +228,7 @@ repo. | First-party operator-facing provider usage/cost dashboard shell | Implemented now | landed first-party `Phase 6R-AA` packet | Current bounded dashboard shell profile ids/definitions, dashboard issue/state posture, and capability exposure above the landed normalized usage/cost seam. | | First-party provider usage/cost history/export shell | Implemented now | landed first-party `Phase 6R-AB` packet | Current bounded history/export shell profile ids/definitions, rolling history entry and export issue/state posture, summary export preview/copy behavior, and capability exposure above the landed provider usage/cost dashboard seam. | | First-party provider receipt review and posted-charge inspection shell | Implemented now | landed first-party `Phase 6R-AC` packet | Current bounded receipt-review shell profile ids/definitions, derived receipt-review entry and issue/state posture, and capability exposure above the landed provider usage/cost history/export seam. | +| First-party provider billing settlement and invoice reconciliation shell | Implemented now | landed first-party `Phase 6R-AD` packet | Current bounded settlement shell profile ids/definitions, derived settlement entry and issue/state posture, invoice-reconciliation readiness, and capability exposure above the landed provider receipt-review seam. | | OpenAI-compatible custom endpoint support | Deep-source grounded retained | doctrine-defined first-party target | First-class target is now represented in the landed custody model, but full routing/runtime support is still deferred. | | Provider-specific overlays | Shallow placeholder | future provider-family work only | Keep internal until source-grounded and normalized. | diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md index 09e1a26..5fafd6a 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -179,10 +179,14 @@ Canonical discovery surfaces for roadmap interpretation: inspection control pass is now consumed - the bounded first-party `Phase 6R-AC` provider receipt review and posted-charge inspection packet is now landed in current code -- the current next bounded move is a source-backed `Phase 6R-AD` first-party provider billing - settlement and invoice reconciliation preparation/control pass, while keeping actual payment - execution, real device-permission workflow, native capture-route ownership, and actual payload - shipping separately deferred +- the generic source-backed `Phase 6R-AD` first-party provider billing settlement and invoice + reconciliation control pass is now consumed +- the bounded first-party `Phase 6R-AD` provider billing settlement and invoice reconciliation + packet is now landed in current code +- the current next bounded move is a source-backed `Phase 6R-AE` first-party provider settlement + exception and external-portal handoff preparation/control pass, while keeping actual payment + execution, provider-portal ownership, real device-permission workflow, native capture-route + ownership, and actual payload shipping separately deferred - the repo-row implementation queue is now live from that `Phase 6R-A` entry point rather than waiting on another first-party packet @@ -269,16 +273,16 @@ Current routing truth: - `brianpeiris/RiftSketch` - the v6.3 CSV label `not_live_reference_or_discard_candidate` is not the final state by itself; read the closed `0R-E` packet for the retained-versus-discarded split -The next bounded move is a source-backed `Phase 6R-AD` first-party provider -billing settlement and invoice reconciliation preparation/control pass. +The next bounded move is a source-backed `Phase 6R-AE` first-party provider +settlement exception and external-portal handoff preparation/control pass. Queue interpretation after that control pass: - then continue with the retained repo-row queue - highest current repo-row queue pressure sits in: - - the first-party provider billing settlement and invoice reconciliation - remainder above - the partially landed `whisper.cpp` and `faster-whisper` speech seams + - the first-party provider settlement exception and external-portal handoff + remainder above the partially landed `whisper.cpp` and `faster-whisper` + speech seams - `HactarCE/Hyperspeedcube` is now closed for the currently justified retained row: - landed: - puzzle catalog contract @@ -371,6 +375,8 @@ Queue interpretation after that control pass: - first-party provider usage/cost history/export shell boundary - first-party provider receipt review and posted-charge inspection shell boundary + - first-party provider billing settlement and invoice reconciliation shell + boundary - bounded shell capability exposure for: - microphone capture shell - device-permission shell @@ -391,8 +397,11 @@ Queue interpretation after that control pass: - provider receipt review shell - provider posted-charge inspection - provider charge-variance review + - provider billing settlement shell + - provider invoice reconciliation + - provider settlement-readiness review - still deferred: - - provider billing settlement and invoice reconciliation shell surfaces + - provider settlement exception and external-portal handoff shell surfaces - actual payment execution or provider-portal ownership - real device-permission workflow - native capture-route ownership beyond bounded shell posture @@ -441,12 +450,12 @@ Queue interpretation after that control pass: - viseme / gesture runtime integration - broad assistant-platform scope - the next queue shape is now: - - first-party provider billing settlement and invoice reconciliation shell + - first-party provider settlement exception and external-portal handoff assessment above the landed speech session, shell, payload-custody, provider-custody, provider-routing, normalized usage/cost, operator-facing - dashboard, history/export, and receipt-review seams + dashboard, history/export, receipt-review, and settlement seams - the next bounded move should stay narrow: - - a source-backed `Phase 6R-AD` control pass before any widening into actual + - a source-backed `Phase 6R-AE` control pass before any widening into actual payment execution, provider-portal ownership, actual payload shipping, real device-permission workflow, broad voice-output ownership, or broad assistant-platform scope