Implement Phase 6R-AE provider settlement exception shell

This commit is contained in:
axiomlogicnexus 2026-05-25 22:53:38 +02:00
parent f488d2571f
commit a7e69c7f4d
14 changed files with 1727 additions and 35 deletions

View file

@ -1148,6 +1148,69 @@ namespace HyperTwistContractLibraryInternal
return Profile;
}
FHyperTwistSpeechProviderSettlementExceptionShellProfile
MakeDefaultSpeechProviderSettlementExceptionShellProfile(const FString& ProfileId)
{
FHyperTwistSpeechProviderSettlementExceptionShellProfile Profile;
Profile.SettlementExceptionShellProfileId = ProfileId;
Profile.ShellKind = TEXT("operator-provider-settlement-exception");
Profile.ExceptionSurfaceMode = TEXT("settlement-exception-triage");
Profile.ExternalPortalHandoffMode = TEXT("reference-only-provider-portal-handoff");
Profile.RefreshActionId = TEXT("refresh-provider-settlement-exceptions");
Profile.ExceptionInspectActionId = TEXT("inspect-provider-settlement-exception");
Profile.ExternalPortalHandoffActionId =
TEXT("review-provider-external-portal-handoff");
Profile.RouteInspectActionId = TEXT("inspect-provider-route");
Profile.BudgetInspectActionId = TEXT("review-provider-budget");
Profile.RetainedExceptionEntryLimit = 12;
Profile.bSupportsExceptionTimeline = true;
Profile.bSupportsExternalPortalHandoff = true;
Profile.bSupportsSettlementExceptionReview = true;
Profile.bSupportsPendingChargeExceptions = true;
Profile.bPreservesFirstPartyHandoffOwnership = 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-exception-timeline-card"), TEXT("settlement-exception-timeline-card"), TEXT("top-left"), true);
AddPanel(TEXT("external-portal-handoff-card"), TEXT("external-portal-handoff-card"), TEXT("top-right"), true);
AddPanel(TEXT("exception-summary-card"), TEXT("exception-summary-card"), TEXT("left-stack-below-settlement"), true);
AddPanel(TEXT("exception-readiness-card"), TEXT("exception-readiness-card"), TEXT("right-stack-below-handoff"), true);
AddAction(TEXT("refresh-provider-settlement-exceptions"), TEXT("F9"), TEXT("settlement-exception-timeline-card"));
AddAction(TEXT("inspect-provider-settlement-exception"), TEXT("E"), TEXT("settlement-exception-timeline-card"));
AddAction(TEXT("review-provider-external-portal-handoff"), TEXT("H"), TEXT("external-portal-handoff-card"));
AddAction(TEXT("inspect-provider-route"), TEXT("P"), TEXT("exception-summary-card"));
AddAction(TEXT("review-provider-budget"), TEXT("B"), TEXT("exception-readiness-card"));
AddAction(TEXT("close-speech-session"), TEXT("Escape"), TEXT("session-root"));
return Profile;
}
FHyperTwistSpeechModelPayloadDescriptor MakeSpeechModelPayloadDescriptor(
const TCHAR* PayloadId,
const TCHAR* PayloadKind,
@ -1238,7 +1301,10 @@ namespace HyperTwistContractLibraryInternal
TEXT("providerChargeVarianceReview"),
TEXT("providerBillingSettlementShell"),
TEXT("providerInvoiceReconciliation"),
TEXT("providerSettlementReadinessReview")
TEXT("providerSettlementReadinessReview"),
TEXT("providerSettlementExceptionShell"),
TEXT("providerExternalPortalHandoff"),
TEXT("providerSettlementExceptionReview")
};
}
@ -2482,6 +2548,12 @@ FHyperTwistSpeechSessionConfig UHyperTwistContractLibrary::MakeSampleSpeechSessi
HyperTwistContractLibraryInternal::MakeDefaultSpeechProviderBillingSettlementShellProfile(
Config.ProviderBillingSettlementShellProfileId
);
Config.ProviderSettlementExceptionShellProfileId =
TEXT("speech-provider-settlement-exception-shell-v1");
Config.ProviderSettlementExceptionShellProfileDefinition =
HyperTwistContractLibraryInternal::MakeDefaultSpeechProviderSettlementExceptionShellProfile(
Config.ProviderSettlementExceptionShellProfileId
);
Config.PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1");
Config.PayloadCustodyProfileDefinition =
HyperTwistContractLibraryInternal::MakeDefaultSpeechPayloadCustodyProfile(
@ -2601,7 +2673,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistContractLibrary::MakeMockSpeechService
TEXT("providerChargeVarianceReview"),
TEXT("providerBillingSettlementShell"),
TEXT("providerInvoiceReconciliation"),
TEXT("providerSettlementReadinessReview")
TEXT("providerSettlementReadinessReview"),
TEXT("providerSettlementExceptionShell"),
TEXT("providerExternalPortalHandoff"),
TEXT("providerSettlementExceptionReview")
};
Health.SupportedOrchestrationProfiles = {
TEXT("python-batch-transcribe-v1")

View file

@ -353,7 +353,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal
TEXT("providerChargeVarianceReview"),
TEXT("providerBillingSettlementShell"),
TEXT("providerInvoiceReconciliation"),
TEXT("providerSettlementReadinessReview")
TEXT("providerSettlementReadinessReview"),
TEXT("providerSettlementExceptionShell"),
TEXT("providerExternalPortalHandoff"),
TEXT("providerSettlementExceptionReview")
};
Health.SupportedOrchestrationProfiles = {
TEXT("python-batch-transcribe-v1")
@ -439,7 +442,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal
TEXT("providerChargeVarianceReview"),
TEXT("providerBillingSettlementShell"),
TEXT("providerInvoiceReconciliation"),
TEXT("providerSettlementReadinessReview")
TEXT("providerSettlementReadinessReview"),
TEXT("providerSettlementExceptionShell"),
TEXT("providerExternalPortalHandoff"),
TEXT("providerSettlementExceptionReview")
};
Health.SupportedOrchestrationProfiles = {
TEXT("python-batch-transcribe-v1")
@ -510,6 +516,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal
Health.Capabilities.AddUnique(TEXT("providerBillingSettlementShell"));
Health.Capabilities.AddUnique(TEXT("providerInvoiceReconciliation"));
Health.Capabilities.AddUnique(TEXT("providerSettlementReadinessReview"));
Health.Capabilities.AddUnique(TEXT("providerSettlementExceptionShell"));
Health.Capabilities.AddUnique(TEXT("providerExternalPortalHandoff"));
Health.Capabilities.AddUnique(TEXT("providerSettlementExceptionReview"));
Health.SupportedOrchestrationProfiles.AddUnique(TEXT("python-batch-transcribe-v1"));
if (Health.ProviderProfileId.IsEmpty())
{

View file

@ -158,6 +158,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistMockSpeechClient::GetSpeechServiceHeal
Health.Capabilities.AddUnique(TEXT("providerBillingSettlementShell"));
Health.Capabilities.AddUnique(TEXT("providerInvoiceReconciliation"));
Health.Capabilities.AddUnique(TEXT("providerSettlementReadinessReview"));
Health.Capabilities.AddUnique(TEXT("providerSettlementExceptionShell"));
Health.Capabilities.AddUnique(TEXT("providerExternalPortalHandoff"));
Health.Capabilities.AddUnique(TEXT("providerSettlementExceptionReview"));
Health.SupportedOrchestrationProfiles.AddUnique(TEXT("python-batch-transcribe-v1"));
return Health;
}

View file

@ -2936,6 +2936,12 @@ namespace HyperTwistTrainingSubsystemInternal
const FHyperTwistTrainingCompanionSpeechSessionState* PriorState
);
FHyperTwistSpeechProviderSettlementExceptionShellState
BuildSpeechProviderSettlementExceptionShellState(
const FHyperTwistSpeechSessionConfig& SessionConfig,
const FHyperTwistTrainingCompanionSpeechSessionState* PriorState
);
FHyperTwistSpeechMicrophoneShellProfile BuildDefaultCoachCommandMicrophoneShellProfile(const FString& ProfileId)
{
FHyperTwistSpeechMicrophoneShellProfile Profile;
@ -3243,6 +3249,69 @@ namespace HyperTwistTrainingSubsystemInternal
return Profile;
}
FHyperTwistSpeechProviderSettlementExceptionShellProfile
BuildDefaultSpeechProviderSettlementExceptionShellProfile(const FString& ProfileId)
{
FHyperTwistSpeechProviderSettlementExceptionShellProfile Profile;
Profile.SettlementExceptionShellProfileId = ProfileId;
Profile.ShellKind = TEXT("operator-provider-settlement-exception");
Profile.ExceptionSurfaceMode = TEXT("settlement-exception-triage");
Profile.ExternalPortalHandoffMode = TEXT("reference-only-provider-portal-handoff");
Profile.RefreshActionId = TEXT("refresh-provider-settlement-exceptions");
Profile.ExceptionInspectActionId = TEXT("inspect-provider-settlement-exception");
Profile.ExternalPortalHandoffActionId =
TEXT("review-provider-external-portal-handoff");
Profile.RouteInspectActionId = TEXT("inspect-provider-route");
Profile.BudgetInspectActionId = TEXT("review-provider-budget");
Profile.RetainedExceptionEntryLimit = 12;
Profile.bSupportsExceptionTimeline = true;
Profile.bSupportsExternalPortalHandoff = true;
Profile.bSupportsSettlementExceptionReview = true;
Profile.bSupportsPendingChargeExceptions = true;
Profile.bPreservesFirstPartyHandoffOwnership = 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-exception-timeline-card"), TEXT("settlement-exception-timeline-card"), TEXT("top-left"), true);
AddPanel(TEXT("external-portal-handoff-card"), TEXT("external-portal-handoff-card"), TEXT("top-right"), true);
AddPanel(TEXT("exception-summary-card"), TEXT("exception-summary-card"), TEXT("left-stack-below-settlement"), true);
AddPanel(TEXT("exception-readiness-card"), TEXT("exception-readiness-card"), TEXT("right-stack-below-handoff"), true);
AddAction(TEXT("refresh-provider-settlement-exceptions"), TEXT("F9"), TEXT("settlement-exception-timeline-card"));
AddAction(TEXT("inspect-provider-settlement-exception"), TEXT("E"), TEXT("settlement-exception-timeline-card"));
AddAction(TEXT("review-provider-external-portal-handoff"), TEXT("H"), TEXT("external-portal-handoff-card"));
AddAction(TEXT("inspect-provider-route"), TEXT("P"), TEXT("exception-summary-card"));
AddAction(TEXT("review-provider-budget"), TEXT("B"), TEXT("exception-readiness-card"));
AddAction(TEXT("close-speech-session"), TEXT("Escape"), TEXT("session-root"));
return Profile;
}
FHyperTwistSpeechModelPayloadDescriptor MakeSpeechModelPayloadDescriptor(
const TCHAR* PayloadId,
const TCHAR* PayloadKind,
@ -3333,7 +3402,10 @@ namespace HyperTwistTrainingSubsystemInternal
TEXT("providerChargeVarianceReview"),
TEXT("providerBillingSettlementShell"),
TEXT("providerInvoiceReconciliation"),
TEXT("providerSettlementReadinessReview")
TEXT("providerSettlementReadinessReview"),
TEXT("providerSettlementExceptionShell"),
TEXT("providerExternalPortalHandoff"),
TEXT("providerSettlementExceptionReview")
};
}
@ -4048,6 +4120,28 @@ namespace HyperTwistTrainingSubsystemInternal
}
SessionConfig.ProviderBillingSettlementShellProfileId =
SessionConfig.ProviderBillingSettlementShellProfileDefinition.BillingSettlementShellProfileId;
if (SessionConfig.ProviderSettlementExceptionShellProfileId.IsEmpty()
&& SessionConfig.ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid())
{
SessionConfig.ProviderSettlementExceptionShellProfileId =
SessionConfig.ProviderSettlementExceptionShellProfileDefinition
.SettlementExceptionShellProfileId;
}
if (SessionConfig.ProviderSettlementExceptionShellProfileId.IsEmpty())
{
SessionConfig.ProviderSettlementExceptionShellProfileId =
TEXT("speech-provider-settlement-exception-shell-v1");
}
if (!SessionConfig.ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid())
{
SessionConfig.ProviderSettlementExceptionShellProfileDefinition =
BuildDefaultSpeechProviderSettlementExceptionShellProfile(
SessionConfig.ProviderSettlementExceptionShellProfileId
);
}
SessionConfig.ProviderSettlementExceptionShellProfileId =
SessionConfig.ProviderSettlementExceptionShellProfileDefinition
.SettlementExceptionShellProfileId;
}
void ApplySpeechProviderProfileHealthDefaults(
@ -4389,6 +4483,9 @@ namespace HyperTwistTrainingSubsystemInternal
Health.Capabilities.AddUnique(TEXT("providerBillingSettlementShell"));
Health.Capabilities.AddUnique(TEXT("providerInvoiceReconciliation"));
Health.Capabilities.AddUnique(TEXT("providerSettlementReadinessReview"));
Health.Capabilities.AddUnique(TEXT("providerSettlementExceptionShell"));
Health.Capabilities.AddUnique(TEXT("providerExternalPortalHandoff"));
Health.Capabilities.AddUnique(TEXT("providerSettlementExceptionReview"));
}
bool IsSpeechPermissionError(const FString& ErrorText)
@ -5917,6 +6014,402 @@ namespace HyperTwistTrainingSubsystemInternal
}
}
FString BuildSpeechProviderSettlementExceptionSourceKind(
const FHyperTwistSpeechProviderBillingSettlementEntry& SettlementEntry
)
{
if (SettlementEntry.SourceKind.Contains(TEXT("fallback"), ESearchCase::IgnoreCase))
{
return TEXT("settlement-fallback-health");
}
if (!SettlementEntry.bHasProviderPostedCharge)
{
return TEXT("settlement-derived-pending-charge");
}
if (SettlementEntry.bRequiresManualReview)
{
return TEXT("settlement-derived-variance-review");
}
if (SettlementEntry.bSettlementBlocked)
{
return TEXT("settlement-derived-blocked");
}
return TEXT("settlement-derived-ready");
}
FHyperTwistSpeechProviderSettlementExceptionEntry
BuildSpeechProviderSettlementExceptionEntry(
const FHyperTwistSpeechProviderBillingSettlementEntry& SettlementEntry,
const FString& SelectedSettlementWindowId
)
{
FHyperTwistSpeechProviderSettlementExceptionEntry Entry;
Entry.ExceptionEntryId = FString::Printf(
TEXT("speech-provider-settlement-exception/%s"),
*SettlementEntry.SettlementEntryId
);
Entry.SourceKind = BuildSpeechProviderSettlementExceptionSourceKind(SettlementEntry);
Entry.SettlementEntryId = SettlementEntry.SettlementEntryId;
Entry.ReceiptEntryId = SettlementEntry.ReceiptEntryId;
Entry.HistoryEntryId = SettlementEntry.HistoryEntryId;
Entry.ProviderProfileId = SettlementEntry.ProviderProfileId;
Entry.ServiceLaneId = SettlementEntry.ServiceLaneId;
Entry.CapturedAtUtc = SettlementEntry.CapturedAtUtc;
Entry.EstimatedCostUsd = SettlementEntry.EstimatedCostUsd;
Entry.ReportedCostUsd = SettlementEntry.ReportedCostUsd;
Entry.SettlementDeltaUsd = SettlementEntry.SettlementDeltaUsd;
Entry.bRequiresManualReview = SettlementEntry.bRequiresManualReview;
Entry.bSettlementBlocked = SettlementEntry.bSettlementBlocked;
Entry.HandoffStateId = TEXT("handoff-not-required");
if (Entry.SourceKind.Equals(TEXT("settlement-fallback-health"), ESearchCase::IgnoreCase))
{
Entry.ExceptionKindId = TEXT("route-degraded");
Entry.HandoffStateId = TEXT("handoff-ready-for-route-escalation");
Entry.bExternalPortalHandoffReady = true;
Entry.bRequiresOperatorAttention = true;
}
else if (!SettlementEntry.bHasProviderPostedCharge)
{
Entry.ExceptionKindId = TEXT("provider-charge-pending");
Entry.HandoffStateId = TEXT("handoff-deferred-until-provider-posted-charge");
Entry.bExternalPortalHandoffReady = false;
Entry.bRequiresOperatorAttention = false;
}
else if (SettlementEntry.bRequiresManualReview)
{
Entry.ExceptionKindId = TEXT("settlement-variance-review-required");
Entry.HandoffStateId = TEXT("handoff-ready-for-provider-reference");
Entry.bExternalPortalHandoffReady = true;
Entry.bRequiresOperatorAttention = true;
}
else
{
Entry.ExceptionKindId = TEXT("settlement-blocked");
Entry.HandoffStateId = TEXT("handoff-ready-for-first-party-review");
Entry.bExternalPortalHandoffReady = true;
Entry.bRequiresOperatorAttention = SettlementEntry.bSettlementBlocked;
}
if (!SelectedSettlementWindowId.IsEmpty())
{
Entry.HandoffTargetId = TEXT("external-provider-portal");
}
return Entry;
}
void AddSpeechProviderSettlementExceptionIssue(
FHyperTwistSpeechProviderSettlementExceptionShellState& ShellState,
const FHyperTwistSpeechProviderSettlementExceptionIssue& Issue
)
{
if (!Issue.IsStructurallyValid())
{
return;
}
ShellState.Issues.Add(Issue);
if (!ShellState.ActiveIssue.IsStructurallyValid())
{
ShellState.ActiveIssue = Issue;
}
}
void ApplySpeechProviderSettlementExceptionPosture(
const FHyperTwistSpeechProviderSettlementExceptionShellProfile& Profile,
const FHyperTwistTrainingCompanionSpeechSessionState& SessionState,
FHyperTwistSpeechProviderSettlementExceptionShellState& ShellState
)
{
ShellState.ActiveProviderProfileId =
!SessionState.ProviderBillingSettlementShellState.ActiveProviderProfileId.IsEmpty()
? SessionState.ProviderBillingSettlementShellState.ActiveProviderProfileId
: (!SessionState.SessionConfig.ProviderProfileDefinition.ProviderProfileId.IsEmpty()
? SessionState.SessionConfig.ProviderProfileDefinition.ProviderProfileId
: TEXT("speech-provider/local-http-sidecar-profile-v1"));
ShellState.ActiveProviderDisplayLabel =
!SessionState.ProviderBillingSettlementShellState.ActiveProviderDisplayLabel.IsEmpty()
? SessionState.ProviderBillingSettlementShellState.ActiveProviderDisplayLabel
: (!SessionState.SessionConfig.ProviderProfileDefinition.DisplayLabel.IsEmpty()
? SessionState.SessionConfig.ProviderProfileDefinition.DisplayLabel
: TEXT("Local HTTP Speech Sidecar"));
ShellState.ActiveServiceLaneId =
!SessionState.ProviderBillingSettlementShellState.ActiveServiceLaneId.IsEmpty()
? SessionState.ProviderBillingSettlementShellState.ActiveServiceLaneId
: (!SessionState.SessionConfig.OrchestrationProfile.ServiceLaneId.IsEmpty()
? SessionState.SessionConfig.OrchestrationProfile.ServiceLaneId
: TEXT("speech/python-service"));
ShellState.SelectedSettlementWindowId =
!SessionState.ProviderBillingSettlementShellState.SelectedSettlementWindowId.IsEmpty()
? SessionState.ProviderBillingSettlementShellState.SelectedSettlementWindowId
: TEXT("current-session");
ShellState.ExceptionSurfaceModeId = Profile.ExceptionSurfaceMode;
ShellState.ExternalPortalHandoffModeId = Profile.ExternalPortalHandoffMode;
ShellState.bExceptionTimelineVisible = Profile.bSupportsExceptionTimeline;
ShellState.bExternalPortalHandoffVisible = Profile.bSupportsExternalPortalHandoff;
ShellState.bExceptionReviewVisible = Profile.bSupportsSettlementExceptionReview;
ShellState.ExceptionEntries.Reset();
ShellState.Issues.Reset();
ShellState.ActiveIssue = FHyperTwistSpeechProviderSettlementExceptionIssue();
ShellState.ExceptionEntryCount = 0;
ShellState.ExternalPortalHandoffReadyEntryCount = 0;
ShellState.PendingChargeEntryCount = 0;
ShellState.ManualReviewEntryCount = 0;
ShellState.RouteDegradedEntryCount = 0;
ShellState.BlockedSettlementEntryCount = 0;
ShellState.TotalEstimatedCostUsd = 0.0f;
ShellState.TotalReportedCostUsd = 0.0f;
ShellState.TotalSettlementDeltaUsd = 0.0f;
ShellState.HighestSettlementDeltaUsd = 0.0f;
ShellState.LatestExceptionEntryId.Reset();
ShellState.bExceptionSummaryReady = false;
ShellState.bExternalPortalHandoffReady = false;
ShellState.bSettlementExceptionReviewReady = false;
const FHyperTwistSpeechProviderBillingSettlementShellState& SettlementState =
SessionState.ProviderBillingSettlementShellState;
if (!SettlementState.IsStructurallyValid() || SettlementState.SettlementEntries.Num() <= 0)
{
FHyperTwistSpeechProviderSettlementExceptionIssue Issue;
Issue.IssueId = TEXT("provider-settlement-exception-settlement-missing");
Issue.IssueKind = TEXT("no-settlement-review");
Issue.StatusLine = TEXT("Provider settlement exceptions are waiting for settlement review.");
Issue.DetailLine = TEXT("Refresh the retained provider billing-settlement shell before settlement-exception posture begins.");
Issue.RecommendedActionId = Profile.RefreshActionId;
Issue.bRequiresOperatorAttention = false;
AddSpeechProviderSettlementExceptionIssue(ShellState, Issue);
ShellState.StatusLine = Issue.StatusLine;
ShellState.DetailLine = Issue.DetailLine;
ShellState.ActiveIssueCount = ShellState.Issues.Num();
return;
}
const int32 ExceptionLimit = FMath::Max(1, Profile.RetainedExceptionEntryLimit);
const int32 StartIndex =
FMath::Max(0, SettlementState.SettlementEntries.Num() - ExceptionLimit);
for (int32 EntryIndex = StartIndex;
EntryIndex < SettlementState.SettlementEntries.Num();
++EntryIndex)
{
const FHyperTwistSpeechProviderBillingSettlementEntry& SettlementEntry =
SettlementState.SettlementEntries[EntryIndex];
if (!SettlementEntry.IsStructurallyValid())
{
continue;
}
const bool bHasException =
SettlementEntry.SourceKind.Contains(TEXT("fallback"), ESearchCase::IgnoreCase)
|| !SettlementEntry.bHasProviderPostedCharge
|| SettlementEntry.bRequiresManualReview
|| SettlementEntry.bSettlementBlocked;
if (!bHasException)
{
continue;
}
const FHyperTwistSpeechProviderSettlementExceptionEntry Entry =
BuildSpeechProviderSettlementExceptionEntry(
SettlementEntry,
ShellState.SelectedSettlementWindowId
);
ShellState.ExceptionEntries.Add(Entry);
ShellState.LatestExceptionEntryId = Entry.ExceptionEntryId;
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.bExternalPortalHandoffReady)
{
++ShellState.ExternalPortalHandoffReadyEntryCount;
}
if (Entry.ExceptionKindId.Equals(TEXT("provider-charge-pending"), ESearchCase::IgnoreCase))
{
++ShellState.PendingChargeEntryCount;
}
if (Entry.bRequiresManualReview)
{
++ShellState.ManualReviewEntryCount;
}
if (Entry.ExceptionKindId.Equals(TEXT("route-degraded"), ESearchCase::IgnoreCase))
{
++ShellState.RouteDegradedEntryCount;
}
if (Entry.bSettlementBlocked)
{
++ShellState.BlockedSettlementEntryCount;
}
}
ShellState.ExceptionEntryCount = ShellState.ExceptionEntries.Num();
ShellState.bExceptionSummaryReady = ShellState.ExceptionEntryCount > 0;
ShellState.bExternalPortalHandoffReady =
Profile.bSupportsExternalPortalHandoff
&& ShellState.ExternalPortalHandoffReadyEntryCount > 0;
ShellState.bSettlementExceptionReviewReady =
Profile.bSupportsSettlementExceptionReview
&& ShellState.ExceptionEntryCount > 0;
if (SettlementState.ActiveIssue.IsStructurallyValid()
&& (SettlementState.ActiveIssue.IssueKind.Equals(TEXT("route-degraded"), ESearchCase::IgnoreCase)
|| SettlementState.ActiveIssue.IssueKind.Equals(TEXT("hard-blocked"), ESearchCase::IgnoreCase)
|| SettlementState.ActiveIssue.IssueKind.Equals(TEXT("rate-limited"), ESearchCase::IgnoreCase)
|| SettlementState.ActiveIssue.IssueKind.Equals(TEXT("no-history"), ESearchCase::IgnoreCase)
|| SettlementState.ActiveIssue.IssueKind.Equals(TEXT("no-receipt-review"), ESearchCase::IgnoreCase)))
{
FHyperTwistSpeechProviderSettlementExceptionIssue Issue;
Issue.IssueId = FString::Printf(
TEXT("provider-settlement-exception-%s"),
*SettlementState.ActiveIssue.IssueKind
);
Issue.IssueKind = SettlementState.ActiveIssue.IssueKind;
Issue.StatusLine = SettlementState.ActiveIssue.StatusLine;
Issue.DetailLine = SettlementState.ActiveIssue.DetailLine;
Issue.RecommendedActionId =
SettlementState.ActiveIssue.IssueKind.Equals(TEXT("route-degraded"), ESearchCase::IgnoreCase)
|| SettlementState.ActiveIssue.IssueKind.Equals(TEXT("hard-blocked"), ESearchCase::IgnoreCase)
|| SettlementState.ActiveIssue.IssueKind.Equals(TEXT("rate-limited"), ESearchCase::IgnoreCase)
? Profile.ExternalPortalHandoffActionId
: Profile.RefreshActionId;
Issue.bRequiresOperatorAttention =
SettlementState.ActiveIssue.bRequiresOperatorAttention;
AddSpeechProviderSettlementExceptionIssue(ShellState, Issue);
}
else if (ShellState.ManualReviewEntryCount > 0)
{
FHyperTwistSpeechProviderSettlementExceptionIssue Issue;
Issue.IssueId = TEXT("provider-settlement-exception-external-handoff-review");
Issue.IssueKind = TEXT("external-handoff-review-recommended");
Issue.StatusLine =
TEXT("Settlement exception review is ready for bounded external handoff.");
Issue.DetailLine = TEXT("At least one retained provider charge still diverges from the first-party settlement expectation, so the next bounded step is reference-only external portal review rather than payment execution.");
Issue.RecommendedActionId = Profile.ExternalPortalHandoffActionId;
Issue.bRequiresOperatorAttention = true;
AddSpeechProviderSettlementExceptionIssue(ShellState, Issue);
}
else if (ShellState.PendingChargeEntryCount > 0)
{
FHyperTwistSpeechProviderSettlementExceptionIssue Issue;
Issue.IssueId = TEXT("provider-settlement-exception-charge-pending");
Issue.IssueKind = TEXT("provider-charge-pending");
Issue.StatusLine =
TEXT("Provider settlement exceptions are waiting for posted charges.");
Issue.DetailLine = TEXT("Settlement-exception posture is bounded and ready, but the retained settlement entries still show estimate-only provider cost posture rather than a posted charge.");
Issue.RecommendedActionId = Profile.ExceptionInspectActionId;
Issue.bRequiresOperatorAttention = false;
AddSpeechProviderSettlementExceptionIssue(ShellState, Issue);
}
if (ShellState.ActiveIssue.IsStructurallyValid())
{
ShellState.StatusLine = ShellState.ActiveIssue.StatusLine;
ShellState.DetailLine = ShellState.ActiveIssue.DetailLine;
}
else if (ShellState.ExternalPortalHandoffReadyEntryCount > 0)
{
ShellState.StatusLine =
TEXT("Provider settlement exceptions are ready for bounded external handoff review.");
ShellState.DetailLine = FString::Printf(
TEXT("%d retained settlement exceptions for %s are ready for reference-only provider portal review without widening into payment execution."),
ShellState.ExternalPortalHandoffReadyEntryCount,
*ShellState.ActiveProviderDisplayLabel
);
}
else
{
ShellState.StatusLine =
TEXT("Provider settlement exceptions remain bounded above reconciliation.");
ShellState.DetailLine = FString::Printf(
TEXT("%d retained settlement exceptions are available for %s, but external handoff is not ready yet."),
ShellState.ExceptionEntryCount,
*ShellState.ActiveProviderDisplayLabel
);
}
ShellState.ExceptionSummaryText = FString::Printf(
TEXT("providerProfileId=%s\nserviceLaneId=%s\nexceptionEntryCount=%d\n")
TEXT("externalPortalHandoffReadyEntryCount=%d\npendingChargeEntryCount=%d\n")
TEXT("manualReviewEntryCount=%d\nrouteDegradedEntryCount=%d\n")
TEXT("blockedSettlementEntryCount=%d\ntotalEstimatedCostUsd=%.2f\n")
TEXT("totalReportedCostUsd=%.2f\ntotalSettlementDeltaUsd=%.2f\n")
TEXT("highestSettlementDeltaUsd=%.2f\nlatestExceptionEntryId=%s\n")
TEXT("latestSourceKind=%s\nlastError=%s"),
*ShellState.ActiveProviderProfileId,
*ShellState.ActiveServiceLaneId,
ShellState.ExceptionEntryCount,
ShellState.ExternalPortalHandoffReadyEntryCount,
ShellState.PendingChargeEntryCount,
ShellState.ManualReviewEntryCount,
ShellState.RouteDegradedEntryCount,
ShellState.BlockedSettlementEntryCount,
ShellState.TotalEstimatedCostUsd,
ShellState.TotalReportedCostUsd,
ShellState.TotalSettlementDeltaUsd,
ShellState.HighestSettlementDeltaUsd,
*ShellState.LatestExceptionEntryId,
*ShellState.LatestSourceKind,
*SessionState.LastError
);
ShellState.ActiveIssueCount = ShellState.Issues.Num();
}
void SynchronizeSpeechProviderSettlementExceptionState(
FHyperTwistTrainingCompanionSpeechSessionState& SessionState
)
{
FHyperTwistSpeechSessionConfig EffectiveConfig = SessionState.SessionConfig;
ApplySpeechUsageCostDefaults(EffectiveConfig);
if (!SessionState.ProviderBillingSettlementShellState.IsStructurallyValid())
{
SessionState.ProviderBillingSettlementShellState =
BuildSpeechProviderBillingSettlementShellState(EffectiveConfig, &SessionState);
}
const FHyperTwistSpeechProviderSettlementExceptionShellProfile Profile =
EffectiveConfig.ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid()
? EffectiveConfig.ProviderSettlementExceptionShellProfileDefinition
: BuildDefaultSpeechProviderSettlementExceptionShellProfile(
!EffectiveConfig.ProviderSettlementExceptionShellProfileId.IsEmpty()
? EffectiveConfig.ProviderSettlementExceptionShellProfileId
: TEXT("speech-provider-settlement-exception-shell-v1")
);
if (!SessionState.ProviderSettlementExceptionShellState.IsStructurallyValid())
{
SessionState.ProviderSettlementExceptionShellState =
BuildSpeechProviderSettlementExceptionShellState(EffectiveConfig, &SessionState);
}
FHyperTwistSpeechProviderSettlementExceptionShellState& ShellState =
SessionState.ProviderSettlementExceptionShellState;
ShellState.SettlementExceptionShellProfileId =
Profile.SettlementExceptionShellProfileId;
ShellState.AvailableActionIds.Reset();
for (const FHyperTwistSpeechShellActionBinding& ActionBinding : Profile.ActionBindings)
{
ShellState.AvailableActionIds.AddUnique(ActionBinding.ActionId);
}
ApplySpeechProviderSettlementExceptionPosture(Profile, SessionState, ShellState);
ShellState.AvailableActionIds.AddUnique(Profile.RefreshActionId);
ShellState.AvailableActionIds.AddUnique(Profile.ExceptionInspectActionId);
ShellState.AvailableActionIds.AddUnique(Profile.ExternalPortalHandoffActionId);
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,
@ -6221,6 +6714,63 @@ namespace HyperTwistTrainingSubsystemInternal
return ShellState;
}
FHyperTwistSpeechProviderSettlementExceptionShellState
BuildSpeechProviderSettlementExceptionShellState(
const FHyperTwistSpeechSessionConfig& SessionConfig,
const FHyperTwistTrainingCompanionSpeechSessionState* PriorState
)
{
const FHyperTwistSpeechProviderSettlementExceptionShellProfile Profile =
SessionConfig.ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid()
? SessionConfig.ProviderSettlementExceptionShellProfileDefinition
: BuildDefaultSpeechProviderSettlementExceptionShellProfile(
!SessionConfig.ProviderSettlementExceptionShellProfileId.IsEmpty()
? SessionConfig.ProviderSettlementExceptionShellProfileId
: TEXT("speech-provider-settlement-exception-shell-v1")
);
FHyperTwistSpeechProviderSettlementExceptionShellState ShellState;
ShellState.SettlementExceptionShellProfileId =
Profile.SettlementExceptionShellProfileId;
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.ExceptionSurfaceModeId = Profile.ExceptionSurfaceMode;
ShellState.ExternalPortalHandoffModeId = Profile.ExternalPortalHandoffMode;
ShellState.bExceptionTimelineVisible = Profile.bSupportsExceptionTimeline;
ShellState.bExternalPortalHandoffVisible = Profile.bSupportsExternalPortalHandoff;
ShellState.bExceptionReviewVisible = Profile.bSupportsSettlementExceptionReview;
for (const FHyperTwistSpeechShellActionBinding& ActionBinding : Profile.ActionBindings)
{
ShellState.AvailableActionIds.AddUnique(ActionBinding.ActionId);
}
if (PriorState != nullptr
&& PriorState->ProviderSettlementExceptionShellState
.SettlementExceptionShellProfileId
== Profile.SettlementExceptionShellProfileId)
{
ShellState = PriorState->ProviderSettlementExceptionShellState;
ShellState.SettlementExceptionShellProfileId =
Profile.SettlementExceptionShellProfileId;
ShellState.ExceptionSurfaceModeId = Profile.ExceptionSurfaceMode;
ShellState.ExternalPortalHandoffModeId = Profile.ExternalPortalHandoffMode;
}
return ShellState;
}
FHyperTwistVisionCorrectionState BuildClassicCubeCorrectionState(
const FHyperTwistVisionSessionConfig& SessionConfig,
const FHyperTwistVisionReconstructionSession& ReconstructionSession,
@ -11605,6 +12155,11 @@ bool UHyperTwistTrainingSubsystem::OpenActiveCompanionSpeechSession(FString& Out
SessionConfig,
nullptr
);
ActiveCompanionSpeechSessionState.ProviderSettlementExceptionShellState =
HyperTwistTrainingSubsystemInternal::BuildSpeechProviderSettlementExceptionShellState(
SessionConfig,
nullptr
);
if (ActiveCompanionSpeechSessionState.bSessionOpen
&& ActiveCompanionSpeechSessionState.ActiveSessionId != SessionConfig.SessionId)
@ -11688,6 +12243,9 @@ FHyperTwistSpeechTranscriptResult UHyperTwistTrainingSubsystem::SubmitActiveComp
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderBillingSettlementState(
ActiveCompanionSpeechSessionState
);
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderSettlementExceptionState(
ActiveCompanionSpeechSessionState
);
return Result;
}
@ -11871,6 +12429,9 @@ FHyperTwistSpeechTranscriptResult UHyperTwistTrainingSubsystem::SubmitActiveComp
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderBillingSettlementState(
ActiveCompanionSpeechSessionState
);
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderSettlementExceptionState(
ActiveCompanionSpeechSessionState
);
return Result;
}
@ -12626,6 +13187,9 @@ void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealth()
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderBillingSettlementState(
ActiveCompanionSpeechSessionState
);
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderSettlementExceptionState(
ActiveCompanionSpeechSessionState
);
return;
}
@ -12663,6 +13227,9 @@ void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealth()
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderBillingSettlementState(
ActiveCompanionSpeechSessionState
);
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderSettlementExceptionState(
ActiveCompanionSpeechSessionState
);
}
void UHyperTwistTrainingSubsystem::AppendRecognitionReplayEvent(

View file

@ -4036,6 +4036,403 @@ struct FHyperTwistSpeechProviderBillingSettlementShellState
}
};
USTRUCT(BlueprintType)
struct FHyperTwistSpeechProviderSettlementExceptionShellProfile
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SettlementExceptionShellProfileId =
TEXT("speech-provider-settlement-exception-shell-v1");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ShellKind = TEXT("operator-provider-settlement-exception");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ExceptionSurfaceMode = TEXT("settlement-exception-triage");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ExternalPortalHandoffMode = TEXT("reference-only-provider-portal-handoff");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RefreshActionId = TEXT("refresh-provider-settlement-exceptions");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ExceptionInspectActionId = TEXT("inspect-provider-settlement-exception");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ExternalPortalHandoffActionId =
TEXT("review-provider-external-portal-handoff");
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 RetainedExceptionEntryLimit = 12;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSupportsExceptionTimeline = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSupportsExternalPortalHandoff = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSupportsSettlementExceptionReview = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSupportsPendingChargeExceptions = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPreservesFirstPartyHandoffOwnership = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistSpeechShellPanelLayout> Panels;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistSpeechShellActionBinding> ActionBindings;
bool IsStructurallyValid() const
{
if (SettlementExceptionShellProfileId.IsEmpty()
|| ShellKind.IsEmpty()
|| ExceptionSurfaceMode.IsEmpty()
|| ExternalPortalHandoffMode.IsEmpty()
|| RefreshActionId.IsEmpty()
|| ExceptionInspectActionId.IsEmpty()
|| ExternalPortalHandoffActionId.IsEmpty()
|| RouteInspectActionId.IsEmpty()
|| BudgetInspectActionId.IsEmpty()
|| RetainedExceptionEntryLimit <= 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 FHyperTwistSpeechProviderSettlementExceptionEntry
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ExceptionEntryId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceKind = TEXT("settlement-derived-pending-charge");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SettlementEntryId = TEXT("speech-provider-settlement-entry/current-session");
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 ExceptionKindId = TEXT("provider-charge-pending");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString HandoffTargetId = TEXT("external-provider-portal");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString HandoffStateId = TEXT("handoff-deferred-until-provider-posted-charge");
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")
bool bRequiresManualReview = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bExternalPortalHandoffReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSettlementBlocked = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bRequiresOperatorAttention = false;
bool IsStructurallyValid() const
{
return !ExceptionEntryId.IsEmpty()
&& !SourceKind.IsEmpty()
&& !SettlementEntryId.IsEmpty()
&& !ReceiptEntryId.IsEmpty()
&& !HistoryEntryId.IsEmpty()
&& !ProviderProfileId.IsEmpty()
&& !ServiceLaneId.IsEmpty()
&& !ExceptionKindId.IsEmpty()
&& !HandoffTargetId.IsEmpty()
&& !HandoffStateId.IsEmpty()
&& !CapturedAtUtc.IsEmpty()
&& EstimatedCostUsd >= 0.0f
&& ReportedCostUsd >= 0.0f
&& SettlementDeltaUsd >= 0.0f;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistSpeechProviderSettlementExceptionIssue
{
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 FHyperTwistSpeechProviderSettlementExceptionShellState
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SettlementExceptionShellProfileId;
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 ExceptionSurfaceModeId = TEXT("settlement-exception-triage");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ExternalPortalHandoffModeId = TEXT("reference-only-provider-portal-handoff");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString LatestExceptionEntryId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString LatestSourceKind = TEXT("settlement-derived-pending-charge");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StatusLine = TEXT("Provider settlement exceptions are ready for bounded inspection.");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DetailLine = TEXT("First-party settlement exception posture remains bounded above retained settlement review and only exposes reference handoff surfaces.");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ExceptionSummaryText;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ExceptionEntryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ExternalPortalHandoffReadyEntryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 PendingChargeEntryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ManualReviewEntryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RouteDegradedEntryCount = 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 bExceptionTimelineVisible = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bExternalPortalHandoffVisible = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bExceptionReviewVisible = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bExceptionSummaryReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bExternalPortalHandoffReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSettlementExceptionReviewReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ActiveIssueCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistSpeechProviderSettlementExceptionIssue ActiveIssue;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistSpeechProviderSettlementExceptionIssue> Issues;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistSpeechProviderSettlementExceptionEntry> ExceptionEntries;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> AvailableActionIds;
bool IsStructurallyValid() const
{
if (SettlementExceptionShellProfileId.IsEmpty()
|| ActiveProviderProfileId.IsEmpty()
|| ActiveProviderDisplayLabel.IsEmpty()
|| ActiveServiceLaneId.IsEmpty()
|| SelectedSettlementWindowId.IsEmpty()
|| ExceptionSurfaceModeId.IsEmpty()
|| ExternalPortalHandoffModeId.IsEmpty()
|| LatestSourceKind.IsEmpty()
|| StatusLine.IsEmpty()
|| DetailLine.IsEmpty()
|| ExceptionEntryCount < 0
|| ExternalPortalHandoffReadyEntryCount < 0
|| PendingChargeEntryCount < 0
|| ManualReviewEntryCount < 0
|| RouteDegradedEntryCount < 0
|| BlockedSettlementEntryCount < 0
|| TotalEstimatedCostUsd < 0.0f
|| TotalReportedCostUsd < 0.0f
|| TotalSettlementDeltaUsd < 0.0f
|| HighestSettlementDeltaUsd < 0.0f
|| ActiveIssueCount < 0
|| AvailableActionIds.Num() <= 0)
{
return false;
}
if (ExceptionEntryCount != ExceptionEntries.Num()
|| ExternalPortalHandoffReadyEntryCount > ExceptionEntryCount
|| PendingChargeEntryCount > ExceptionEntryCount
|| ManualReviewEntryCount > ExceptionEntryCount
|| RouteDegradedEntryCount > ExceptionEntryCount
|| BlockedSettlementEntryCount > ExceptionEntryCount)
{
return false;
}
if (ExceptionEntryCount > 0 && LatestExceptionEntryId.IsEmpty())
{
return false;
}
if (bExceptionSummaryReady && ExceptionSummaryText.IsEmpty())
{
return false;
}
if (ActiveIssueCount != Issues.Num())
{
return false;
}
if (ActiveIssueCount > 0 && !ActiveIssue.IsStructurallyValid())
{
return false;
}
for (const FHyperTwistSpeechProviderSettlementExceptionIssue& Issue : Issues)
{
if (!Issue.IsStructurallyValid())
{
return false;
}
}
for (const FHyperTwistSpeechProviderSettlementExceptionEntry& Entry : ExceptionEntries)
{
if (!Entry.IsStructurallyValid())
{
return false;
}
}
for (const FString& ActionId : AvailableActionIds)
{
if (ActionId.IsEmpty())
{
return false;
}
}
return true;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistSpeechSessionConfig
{
@ -4137,6 +4534,14 @@ struct FHyperTwistSpeechSessionConfig
FHyperTwistSpeechProviderBillingSettlementShellProfile
ProviderBillingSettlementShellProfileDefinition;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ProviderSettlementExceptionShellProfileId =
TEXT("speech-provider-settlement-exception-shell-v1");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistSpeechProviderSettlementExceptionShellProfile
ProviderSettlementExceptionShellProfileDefinition;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1");
@ -4191,6 +4596,8 @@ struct FHyperTwistSpeechSessionConfig
|| !ProviderReceiptReviewShellProfileDefinition.IsStructurallyValid()
|| ProviderBillingSettlementShellProfileId.IsEmpty()
|| !ProviderBillingSettlementShellProfileDefinition.IsStructurallyValid()
|| ProviderSettlementExceptionShellProfileId.IsEmpty()
|| !ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid()
|| PayloadCustodyProfileId.IsEmpty()
|| !PayloadCustodyProfileDefinition.IsStructurallyValid()
|| RequiredModelPayloads.Num() <= 0

View file

@ -1952,6 +1952,9 @@ struct FHyperTwistTrainingCompanionSpeechSessionState
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistSpeechProviderBillingSettlementShellState ProviderBillingSettlementShellState;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistSpeechProviderSettlementExceptionShellState ProviderSettlementExceptionShellState;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistSpeechTranscriptResult LastTranscriptResult;
};

View file

@ -0,0 +1,295 @@
// 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 HyperTwistWhisperCppPhase6RAETestInternal
{
FHyperTwistTrainingDeck MakeSpeechDeck()
{
FHyperTwistTrainingDeck Deck;
Deck.DeckId = TEXT("phase6r-ae/provider-settlement-exception");
Deck.Title = TEXT("Phase 6R-AE Provider Settlement Exception");
Deck.DeliveryModes = {
EHyperTwistTrainingDeliveryMode::CoachReviewed
};
FHyperTwistTrainingCase TrainingCase;
TrainingCase.CaseId = TEXT("phase6r-ae-case");
TrainingCase.PuzzleId = TEXT("cube/3x3x3");
TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Sequence;
TrainingCase.PromptLabel = TEXT("Phase 6R-AE 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<FStrProperty>(
UHyperTwistTrainingSubsystem::StaticClass(),
TEXT("CompanionSpeechClientKind")
))
{
SpeechClientKindProperty->SetPropertyValue_InContainer(TrainingSubsystem, ClientKind);
}
}
UHyperTwistTrainingSubsystem* MakeSpeechSubsystem(const FString& SessionId, const FString& ClientKind)
{
UGameInstance* GameInstance = NewObject<UGameInstance>(GetTransientPackage());
if (GameInstance == nullptr)
{
return nullptr;
}
UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject<UHyperTwistTrainingSubsystem>(GameInstance);
if (TrainingSubsystem == nullptr)
{
return nullptr;
}
ForceSpeechClientKind(TrainingSubsystem, ClientKind);
const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
MakeSpeechDeck(),
TEXT("phase6r-ae-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<FObjectPropertyBase>(
UHyperTwistTrainingSubsystem::StaticClass(),
TEXT("ActiveCompanionSpeechClientObject")
))
{
return Cast<UHyperTwistHttpSpeechClient>(
ClientProperty->GetObjectPropertyValue_InContainer(TrainingSubsystem)
);
}
return nullptr;
}
FHyperTwistSpeechUtteranceEnvelope MakeUtterance()
{
FHyperTwistSpeechUtteranceEnvelope Utterance;
Utterance.AudioRef = TEXT("exception-coach");
Utterance.SpeechStartMs = 120;
Utterance.SpeechEndMs = 840;
Utterance.SilenceGapMs = 240;
return Utterance;
}
bool ContainsPanelId(
const TArray<FHyperTwistSpeechShellPanelLayout>& Panels,
const FString& ExpectedPanelId
)
{
for (const FHyperTwistSpeechShellPanelLayout& Panel : Panels)
{
if (Panel.PanelId == ExpectedPanelId)
{
return true;
}
}
return false;
}
bool ContainsActionId(
const TArray<FHyperTwistSpeechShellActionBinding>& ActionBindings,
const FString& ExpectedActionId
)
{
for (const FHyperTwistSpeechShellActionBinding& ActionBinding : ActionBindings)
{
if (ActionBinding.ActionId == ExpectedActionId)
{
return true;
}
}
return false;
}
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistWhisperCppPhase6RAEProviderSettlementExceptionSessionConfigTest,
"HyperTwist.Permissive.WhisperCpp.Phase6R.AE.ProviderSettlementExceptionSessionConfig",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistWhisperCppPhase6RAEProviderSettlementExceptionSessionConfigTest::RunTest(
const FString& Parameters
)
{
UHyperTwistTrainingSubsystem* TrainingSubsystem =
HyperTwistWhisperCppPhase6RAETestInternal::MakeSpeechSubsystem(
TEXT("phase6r-ae-config-session"),
TEXT("mock")
);
TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AE."), TrainingSubsystem);
if (TrainingSubsystem == nullptr)
{
return false;
}
FString OpenError;
TestTrue(TEXT("The mock settlement-exception route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError));
TestTrue(TEXT("Opening the mock settlement-exception speech session must not report an error."), OpenError.IsEmpty());
const FHyperTwistSpeechSessionConfig& Config =
TrainingSubsystem->GetActiveCompanionSpeechSessionState().SessionConfig;
TestTrue(TEXT("The settlement-exception session config must remain structurally valid."), Config.IsStructurallyValid());
TestEqual(TEXT("The settlement-exception shell profile id must be present."), Config.ProviderSettlementExceptionShellProfileId, TEXT("speech-provider-settlement-exception-shell-v1"));
TestTrue(TEXT("The settlement-exception shell profile must remain structurally valid."), Config.ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid());
TestEqual(TEXT("The settlement-exception shell must preserve the operator-facing shell kind."), Config.ProviderSettlementExceptionShellProfileDefinition.ShellKind, TEXT("operator-provider-settlement-exception"));
TestEqual(TEXT("The settlement-exception shell must preserve the exception inspect action id."), Config.ProviderSettlementExceptionShellProfileDefinition.ExceptionInspectActionId, TEXT("inspect-provider-settlement-exception"));
TestEqual(TEXT("The settlement-exception shell must preserve the external handoff action id."), Config.ProviderSettlementExceptionShellProfileDefinition.ExternalPortalHandoffActionId, TEXT("review-provider-external-portal-handoff"));
TestTrue(TEXT("The settlement-exception shell must include the external handoff panel."), HyperTwistWhisperCppPhase6RAETestInternal::ContainsPanelId(Config.ProviderSettlementExceptionShellProfileDefinition.Panels, TEXT("external-portal-handoff-card")));
TestTrue(TEXT("The settlement-exception shell must include the exception inspect action."), HyperTwistWhisperCppPhase6RAETestInternal::ContainsActionId(Config.ProviderSettlementExceptionShellProfileDefinition.ActionBindings, TEXT("inspect-provider-settlement-exception")));
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistWhisperCppPhase6RAEProviderSettlementExceptionShellStateTest,
"HyperTwist.Permissive.WhisperCpp.Phase6R.AE.ProviderSettlementExceptionShellState",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistWhisperCppPhase6RAEProviderSettlementExceptionShellStateTest::RunTest(
const FString& Parameters
)
{
UHyperTwistTrainingSubsystem* TrainingSubsystem =
HyperTwistWhisperCppPhase6RAETestInternal::MakeSpeechSubsystem(
TEXT("phase6r-ae-shell-session"),
TEXT("mock")
);
TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AE."), TrainingSubsystem);
if (TrainingSubsystem == nullptr)
{
return false;
}
FString OpenError;
TestTrue(TEXT("The mock settlement-exception route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError));
TestTrue(TEXT("Opening the mock settlement-exception speech session must not report an error."), OpenError.IsEmpty());
const FHyperTwistSpeechTranscriptResult Result =
TrainingSubsystem->SubmitActiveCompanionSpeechUtterance(
HyperTwistWhisperCppPhase6RAETestInternal::MakeUtterance()
);
TestTrue(TEXT("The mock settlement-exception utterance must produce a final transcript."), Result.bIsFinal);
TestTrue(TEXT("The mock settlement-exception utterance must remain structurally valid."), Result.IsStructurallyValid());
const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
TrainingSubsystem->GetActiveCompanionSpeechSessionState();
const FHyperTwistSpeechServiceHealth& Health = SessionState.ServiceHealth;
const FHyperTwistSpeechProviderSettlementExceptionShellState& ExceptionState =
SessionState.ProviderSettlementExceptionShellState;
TestTrue(TEXT("The service health must expose the settlement-exception shell."), Health.Capabilities.Contains(TEXT("providerSettlementExceptionShell")));
TestTrue(TEXT("The service health must expose external portal handoff."), Health.Capabilities.Contains(TEXT("providerExternalPortalHandoff")));
TestTrue(TEXT("The service health must expose settlement-exception review."), Health.Capabilities.Contains(TEXT("providerSettlementExceptionReview")));
TestTrue(TEXT("The settlement-exception shell state must remain structurally valid."), ExceptionState.IsStructurallyValid());
TestEqual(TEXT("The settlement-exception shell must preserve the mock provider profile."), ExceptionState.ActiveProviderProfileId, TEXT("speech-provider/mock-offline-profile-v1"));
TestEqual(TEXT("The settlement-exception shell must retain exactly two bounded exception entries after open and submit."), ExceptionState.ExceptionEntryCount, 2);
TestEqual(TEXT("The settlement-exception shell must preserve the latest source kind."), ExceptionState.LatestSourceKind, TEXT("settlement-derived-pending-charge"));
TestTrue(TEXT("The settlement-exception shell must preserve summary readiness."), ExceptionState.bExceptionSummaryReady);
TestFalse(TEXT("The settlement-exception shell must not mark external handoff ready before provider-posted charges or route exceptions appear."), ExceptionState.bExternalPortalHandoffReady);
TestTrue(TEXT("The settlement-exception shell must preserve exception-review visibility."), ExceptionState.bSettlementExceptionReviewReady);
TestTrue(TEXT("The settlement-exception shell must preserve the external handoff action."), ExceptionState.AvailableActionIds.Contains(TEXT("review-provider-external-portal-handoff")));
TestTrue(TEXT("The settlement-exception shell must preserve the exception inspect action."), ExceptionState.AvailableActionIds.Contains(TEXT("inspect-provider-settlement-exception")));
TestEqual(TEXT("The settlement-exception shell must preserve zero external-handoff-ready entries in the mock route."), ExceptionState.ExternalPortalHandoffReadyEntryCount, 0);
TestEqual(TEXT("The settlement-exception shell must preserve the pending-charge entry count."), ExceptionState.PendingChargeEntryCount, 2);
TestEqual(TEXT("The settlement-exception shell must preserve the manual-review entry count."), ExceptionState.ManualReviewEntryCount, 0);
TestEqual(TEXT("The settlement-exception shell must preserve the blocked-settlement entry count."), ExceptionState.BlockedSettlementEntryCount, 2);
TestTrue(TEXT("The settlement-exception shell must accumulate the mock estimated cost."), FMath::IsNearlyEqual(ExceptionState.TotalEstimatedCostUsd, 0.02f));
TestTrue(TEXT("The settlement-exception shell must preserve zero settlement delta while provider charges are still pending."), FMath::IsNearlyEqual(ExceptionState.TotalSettlementDeltaUsd, 0.0f));
TestEqual(TEXT("The settlement-exception shell must surface the pending provider-charge issue."), ExceptionState.ActiveIssue.IssueKind, TEXT("provider-charge-pending"));
TestTrue(TEXT("The settlement-exception summary text must preserve the bounded pending-charge count."), ExceptionState.ExceptionSummaryText.Contains(TEXT("pendingChargeEntryCount=2")));
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistWhisperCppPhase6RAEProviderBackedSettlementExceptionFallbackTest,
"HyperTwist.Permissive.WhisperCpp.Phase6R.AE.ProviderBackedSettlementExceptionFallback",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistWhisperCppPhase6RAEProviderBackedSettlementExceptionFallbackTest::RunTest(
const FString& Parameters
)
{
UHyperTwistTrainingSubsystem* TrainingSubsystem =
HyperTwistWhisperCppPhase6RAETestInternal::MakeSpeechSubsystem(
TEXT("phase6r-ae-provider-session"),
TEXT("http-sidecar")
);
TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AE."), TrainingSubsystem);
if (TrainingSubsystem == nullptr)
{
return false;
}
UHyperTwistHttpSpeechClient* HttpSpeechClient =
HyperTwistWhisperCppPhase6RAETestInternal::ResolveHttpSpeechClient(TrainingSubsystem);
TestNotNull(TEXT("The provider-backed speech client must be available for the settlement-exception fallback test."), HttpSpeechClient);
if (HttpSpeechClient == nullptr)
{
return false;
}
HttpSpeechClient->ServiceBaseUrl.Reset();
FString OpenError;
TestFalse(TEXT("The provider-backed settlement-exception path must fail cleanly when no provider endpoint is configured."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError));
TestEqual(TEXT("The provider-backed settlement-exception path must report the missing service base URL."), OpenError, TEXT("service-base-url-missing"));
const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
TrainingSubsystem->GetActiveCompanionSpeechSessionState();
const FHyperTwistSpeechProviderSettlementExceptionShellState& ExceptionState =
SessionState.ProviderSettlementExceptionShellState;
TestTrue(TEXT("The provider-backed settlement-exception fallback must remain structurally valid."), ExceptionState.IsStructurallyValid());
TestEqual(TEXT("The fallback settlement-exception shell must preserve the sidecar profile id."), ExceptionState.ActiveProviderProfileId, TEXT("speech-provider/local-http-sidecar-profile-v1"));
TestEqual(TEXT("The fallback settlement-exception shell must preserve the fallback source kind."), ExceptionState.LatestSourceKind, TEXT("settlement-fallback-health"));
TestEqual(TEXT("The fallback settlement-exception shell must preserve one exception entry."), ExceptionState.ExceptionEntryCount, 1);
TestEqual(TEXT("The fallback settlement-exception shell must surface one active issue."), ExceptionState.ActiveIssueCount, 1);
TestEqual(TEXT("The fallback settlement-exception shell must surface the route-degraded issue kind."), ExceptionState.ActiveIssue.IssueKind, TEXT("route-degraded"));
TestTrue(TEXT("The fallback settlement-exception shell must still expose summary readiness for bounded inspection output."), ExceptionState.bExceptionSummaryReady);
TestTrue(TEXT("The fallback settlement-exception shell must mark external handoff ready for route escalation posture."), ExceptionState.bExternalPortalHandoffReady);
TestEqual(TEXT("The fallback settlement-exception shell must preserve one external-handoff-ready entry."), ExceptionState.ExternalPortalHandoffReadyEntryCount, 1);
TestTrue(TEXT("The fallback settlement-exception summary text must preserve the missing-endpoint reason."), ExceptionState.ExceptionSummaryText.Contains(TEXT("service-base-url-missing")));
return true;
}
#endif

View file

@ -207,9 +207,12 @@ Status update on `2026-05-21`:
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
- the generic source-backed `Phase 6R-AE` first-party provider settlement exception and
external-portal handoff control pass is now consumed
- the bounded first-party `Phase 6R-AE` provider settlement exception and external-portal handoff
packet is now landed in current code
- the current next bounded move is a source-backed `Phase 6R-AF` first-party real
device-permission workflow 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
@ -1629,11 +1632,15 @@ Approved working posture:
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 next bounded first-party implementation slice is now landed as provider settlement
exception and external-portal handoff shell metadata above the existing transcript-session,
shell, payload-custody, provider-custody, provider-routing, usage/cost, history/export,
receipt-review, and settlement seams
- the top-level provider/session contract remains first-party HyperTwist-owned and
provider-neutral; this row does not own that lane
- 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
- keep 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

View file

@ -0,0 +1,175 @@
# HyperTwist Phase 6R-AE first-party provider settlement exception and external-portal handoff implementation packet
Created on `2026-05-25`
## Status
- first-party HyperTwist packet
- bounded `Phase 6R-AE` 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, provider
receipt-review, and provider billing-settlement seams.
The landed slice is:
- first-party provider settlement exception and external-portal handoff 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_AE_FIRST_PARTY_PROVIDER_SETTLEMENT_EXCEPTION_AND_EXTERNAL_PORTAL_HANDOFF_PREPARATION_PACKET_2026-05-25.md`
- `docs/arch/HYPERTWIST_PHASE6R_AD_FIRST_PARTY_PROVIDER_BILLING_SETTLEMENT_AND_INVOICE_RECONCILIATION_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 exception and handoff 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-exception seam through:
- retained recognition contract types for:
- `FHyperTwistSpeechProviderSettlementExceptionShellProfile`
- `FHyperTwistSpeechProviderSettlementExceptionEntry`
- `FHyperTwistSpeechProviderSettlementExceptionIssue`
- `FHyperTwistSpeechProviderSettlementExceptionShellState`
- expanded `FHyperTwistSpeechSessionConfig`
- expanded `FHyperTwistTrainingCompanionSpeechSessionState`
- contract-library defaults for:
- exception shell profile ids and definitions
- exception panel ids and action bindings
- service-health capability exposure
- active companion session-config, service-health, and derived settlement-exception
shell composition in:
- `UHyperTwistTrainingSubsystem`
- direct speech-client health capability exposure in:
- `UHyperTwistHttpSpeechClient`
- `UHyperTwistMockSpeechClient`
- focused automation coverage in:
- `HyperTwistWhisperCppPhase6RAEProviderSettlementExceptionShellContractTest.cpp`
## Why this is still intentionally bounded
This packet lands the settlement-exception and external-portal handoff shell
family only.
Still deferred:
- actual payment execution or provider-portal ownership
- real device-permission workflow
- native audio-device route ownership beyond bounded shell posture
- actual downloadable model/payload shipping
- broad assistant-platform scope
## Validation
Build validation:
- `UnrealHyperTwistEditor Win64 Development`
Focused automation validation:
- `HyperTwist.Permissive.WhisperCpp.Phase6R.AE` `3/3`
Regression automation validation:
- `HyperTwist.Permissive.WhisperCpp.Phase6R.AD` `3/3`
- `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`
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-AE` 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
- first-party provider settlement exception and external-portal handoff shell
boundary for:
- exception shell profile ids and definitions
- derived exception entry and issue/state posture
- reference-only external-portal handoff readiness
- capability exposure for:
- `providerSettlementExceptionShell`
- `providerExternalPortalHandoff`
- `providerSettlementExceptionReview`
- still deferred:
- actual payment execution or provider-portal ownership
- real device-permission workflow
- native audio-device route ownership beyond bounded shell posture
- actual downloadable model/payload shipping
- broad assistant-platform scope
The next clean move is:
- a source-backed `Phase 6R-AF` first-party real device-permission workflow
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, settlement shell lane, and
exception/handoff 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,
settlement, and exception/handoff seams rather than widening it into
payment execution or provider-portal ownership

View file

@ -0,0 +1,138 @@
# HyperTwist Phase 6R-AE first-party provider settlement exception and external-portal handoff preparation packet
Created on `2026-05-25`
## Status
- first-party HyperTwist packet
- source-backed `Phase 6R-AE` 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, provider
receipt-review, and provider billing-settlement seams.
The granted slice is:
- first-party provider settlement exception and external-portal handoff 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_AD_FIRST_PARTY_PROVIDER_BILLING_SETTLEMENT_AND_INVOICE_RECONCILIATION_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 exception and handoff 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-AE` slice may land:
1. first-party settlement-exception shell profile ids and definitions
2. derived exception entry, issue, and shell-state record shapes
3. session-config and companion-session-state expansion for those shapes
4. bounded capability exposure for:
- `providerSettlementExceptionShell`
- `providerExternalPortalHandoff`
- `providerSettlementExceptionReview`
5. focused automation for pending-charge exception posture and provider-backed
route-degraded handoff 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 settlement-exception shell profile
- provider settlement-exception entry
- provider settlement-exception issue
- provider settlement-exception shell state
- expanded speech session config
- expanded companion speech session state
- contract-library defaults for:
- exception shell profile ids and definitions
- exception action bindings and panel ids
- service-health capability exposure
- training-subsystem composition for:
- bounded exception posture derived from retained settlement snapshots
- reference-only external-portal handoff readiness posture
- pending-charge and route-degraded exception posture
- explicit separation between first-party handoff surfaces and actual
payment execution
- direct speech-client health capability exposure for:
- `providerSettlementExceptionShell`
- `providerExternalPortalHandoff`
- `providerSettlementExceptionReview`
## Validation target
Validate with:
- Unreal build for `UnrealHyperTwistEditor Win64 Development`
- focused automation:
- `HyperTwist.Permissive.WhisperCpp.Phase6R.AE`
- regressions:
- `HyperTwist.Permissive.WhisperCpp.Phase6R.AD`
- `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-AF` first-party real device-permission workflow
preparation/control pass
Keep the narrow sequencing guards visible:
- keep real device-permission workflow separate from native capture-route
ownership
- keep actual payment execution and provider-portal ownership separate from
this packet
- keep actual payload shipping separate from usage/cost governance
- keep broad assistant-platform widening outside this packet

View file

@ -184,15 +184,19 @@ The next bounded move is now:
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:
35. the generic source-backed `Phase 6R-AE` first-party provider settlement exception and
external-portal handoff control pass is now consumed
36. the bounded first-party `Phase 6R-AE` provider settlement exception and external-portal
handoff packet is now landed in current code
37. the next bounded move is a source-backed `Phase 6R-AF` first-party real
device-permission workflow preparation/control pass
38. keep the speech-lane guard visible:
- keep code-license judgments separate from model, voice, and payload-license review
37. keep the provider-neutral speech-lane guard visible:
39. 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
38. keep the `MagicTile` guard visible:
40. 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

View file

@ -222,8 +222,10 @@ Current landed ordering status:
first-party implementation anchor
- 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
- provider settlement exception and external-portal handoff now also have a
bounded first-party implementation anchor
- real device-permission workflow is now the next bounded first-party gap
above the landed permission/readiness shell posture
- actual payment execution or provider-portal ownership remains later
first-party work
- provider-specific adapters remain later work

View file

@ -130,6 +130,7 @@ Do not collapse those three tiers into one undifferentiated "features" voice.
| 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. |
| First-party provider settlement exception and external-portal handoff shell | Implemented now | landed first-party `Phase 6R-AE` | First-party settlement-exception shell profiles, derived exception entries, reference-only external-portal handoff posture, and bounded exception capability exposure are now live above the landed provider settlement 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. |
@ -229,6 +230,7 @@ repo.
| 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. |
| First-party provider settlement exception and external-portal handoff shell | Implemented now | landed first-party `Phase 6R-AE` packet | Current bounded settlement-exception shell profile ids/definitions, derived exception entry and issue/state posture, reference-only external-portal handoff readiness, and capability exposure above the landed provider settlement 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. |

View file

@ -183,10 +183,14 @@ Canonical discovery surfaces for roadmap interpretation:
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 generic source-backed `Phase 6R-AE` first-party provider settlement exception and
external-portal handoff control pass is now consumed
- the bounded first-party `Phase 6R-AE` provider settlement exception and external-portal
handoff packet is now landed in current code
- the current next bounded move is a source-backed `Phase 6R-AF` first-party real
device-permission workflow preparation/control pass, while keeping native capture-route
ownership, actual payment execution, provider-portal 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
@ -273,16 +277,15 @@ 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-AE` first-party provider
settlement exception and external-portal handoff preparation/control pass.
The next bounded move is a source-backed `Phase 6R-AF` first-party real
device-permission workflow 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 settlement exception and external-portal handoff
remainder above the partially landed `whisper.cpp` and `faster-whisper`
speech seams
- the first-party real device-permission workflow 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
@ -400,8 +403,10 @@ Queue interpretation after that control pass:
- provider billing settlement shell
- provider invoice reconciliation
- provider settlement-readiness review
- provider settlement exception shell
- provider external-portal handoff
- provider settlement-exception review
- still deferred:
- 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
@ -450,14 +455,14 @@ Queue interpretation after that control pass:
- viseme / gesture runtime integration
- broad assistant-platform scope
- the next queue shape is now:
- 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, receipt-review, and settlement seams
- first-party real device-permission workflow assessment above the landed
speech session, shell, payload-custody, provider-custody, provider-routing,
normalized usage/cost, operator-facing dashboard, history/export,
receipt-review, settlement, and exception/handoff seams
- the next bounded move should stay narrow:
- 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
- a source-backed `Phase 6R-AF` control pass before any widening into native
capture-route ownership, actual payment execution, provider-portal
ownership, actual payload shipping, broad voice-output ownership, or broad
assistant-platform scope
- keep the speech-input / voice sidecar legal sequencing guard visible:
- keep code-license judgments separate from model, voice, and payload-license review