Retain coach handoff history on dashboard

This commit is contained in:
axiomlogicnexus 2026-04-27 22:27:44 +02:00
parent d0a55eca5a
commit 69cb604fcd
2 changed files with 424 additions and 29 deletions

View file

@ -39,6 +39,34 @@ namespace HyperTwistCoachDashboardWidgetInternal
return TEXT("Unknown");
}
FString DescribeGuidanceLane(const EHyperTwistTrainingCoachGuidanceLane Lane)
{
switch (Lane)
{
case EHyperTwistTrainingCoachGuidanceLane::FollowUp:
return TEXT("follow-up");
case EHyperTwistTrainingCoachGuidanceLane::Recommended:
return TEXT("recommended");
default:
return TEXT("none");
}
}
FString DescribeHandoffRecommendationKind(const EHandoffRecommendationKind Kind)
{
switch (Kind)
{
case EHandoffRecommendationKind::Queue:
return TEXT("queue");
case EHandoffRecommendationKind::FollowUp:
return TEXT("follow-up");
case EHandoffRecommendationKind::Recommended:
return TEXT("recommended");
default:
return TEXT("none");
}
}
bool IsDashboardHandoffGuidanceSource(const FString& SourceLabel)
{
return SourceLabel.StartsWith(TEXT("coach-memory-dashboard-"));
@ -300,6 +328,7 @@ void UHyperTwistCoachDashboardWidget::RefreshCoachDashboardView()
SyncSelectedQueueEntry();
SyncSelectedAttempt();
SyncSelectedGuidanceDecisionHistoryEntry();
SyncSelectedHandoffHistoryEntry();
SyncSelectedQueueSuppressionHistoryEntry();
SyncSelectedClosureRecoveryHistoryEntry();
SyncPreferredGuidancePreviewLane(false);
@ -1102,6 +1131,46 @@ bool UHyperTwistCoachDashboardWidget::SelectNextGuidanceDecisionHistoryEntry()
return true;
}
bool UHyperTwistCoachDashboardWidget::SelectPreviousHandoffHistoryEntry()
{
if (HandoffHistoryEntries.Num() <= 0)
{
SelectedHandoffHistoryIndex = INDEX_NONE;
return false;
}
if (SelectedHandoffHistoryIndex == INDEX_NONE)
{
SyncSelectedHandoffHistoryEntry();
return SelectedHandoffHistoryIndex != INDEX_NONE;
}
SelectedHandoffHistoryIndex =
(SelectedHandoffHistoryIndex - 1 + HandoffHistoryEntries.Num()) % HandoffHistoryEntries.Num();
UpdateDashboardPresentation();
return true;
}
bool UHyperTwistCoachDashboardWidget::SelectNextHandoffHistoryEntry()
{
if (HandoffHistoryEntries.Num() <= 0)
{
SelectedHandoffHistoryIndex = INDEX_NONE;
return false;
}
if (SelectedHandoffHistoryIndex == INDEX_NONE)
{
SyncSelectedHandoffHistoryEntry();
return SelectedHandoffHistoryIndex != INDEX_NONE;
}
SelectedHandoffHistoryIndex =
(SelectedHandoffHistoryIndex + 1) % HandoffHistoryEntries.Num();
UpdateDashboardPresentation();
return true;
}
bool UHyperTwistCoachDashboardWidget::SelectPreviousQueueSuppressionHistoryEntry()
{
if (QueueSuppressionHistoryEntries.Num() <= 0)
@ -1469,6 +1538,16 @@ void UHyperTwistCoachDashboardWidget::HandleNextGuidanceDecisionClicked()
SelectNextGuidanceDecisionHistoryEntry();
}
void UHyperTwistCoachDashboardWidget::HandlePreviousHandoffHistoryClicked()
{
SelectPreviousHandoffHistoryEntry();
}
void UHyperTwistCoachDashboardWidget::HandleNextHandoffHistoryClicked()
{
SelectNextHandoffHistoryEntry();
}
void UHyperTwistCoachDashboardWidget::HandlePreviousQueueSuppressionClicked()
{
SelectPreviousQueueSuppressionHistoryEntry();
@ -1826,6 +1905,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
TEXT("CoachHandoffRecommendationDetail"),
8
);
HandoffHistoryHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachHandoffHistoryHeader"),
2
);
HandoffHistoryStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachHandoffHistoryStatus"),
2
);
HandoffHistoryDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachHandoffHistoryDetail"),
8
);
GuidanceReviewHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
@ -1984,6 +2081,39 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
);
StartRecommendedRunButtonLabel = StartRecommendedRunLabelRaw;
}
UHorizontalBox* HandoffHistoryNavRow = WidgetTree->ConstructWidget<UHorizontalBox>(
UHorizontalBox::StaticClass(),
TEXT("CoachDashboardHandoffHistoryNavRow")
);
if (HandoffHistoryNavRow != nullptr)
{
if (UVerticalBoxSlot* HandoffHistoryNavRowSlot = RootLayout->AddChildToVerticalBox(HandoffHistoryNavRow))
{
HandoffHistoryNavRowSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 0.0f));
}
UTextBlock* PreviousHandoffHistoryLabelRaw = nullptr;
PreviousHandoffHistoryButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
WidgetTree,
HandoffHistoryNavRow,
TEXT("PreviousHandoffHistoryButton"),
TEXT("PreviousHandoffHistoryButtonLabel"),
TEXT("Prev Handoff"),
PreviousHandoffHistoryLabelRaw
);
PreviousHandoffHistoryButtonLabel = PreviousHandoffHistoryLabelRaw;
UTextBlock* NextHandoffHistoryLabelRaw = nullptr;
NextHandoffHistoryButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
WidgetTree,
HandoffHistoryNavRow,
TEXT("NextHandoffHistoryButton"),
TEXT("NextHandoffHistoryButtonLabel"),
TEXT("Next Handoff"),
NextHandoffHistoryLabelRaw
);
NextHandoffHistoryButtonLabel = NextHandoffHistoryLabelRaw;
}
UHorizontalBox* GuidancePreviewActionRow = WidgetTree->ConstructWidget<UHorizontalBox>(
UHorizontalBox::StaticClass(),
TEXT("CoachDashboardGuidancePreviewActions")
@ -2787,6 +2917,14 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
{
NextGuidanceDecisionButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleNextGuidanceDecisionClicked);
}
if (PreviousHandoffHistoryButton != nullptr)
{
PreviousHandoffHistoryButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandlePreviousHandoffHistoryClicked);
}
if (NextHandoffHistoryButton != nullptr)
{
NextHandoffHistoryButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleNextHandoffHistoryClicked);
}
if (DismissRecapButton != nullptr)
{
DismissRecapButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleDismissRecapClicked);
@ -3038,6 +3176,21 @@ void UHyperTwistCoachDashboardWidget::SyncSelectedGuidanceDecisionHistoryEntry()
}
}
void UHyperTwistCoachDashboardWidget::SyncSelectedHandoffHistoryEntry()
{
if (HandoffHistoryEntries.Num() <= 0)
{
SelectedHandoffHistoryIndex = INDEX_NONE;
return;
}
if (SelectedHandoffHistoryIndex < 0
|| SelectedHandoffHistoryIndex >= HandoffHistoryEntries.Num())
{
SelectedHandoffHistoryIndex = HandoffHistoryEntries.Num() - 1;
}
}
void UHyperTwistCoachDashboardWidget::SyncSelectedQueueSuppressionHistoryEntry()
{
if (QueueSuppressionHistoryEntries.Num() <= 0)
@ -3914,6 +4067,88 @@ void UHyperTwistCoachDashboardWidget::RecordQueueRecoveryOutcome(
}
}
void UHyperTwistCoachDashboardWidget::RecordCoachHandoffHistorySnapshot(
const FString& RecommendedHandoffKindLabel,
const FString& RecommendationStatusLine,
const FString& RecommendationDetailLine,
const FString& QueueSourceLabel,
const int32 ClosureContinuationBias,
const int32 ClosureSuppressionBias,
const bool bQueueReady,
const bool bFollowUpReady,
const bool bRecommendedReady)
{
FHyperTwistCoachDashboardHandoffHistoryEntry Entry;
Entry.RecordedAtUtc = FDateTime::UtcNow().ToIso8601();
Entry.RecommendedHandoffKindLabel =
RecommendedHandoffKindLabel.IsEmpty() ? TEXT("none") : RecommendedHandoffKindLabel;
Entry.RecommendationStatusLine = RecommendationStatusLine;
Entry.RecommendationDetailLine = RecommendationDetailLine;
Entry.QueueSourceLabel = QueueSourceLabel;
Entry.PreferredGuidanceLane = CachedCoachPanelState.PreferredGuidanceLane;
Entry.PreferredGuidanceSourceLabel = CachedCoachPanelState.PreferredGuidanceSourceLabel;
Entry.PreferredRecoveryAction = CachedCoachPanelState.PreferredQueueRecoveryAction;
Entry.PreferredRecoveryActionSourceLabel =
CachedCoachPanelState.PreferredQueueRecoveryActionSourceLabel;
Entry.AcceptedQueuedHandoffCount =
CachedCoachQueueExecutionSummary.StartedQueuedHandoffEventCount;
Entry.AcceptedFollowUpHandoffCount =
CachedCoachQueueExecutionSummary.StartedFollowUpHandoffEventCount;
Entry.AcceptedRecommendedHandoffCount =
CachedCoachQueueExecutionSummary.StartedRecommendedHandoffEventCount;
Entry.ClosureContinuationBias = ClosureContinuationBias;
Entry.ClosureSuppressionBias = ClosureSuppressionBias;
Entry.bQueueReady = bQueueReady;
Entry.bFollowUpReady = bFollowUpReady;
Entry.bRecommendedReady = bRecommendedReady;
if (!Entry.IsStructurallyValid())
{
return;
}
const bool bMatchesLastEntry = HandoffHistoryEntries.Num() > 0
&& HandoffHistoryEntries.Last().RecommendedHandoffKindLabel
== Entry.RecommendedHandoffKindLabel
&& HandoffHistoryEntries.Last().RecommendationStatusLine
== Entry.RecommendationStatusLine
&& HandoffHistoryEntries.Last().RecommendationDetailLine
== Entry.RecommendationDetailLine
&& HandoffHistoryEntries.Last().QueueSourceLabel == Entry.QueueSourceLabel
&& HandoffHistoryEntries.Last().PreferredGuidanceLane == Entry.PreferredGuidanceLane
&& HandoffHistoryEntries.Last().PreferredGuidanceSourceLabel
== Entry.PreferredGuidanceSourceLabel
&& HandoffHistoryEntries.Last().PreferredRecoveryAction
== Entry.PreferredRecoveryAction
&& HandoffHistoryEntries.Last().PreferredRecoveryActionSourceLabel
== Entry.PreferredRecoveryActionSourceLabel
&& HandoffHistoryEntries.Last().AcceptedQueuedHandoffCount
== Entry.AcceptedQueuedHandoffCount
&& HandoffHistoryEntries.Last().AcceptedFollowUpHandoffCount
== Entry.AcceptedFollowUpHandoffCount
&& HandoffHistoryEntries.Last().AcceptedRecommendedHandoffCount
== Entry.AcceptedRecommendedHandoffCount
&& HandoffHistoryEntries.Last().ClosureContinuationBias
== Entry.ClosureContinuationBias
&& HandoffHistoryEntries.Last().ClosureSuppressionBias
== Entry.ClosureSuppressionBias
&& HandoffHistoryEntries.Last().bQueueReady == Entry.bQueueReady
&& HandoffHistoryEntries.Last().bFollowUpReady == Entry.bFollowUpReady
&& HandoffHistoryEntries.Last().bRecommendedReady == Entry.bRecommendedReady;
if (bMatchesLastEntry)
{
return;
}
HandoffHistoryEntries.Add(Entry);
constexpr int32 MaxRetainedHandoffHistoryEntries = 16;
if (HandoffHistoryEntries.Num() > MaxRetainedHandoffHistoryEntries)
{
const int32 ExcessCount = HandoffHistoryEntries.Num() - MaxRetainedHandoffHistoryEntries;
HandoffHistoryEntries.RemoveAt(0, ExcessCount, EAllowShrinking::No);
}
SelectedHandoffHistoryIndex = HandoffHistoryEntries.Num() - 1;
}
void UHyperTwistCoachDashboardWidget::RecordQueueSuppressionSnapshot()
{
const bool bHasPrimarySuppression =
@ -4369,6 +4604,12 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::None;
FString CoachHandoffRecommendationStatusLine;
FString CoachHandoffRecommendationDetailLine;
const bool bQueueReadyForHandoff = CachedCoachPanelState.bCanStartQueuedRun;
const bool bFollowUpReadyForHandoff =
CachedCoachPanelState.bCanStartCoachFollowUpRun
&& CachedCoachPanelState.bHasFollowUpGuidance;
const bool bRecommendedReadyForHandoff =
CachedCoachPanelState.bCanStartCoachRecommendedRun;
if (!bCanUseCoachHandoff)
{
CoachHandoffRecommendationStatusLine =
@ -4383,13 +4624,12 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
if (HandoffClosureBias.bHasSignal
&& HandoffClosureBias.ContinuationBias >= HandoffClosureBias.SuppressionBias + 2)
{
if (CachedCoachPanelState.bCanStartQueuedRun && bQueueLooksFollowUpOrRecovery)
if (bQueueReadyForHandoff && bQueueLooksFollowUpOrRecovery)
{
HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue;
}
else if (CachedCoachPanelState.bCanStartCoachFollowUpRun
&& CachedCoachPanelState.bHasFollowUpGuidance)
else if (bFollowUpReadyForHandoff)
{
HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::FollowUp;
@ -4398,12 +4638,12 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
else if (HandoffClosureBias.bHasSignal
&& HandoffClosureBias.SuppressionBias >= HandoffClosureBias.ContinuationBias + 2)
{
if (CachedCoachPanelState.bCanStartQueuedRun && bQueueLooksRecommendedOrPrimary)
if (bQueueReadyForHandoff && bQueueLooksRecommendedOrPrimary)
{
HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue;
}
else if (CachedCoachPanelState.bCanStartCoachRecommendedRun)
else if (bRecommendedReadyForHandoff)
{
HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Recommended;
@ -4413,18 +4653,17 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
if (HandoffRecommendationKind
== HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::None)
{
if (CachedCoachPanelState.bCanStartQueuedRun)
if (bQueueReadyForHandoff)
{
HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue;
}
else if (CachedCoachPanelState.bCanStartCoachFollowUpRun
&& CachedCoachPanelState.bHasFollowUpGuidance)
else if (bFollowUpReadyForHandoff)
{
HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::FollowUp;
}
else if (CachedCoachPanelState.bCanStartCoachRecommendedRun)
else if (bRecommendedReadyForHandoff)
{
HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Recommended;
@ -4461,23 +4700,29 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
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"),
(CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance)
? TEXT("yes")
: TEXT("no"),
CachedCoachPanelState.bCanStartCoachRecommendedRun ? TEXT("yes") : TEXT("no"),
CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp
? TEXT("follow-up")
: (CachedCoachPanelState.PreferredGuidanceLane
== EHyperTwistTrainingCoachGuidanceLane::Recommended
? TEXT("recommended")
: TEXT("none")),
bQueueReadyForHandoff ? TEXT("yes") : TEXT("no"),
bFollowUpReadyForHandoff ? TEXT("yes") : TEXT("no"),
bRecommendedReadyForHandoff ? TEXT("yes") : TEXT("no"),
*HyperTwistCoachDashboardWidgetInternal::DescribeGuidanceLane(
CachedCoachPanelState.PreferredGuidanceLane),
*HandoffMemorySourceLabel,
bDashboardHandoffMemorySource
? TEXT(" | Accepted dashboard handoffs are actively reinforcing this recommendation.")
: TEXT("")
);
}
RecordCoachHandoffHistorySnapshot(
HyperTwistCoachDashboardWidgetInternal::DescribeHandoffRecommendationKind(
HandoffRecommendationKind),
CoachHandoffRecommendationStatusLine,
CoachHandoffRecommendationDetailLine,
CachedCoachPanelState.NextQueueSourceLabel,
HandoffClosureBias.ContinuationBias,
HandoffClosureBias.SuppressionBias,
bQueueReadyForHandoff,
bFollowUpReadyForHandoff,
bRecommendedReadyForHandoff
);
FString QueuedRunLabel = CachedCoachPanelState.NextQueueSourceLabel.IsEmpty()
? TEXT("Start Queue")
: FString::Printf(TEXT("Queue: %s"), *CachedCoachPanelState.NextQueueSourceLabel);
@ -4501,6 +4746,55 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
default:
break;
}
const FHyperTwistCoachDashboardHandoffHistoryEntry* SelectedHandoffHistoryEntry =
SelectedHandoffHistoryIndex >= 0
&& SelectedHandoffHistoryIndex < HandoffHistoryEntries.Num()
? &HandoffHistoryEntries[SelectedHandoffHistoryIndex]
: nullptr;
const FString HandoffHistoryStatusLine =
SelectedHandoffHistoryEntry != nullptr
? FString::Printf(
TEXT("Handoff history: %d entries | selected %d/%d | Recommendation: %s | Recorded: %s"),
HandoffHistoryEntries.Num(),
SelectedHandoffHistoryIndex + 1,
HandoffHistoryEntries.Num(),
SelectedHandoffHistoryEntry->RecommendedHandoffKindLabel.IsEmpty()
? TEXT("none")
: *SelectedHandoffHistoryEntry->RecommendedHandoffKindLabel,
*SelectedHandoffHistoryEntry->RecordedAtUtc
)
: TEXT("Handoff history: no retained handoff-pattern shifts recorded yet.");
const FString HandoffHistoryDetailLine =
SelectedHandoffHistoryEntry != nullptr
? FString::Printf(
TEXT("Accepted queued/follow-up/recommended: %d/%d/%d | queue source: %s | preferred lane: %s | memory source: %s | recovery action: %s | recovery source: %s | closure continuation bias %d | closure suppression bias %d | readiness queue/follow-up/recommended: %s/%s/%s || %s"),
SelectedHandoffHistoryEntry->AcceptedQueuedHandoffCount,
SelectedHandoffHistoryEntry->AcceptedFollowUpHandoffCount,
SelectedHandoffHistoryEntry->AcceptedRecommendedHandoffCount,
SelectedHandoffHistoryEntry->QueueSourceLabel.IsEmpty()
? TEXT("n/a")
: *SelectedHandoffHistoryEntry->QueueSourceLabel,
*HyperTwistCoachDashboardWidgetInternal::DescribeGuidanceLane(
SelectedHandoffHistoryEntry->PreferredGuidanceLane),
*HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel(
SelectedHandoffHistoryEntry->PreferredGuidanceSourceLabel),
SelectedHandoffHistoryEntry->PreferredRecoveryAction
== EHyperTwistTrainingQueueRecoveryAction::None
? TEXT("none")
: *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
SelectedHandoffHistoryEntry->PreferredRecoveryAction),
*HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel(
SelectedHandoffHistoryEntry->PreferredRecoveryActionSourceLabel),
SelectedHandoffHistoryEntry->ClosureContinuationBias,
SelectedHandoffHistoryEntry->ClosureSuppressionBias,
SelectedHandoffHistoryEntry->bQueueReady ? TEXT("yes") : TEXT("no"),
SelectedHandoffHistoryEntry->bFollowUpReady ? TEXT("yes") : TEXT("no"),
SelectedHandoffHistoryEntry->bRecommendedReady ? TEXT("yes") : TEXT("no"),
SelectedHandoffHistoryEntry->RecommendationDetailLine.IsEmpty()
? *SelectedHandoffHistoryEntry->RecommendationStatusLine
: *SelectedHandoffHistoryEntry->RecommendationDetailLine
)
: TEXT("Handoff history detail: retained recommendation shifts and accepted queued/follow-up/recommended handoff patterns will accumulate here over time.");
const auto BuildDeckPreviewLine = [](const FHyperTwistTrainingDeck& Deck, const FHyperTwistCoachBrief& Brief, const TCHAR* EmptyPrefix)
{
if (Deck.IsStructurallyValid())
@ -5422,6 +5716,34 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
{
CoachHandoffRecommendationDetailTextBlock->SetText(FText::FromString(CoachHandoffRecommendationDetailLine));
}
if (HandoffHistoryHeaderTextBlock != nullptr)
{
HandoffHistoryHeaderTextBlock->SetText(FText::FromString(TEXT("[Handoff History]")));
}
if (HandoffHistoryStatusTextBlock != nullptr)
{
HandoffHistoryStatusTextBlock->SetText(FText::FromString(HandoffHistoryStatusLine));
}
if (HandoffHistoryDetailTextBlock != nullptr)
{
HandoffHistoryDetailTextBlock->SetText(FText::FromString(HandoffHistoryDetailLine));
}
if (PreviousHandoffHistoryButtonLabel != nullptr)
{
PreviousHandoffHistoryButtonLabel->SetText(FText::FromString(TEXT("Prev Handoff")));
}
if (PreviousHandoffHistoryButton != nullptr)
{
PreviousHandoffHistoryButton->SetIsEnabled(HandoffHistoryEntries.Num() > 1);
}
if (NextHandoffHistoryButtonLabel != nullptr)
{
NextHandoffHistoryButtonLabel->SetText(FText::FromString(TEXT("Next Handoff")));
}
if (NextHandoffHistoryButton != nullptr)
{
NextHandoffHistoryButton->SetIsEnabled(HandoffHistoryEntries.Num() > 1);
}
if (GuidanceReviewHeaderTextBlock != nullptr)
{
GuidanceReviewHeaderTextBlock->SetText(FText::FromString(TEXT("[Guidance Review]")));
@ -5460,7 +5782,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (StartQueuedRunButton != nullptr)
{
StartQueuedRunButton->SetIsEnabled(bCanUseCoachHandoff && CachedCoachPanelState.bCanStartQueuedRun);
StartQueuedRunButton->SetIsEnabled(bCanUseCoachHandoff && bQueueReadyForHandoff);
}
if (StartFollowUpRunButtonLabel != nullptr)
{
@ -5468,11 +5790,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (StartFollowUpRunButton != nullptr)
{
StartFollowUpRunButton->SetIsEnabled(
bCanUseCoachHandoff
&& CachedCoachPanelState.bCanStartCoachFollowUpRun
&& CachedCoachPanelState.bHasFollowUpGuidance
);
StartFollowUpRunButton->SetIsEnabled(bCanUseCoachHandoff && bFollowUpReadyForHandoff);
}
if (StartRecommendedRunButtonLabel != nullptr)
{
@ -5480,9 +5798,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (StartRecommendedRunButton != nullptr)
{
StartRecommendedRunButton->SetIsEnabled(
bCanUseCoachHandoff && CachedCoachPanelState.bCanStartCoachRecommendedRun
);
StartRecommendedRunButton->SetIsEnabled(bCanUseCoachHandoff && bRecommendedReadyForHandoff);
}
if (PreviewRecommendedButtonLabel != nullptr)
{

View file

@ -214,6 +214,36 @@ struct FHyperTwistCoachDashboardClosureRecoveryEntry
}
};
struct FHyperTwistCoachDashboardHandoffHistoryEntry
{
FString RecordedAtUtc;
FString RecommendedHandoffKindLabel;
FString RecommendationStatusLine;
FString RecommendationDetailLine;
FString QueueSourceLabel;
EHyperTwistTrainingCoachGuidanceLane PreferredGuidanceLane =
EHyperTwistTrainingCoachGuidanceLane::None;
FString PreferredGuidanceSourceLabel;
EHyperTwistTrainingQueueRecoveryAction PreferredRecoveryAction =
EHyperTwistTrainingQueueRecoveryAction::None;
FString PreferredRecoveryActionSourceLabel;
int32 AcceptedQueuedHandoffCount = 0;
int32 AcceptedFollowUpHandoffCount = 0;
int32 AcceptedRecommendedHandoffCount = 0;
int32 ClosureContinuationBias = 0;
int32 ClosureSuppressionBias = 0;
bool bQueueReady = false;
bool bFollowUpReady = false;
bool bRecommendedReady = false;
bool IsStructurallyValid() const
{
return !RecordedAtUtc.IsEmpty()
&& !RecommendedHandoffKindLabel.IsEmpty()
&& !RecommendationStatusLine.IsEmpty();
}
};
UCLASS(BlueprintType, Blueprintable)
class UNREALHYPERTWIST_API UHyperTwistCoachDashboardWidget : public UHyperTwistTrainingPanelWidget
{
@ -256,6 +286,9 @@ public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
int32 SelectedGuidanceDecisionIndex = INDEX_NONE;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
int32 SelectedHandoffHistoryIndex = INDEX_NONE;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
int32 SelectedQueueSuppressionIndex = INDEX_NONE;
@ -349,6 +382,12 @@ public:
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
bool SelectNextGuidanceDecisionHistoryEntry();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
bool SelectPreviousHandoffHistoryEntry();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
bool SelectNextHandoffHistoryEntry();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
bool SelectPreviousQueueSuppressionHistoryEntry();
@ -490,6 +529,27 @@ protected:
UPROPERTY(Transient)
TObjectPtr<UTextBlock> CoachHandoffRecommendationDetailTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> HandoffHistoryHeaderTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> HandoffHistoryStatusTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> HandoffHistoryDetailTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UButton> PreviousHandoffHistoryButton = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> PreviousHandoffHistoryButtonLabel = nullptr;
UPROPERTY(Transient)
TObjectPtr<UButton> NextHandoffHistoryButton = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> NextHandoffHistoryButtonLabel = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidanceReviewHeaderTextBlock = nullptr;
@ -931,6 +991,12 @@ protected:
UFUNCTION()
void HandleNextGuidanceDecisionClicked();
UFUNCTION()
void HandlePreviousHandoffHistoryClicked();
UFUNCTION()
void HandleNextHandoffHistoryClicked();
UFUNCTION()
void HandlePreviousQueueSuppressionClicked();
@ -998,6 +1064,7 @@ private:
void SyncRetainedRunRecap();
void SyncSelectedGuidancePreviewCase();
void SyncSelectedGuidanceDecisionHistoryEntry();
void SyncSelectedHandoffHistoryEntry();
void SyncSelectedQueueSuppressionHistoryEntry();
void SyncSelectedClosureRecoveryHistoryEntry();
void SyncPreferredGuidancePreviewLane(bool bForceAdoptPreferredLane);
@ -1039,6 +1106,17 @@ private:
bool bBeforeCompletionGap,
bool bSucceeded
);
void RecordCoachHandoffHistorySnapshot(
const FString& RecommendedHandoffKindLabel,
const FString& RecommendationStatusLine,
const FString& RecommendationDetailLine,
const FString& QueueSourceLabel,
int32 ClosureContinuationBias,
int32 ClosureSuppressionBias,
bool bQueueReady,
bool bFollowUpReady,
bool bRecommendedReady
);
void RecordQueueSuppressionSnapshot();
void RecordClosureRecoverySnapshot();
void RecordGuidanceOutcomeDecision(
@ -1087,6 +1165,7 @@ private:
FHyperTwistTrainingDeck RetainedGuidanceComparisonDeck;
FString RetainedGuidanceComparisonSelectedCaseId;
TArray<FHyperTwistCoachDashboardDecisionHistoryEntry> GuidanceDecisionHistoryEntries;
TArray<FHyperTwistCoachDashboardHandoffHistoryEntry> HandoffHistoryEntries;
bool bHasQueueRecoveryOutcome = false;
FHyperTwistCoachDashboardQueueRecoveryOutcome QueueRecoveryOutcome;
TArray<FHyperTwistCoachDashboardQueueRecoveryOutcome> QueueRecoveryOutcomeHistory;