diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 1b50d58..b2d9135 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -39,6 +39,55 @@ namespace HyperTwistCoachDashboardWidgetInternal return TEXT("Unknown"); } + bool IsDashboardHandoffGuidanceSource(const FString& SourceLabel) + { + return SourceLabel.StartsWith(TEXT("coach-memory-dashboard-")); + } + + bool IsQueueRecoveryMemorySource(const FString& SourceLabel) + { + return SourceLabel.StartsWith(TEXT("coach-memory-queue-")) + || IsDashboardHandoffGuidanceSource(SourceLabel); + } + + FString DescribeCoachMemorySourceLabel(const FString& SourceLabel) + { + if (SourceLabel == TEXT("coach-memory-dashboard-follow-up")) + { + return TEXT("accepted follow-up handoff history"); + } + if (SourceLabel == TEXT("coach-memory-dashboard-recommended")) + { + return TEXT("accepted recommended handoff history"); + } + if (SourceLabel == TEXT("coach-memory-queue-follow-up")) + { + return TEXT("queue follow-up recovery memory"); + } + if (SourceLabel == TEXT("coach-memory-queue-recommended")) + { + return TEXT("queue recommended recovery memory"); + } + if (SourceLabel == TEXT("dashboard-handoff-queued")) + { + return TEXT("accepted queued dashboard handoff"); + } + if (SourceLabel == TEXT("queue-history-promote-pressure")) + { + return TEXT("queue promote-pressure history"); + } + if (SourceLabel == TEXT("queue-history-launch-pressure")) + { + return TEXT("queue launch-pressure history"); + } + if (SourceLabel == TEXT("queue-history-balanced-pressure")) + { + return TEXT("balanced queue-recovery history"); + } + + return SourceLabel.IsEmpty() ? TEXT("n/a") : SourceLabel; + } + UTextBlock* AddTextRow( UWidgetTree* WidgetTree, UVerticalBox* Parent, @@ -333,7 +382,8 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture() const FString DeferredEntryId = FindFirstDeferredQueueEntryId(); const bool bQueueRecoveryMemorySource = - CachedCoachPanelState.PreferredGuidanceSourceLabel.StartsWith(TEXT("coach-memory-queue-")); + HyperTwistCoachDashboardWidgetInternal::IsQueueRecoveryMemorySource( + CachedCoachPanelState.PreferredGuidanceSourceLabel); const bool bPrefersFollowUp = CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp; const bool bPrefersRecommended = @@ -4301,6 +4351,12 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() HyperTwistCoachDashboardWidgetInternal::DeriveClosureRecoveryDashboardBias( ClosureRecoveryHistoryEntries ); + const FString HandoffMemorySourceLabel = + HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel( + CachedCoachPanelState.PreferredGuidanceSourceLabel); + const bool bDashboardHandoffMemorySource = + HyperTwistCoachDashboardWidgetInternal::IsDashboardHandoffGuidanceSource( + CachedCoachPanelState.PreferredGuidanceSourceLabel); const bool bQueueLooksFollowUpOrRecovery = CachedCoachPanelState.NextQueueSourceLabel.Contains(TEXT("follow-up")) || CachedCoachPanelState.NextQueueSourceLabel.Contains(TEXT("queue-recovery")) @@ -4402,7 +4458,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } CoachHandoffRecommendationDetailLine = FString::Printf( - TEXT("Closure continuation bias %d | Closure suppression bias %d | Queue ready: %s | Follow-up ready: %s | Recommended ready: %s | Preferred lane: %s"), + TEXT("Closure continuation bias %d | Closure suppression bias %d | Queue ready: %s | Follow-up ready: %s | Recommended ready: %s | Preferred lane: %s | Memory source: %s%s"), HandoffClosureBias.ContinuationBias, HandoffClosureBias.SuppressionBias, CachedCoachPanelState.bCanStartQueuedRun ? TEXT("yes") : TEXT("no"), @@ -4415,7 +4471,11 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() : (CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended ? TEXT("recommended") - : TEXT("none")) + : TEXT("none")), + *HandoffMemorySourceLabel, + bDashboardHandoffMemorySource + ? TEXT(" | Accepted dashboard handoffs are actively reinforcing this recommendation.") + : TEXT("") ); } FString QueuedRunLabel = CachedCoachPanelState.NextQueueSourceLabel.IsEmpty() @@ -4524,7 +4584,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() ? TEXT("closure-history + memory") : (CachedCoachPanelState.PreferredGuidanceSourceLabel.IsEmpty() ? TEXT("availability fallback") - : CachedCoachPanelState.PreferredGuidanceSourceLabel)); + : HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel( + CachedCoachPanelState.PreferredGuidanceSourceLabel))); const FString GuidancePreferenceStatusLine = GuidanceLanePreference.IsStructurallyValid() ? FString::Printf( @@ -4549,7 +4610,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() ) : TEXT("Preference detail: build recommended or follow-up guidance to let the dashboard choose a next surfaced lane."); const bool bQueueRecoveryMemorySource = - CachedCoachPanelState.PreferredGuidanceSourceLabel.StartsWith(TEXT("coach-memory-queue-")); + HyperTwistCoachDashboardWidgetInternal::IsQueueRecoveryMemorySource( + CachedCoachPanelState.PreferredGuidanceSourceLabel); const bool bQueueRecoveryFollowUp = CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp; const bool bQueueRecoveryRecommended = @@ -4560,13 +4622,14 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() : (bQueueRecoveryRecommended ? TEXT("recommended") : TEXT("none")); - const FString QueueRecoverySourceLabel = CachedCoachPanelState.PreferredGuidanceSourceLabel.IsEmpty() - ? TEXT("n/a") - : CachedCoachPanelState.PreferredGuidanceSourceLabel; + const FString QueueRecoverySourceLabel = + HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel( + CachedCoachPanelState.PreferredGuidanceSourceLabel); const FString QueueRecoveryActionSourceLabel = CachedCoachPanelState.PreferredQueueRecoveryActionSourceLabel.IsEmpty() ? TEXT("n/a") - : CachedCoachPanelState.PreferredQueueRecoveryActionSourceLabel; + : HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel( + CachedCoachPanelState.PreferredQueueRecoveryActionSourceLabel); const FHyperTwistCoachDashboardQueueRecoveryWeighting QueueRecoveryWeighting = BuildQueueRecoveryWeighting(); const bool bCanLaunchQueueRecovery = CachedCoachPanelState.bCanStartQueuedRun @@ -4875,7 +4938,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() const FString GuidanceRationaleDetailLine = ActivePreviewBrief != nullptr ? FString::Printf( - TEXT("Brief: %s | Action: %s | Deck: %s | Method: %s | Focus cases: %d | Review: %s | Cadence: %s | Variety: %s | Selected focus: %s | Matching signals: %d | Case signal: %s | Top signals: %s"), + TEXT("Brief: %s | Action: %s | Deck: %s | Method: %s | Focus cases: %d | Review: %s | Cadence: %s | Variety: %s | Guidance source: %s | Selected focus: %s | Matching signals: %d | Case signal: %s | Top signals: %s"), ActivePreviewBrief->Headline.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->Headline, ActivePreviewBrief->PrimaryActionLabel.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->PrimaryActionLabel, ActivePreviewBrief->FocusDeckId.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->FocusDeckId, @@ -4884,6 +4947,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() ActivePreviewBrief->bNeedsCoachReview ? TEXT("yes") : TEXT("no"), ActivePreviewBrief->bNeedsCadenceRecovery ? TEXT("yes") : TEXT("no"), ActivePreviewBrief->bNeedsVarietyRotation ? TEXT("yes") : TEXT("no"), + *HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel( + CachedCoachPanelState.PreferredGuidanceSourceLabel), bSelectedPreviewCaseIsExplicitFocus ? TEXT("yes") : TEXT("no"), SelectedPreviewCaseSignalCount, *SelectedPreviewCaseSignalLine, @@ -5901,9 +5966,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() ? TEXT("yes") : TEXT("no"), *ClosureGuidanceLaneLabel, - CachedCoachPanelState.PreferredGuidanceSourceLabel.IsEmpty() - ? TEXT("n/a") - : *CachedCoachPanelState.PreferredGuidanceSourceLabel, + *HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel( + CachedCoachPanelState.PreferredGuidanceSourceLabel), ClosureRecoverySignal != nullptr ? TEXT(" || Signal detail: ") : TEXT(""), ClosureRecoverySignal != nullptr ? *ClosureRecoverySignal->Detail @@ -5950,9 +6014,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() SelectedClosureRecoveryEntry->bBorderlineFollowUpSuppressedByRecoveryHistory ? TEXT("yes") : TEXT("no"), - SelectedClosureRecoveryEntry->PreferredGuidanceSourceLabel.IsEmpty() - ? TEXT("n/a") - : *SelectedClosureRecoveryEntry->PreferredGuidanceSourceLabel, + *HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel( + SelectedClosureRecoveryEntry->PreferredGuidanceSourceLabel), SelectedClosureRecoveryEntry->bActiveSignalPromoted ? TEXT("yes") : TEXT("no"), SelectedClosureRecoveryEntry->ActiveSignalHeadline.IsEmpty() ? TEXT("n/a") diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index 0fe1a85..baea29e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -18,6 +18,55 @@ namespace HyperTwistTrainingCoachLibraryInternal return EHyperTwistTrainingCoachGuidanceLane::None; } + bool IsDashboardHandoffGuidanceSource(const FString& SourceLabel) + { + return SourceLabel.StartsWith(TEXT("coach-memory-dashboard-")); + } + + bool IsQueueRecoveryMemorySource(const FString& SourceLabel) + { + return SourceLabel.StartsWith(TEXT("coach-memory-queue-")) + || IsDashboardHandoffGuidanceSource(SourceLabel); + } + + FString DescribeCoachMemorySourceLabel(const FString& SourceLabel) + { + if (SourceLabel == TEXT("coach-memory-dashboard-follow-up")) + { + return TEXT("accepted follow-up handoff history"); + } + if (SourceLabel == TEXT("coach-memory-dashboard-recommended")) + { + return TEXT("accepted recommended handoff history"); + } + if (SourceLabel == TEXT("coach-memory-queue-follow-up")) + { + return TEXT("queue follow-up recovery memory"); + } + if (SourceLabel == TEXT("coach-memory-queue-recommended")) + { + return TEXT("queue recommended recovery memory"); + } + if (SourceLabel == TEXT("dashboard-handoff-queued")) + { + return TEXT("accepted queued dashboard handoff"); + } + if (SourceLabel == TEXT("queue-history-promote-pressure")) + { + return TEXT("queue promote-pressure history"); + } + if (SourceLabel == TEXT("queue-history-launch-pressure")) + { + return TEXT("queue launch-pressure history"); + } + if (SourceLabel == TEXT("queue-history-balanced-pressure")) + { + return TEXT("balanced queue-recovery history"); + } + + return SourceLabel.IsEmpty() ? TEXT("none") : SourceLabel; + } + float ResolvePreferredGuidanceLanePressure( const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot) { @@ -531,13 +580,18 @@ TArray UHyperTwistTrainingCoachLibrary::DeriveCoachSigna const bool bBlockedFollowUp = CoachMemorySnapshot.bHasBlockedFollowUpScheduleFriction; const bool bRepeatManualReschedule = CoachMemorySnapshot.bHasRepeatManualScheduleFriction; const bool bQueueRecoverySource = - CoachMemorySnapshot.PreferredGuidanceSourceLabel.StartsWith(TEXT("coach-memory-queue-")); + HyperTwistTrainingCoachLibraryInternal::IsQueueRecoveryMemorySource( + CoachMemorySnapshot.PreferredGuidanceSourceLabel); const bool bQueueRecoveryFollowUp = bQueueRecoverySource && CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp; const bool bQueueRecoveryRecommended = bQueueRecoverySource && CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended; + const bool bDashboardFollowUpRecovery = + CoachMemorySnapshot.PreferredGuidanceSourceLabel == TEXT("coach-memory-dashboard-follow-up"); + const bool bDashboardRecommendedRecovery = + CoachMemorySnapshot.PreferredGuidanceSourceLabel == TEXT("coach-memory-dashboard-recommended"); const bool bPromoteQueueRecovery = CoachMemorySnapshot.PreferredQueueRecoveryAction == EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred; @@ -550,33 +604,61 @@ TArray UHyperTwistTrainingCoachLibrary::DeriveCoachSigna + (bBlockedFollowUp ? 6.0f : 0.0f) + (bRepeatManualReschedule ? 5.0f : 0.0f) + (CoachMemorySnapshot.bHasQueueExecutionCompletionGap ? 4.0f : 0.0f) - + (bQueueRecoverySource ? 2.0f : 0.0f); + + (bQueueRecoverySource ? 2.0f : 0.0f) + + ((bDashboardFollowUpRecovery || bDashboardRecommendedRecovery) ? 2.0f : 0.0f); const EHyperTwistCoachFocusLane FocusLane = bArchiveAware ? EHyperTwistCoachFocusLane::Stability : EHyperTwistCoachFocusLane::Cadence; - const FString Headline = bQueueRecoveryFollowUp - ? (bPromoteQueueRecovery + FString Headline; + if (bDashboardFollowUpRecovery) + { + Headline = bPromoteQueueRecovery + ? TEXT("Accepted follow-up handoffs keep the deferred follow-up lane active") + : TEXT("Accepted follow-up handoffs favor recovering the follow-up lane next"); + } + else if (bDashboardRecommendedRecovery) + { + Headline = bPromoteQueueRecovery + ? TEXT("Accepted recommended handoffs keep the deferred recommended lane active") + : TEXT("Accepted recommended handoffs favor restoring the recommended lane next"); + } + else if (bQueueRecoveryFollowUp) + { + Headline = bPromoteQueueRecovery ? TEXT("Promote the deferred follow-up lane back into active queue flow") - : TEXT("Recover the deferred follow-up lane before the queue drifts further")) - : (bQueueRecoveryRecommended - ? (bPromoteQueueRecovery - ? TEXT("Promote the deferred recommended lane back into the active queue") - : TEXT("Recover the deferred recommended lane and restore queue cadence")) - : (bBlockedFollowUp - ? (bPromoteQueueRecovery - ? TEXT("Promote deferred work to relieve follow-up scheduling friction") - : TEXT("Follow-up scheduling friction is stalling the queue")) - : (bArchiveAware - ? TEXT("Archive-aware scheduling friction needs cleanup") - : (bLaunchQueueRecovery - ? TEXT("Launch recovery work to restore coaching queue cadence") - : TEXT("Coaching queue scheduling needs intervention"))))); + : TEXT("Recover the deferred follow-up lane before the queue drifts further"); + } + else if (bQueueRecoveryRecommended) + { + Headline = bPromoteQueueRecovery + ? TEXT("Promote the deferred recommended lane back into the active queue") + : TEXT("Recover the deferred recommended lane and restore queue cadence"); + } + else if (bBlockedFollowUp) + { + Headline = bPromoteQueueRecovery + ? TEXT("Promote deferred work to relieve follow-up scheduling friction") + : TEXT("Follow-up scheduling friction is stalling the queue"); + } + else if (bArchiveAware) + { + Headline = TEXT("Archive-aware scheduling friction needs cleanup"); + } + else if (bLaunchQueueRecovery) + { + Headline = TEXT("Launch recovery work to restore coaching queue cadence"); + } + else + { + Headline = TEXT("Coaching queue scheduling needs intervention"); + } const FString DeferralReasonLabel = !CoachMemorySnapshot.DominantScheduleDeferralReasonLabel.IsEmpty() ? CoachMemorySnapshot.DominantScheduleDeferralReasonLabel : TEXT("none"); const FString RecoverySourceLabel = bQueueRecoverySource - ? CoachMemorySnapshot.PreferredGuidanceSourceLabel + ? HyperTwistTrainingCoachLibraryInternal::DescribeCoachMemorySourceLabel( + CoachMemorySnapshot.PreferredGuidanceSourceLabel) : TEXT("none"); const FString RecoveryActionLabel = CoachMemorySnapshot.PreferredQueueRecoveryAction == EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred @@ -588,7 +670,8 @@ TArray UHyperTwistTrainingCoachLibrary::DeriveCoachSigna const FString RecoveryActionSourceLabel = CoachMemorySnapshot.PreferredQueueRecoveryActionSourceLabel.IsEmpty() ? TEXT("none") - : CoachMemorySnapshot.PreferredQueueRecoveryActionSourceLabel; + : HyperTwistTrainingCoachLibraryInternal::DescribeCoachMemorySourceLabel( + CoachMemorySnapshot.PreferredQueueRecoveryActionSourceLabel); const FString SignalId = bQueueRecoveryFollowUp ? (bPromoteQueueRecovery ? TEXT("schedule_friction_follow_up_promote_recovery") @@ -1556,7 +1639,8 @@ FHyperTwistCoachSignal UHyperTwistTrainingCoachLibrary::DeriveClosureRecoveryHis CoachMemorySnapshot.FollowUpReadyCoachSequenceCount, CoachMemorySnapshot.PreferredGuidanceSourceLabel.IsEmpty() ? TEXT("n/a") - : *CoachMemorySnapshot.PreferredGuidanceSourceLabel + : *HyperTwistTrainingCoachLibraryInternal::DescribeCoachMemorySourceLabel( + CoachMemorySnapshot.PreferredGuidanceSourceLabel) ); const EHyperTwistTrainingDeliveryMode SuggestedMode = bHistoryPrefersFollowUp ? EHyperTwistTrainingDeliveryMode::CoachReviewed