Recommend coach handoff from closure history

This commit is contained in:
axiomlogicnexus 2026-04-27 21:41:39 +02:00
parent 6167b8048f
commit 33f0dad486
2 changed files with 185 additions and 3 deletions

View file

@ -20,6 +20,14 @@ namespace HyperTwistCoachDashboardWidgetInternal
bool bHasSignal = false;
};
enum class EHandoffRecommendationKind : uint8
{
None,
Queue,
FollowUp,
Recommended
};
template <typename TEnum>
FString EnumDisplayName(const TEnum Value)
{
@ -1750,6 +1758,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
TEXT("CoachHandoffStatus"),
2
);
CoachHandoffRecommendationHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachHandoffRecommendationHeader"),
2
);
CoachHandoffRecommendationStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachHandoffRecommendationStatus"),
2
);
CoachHandoffRecommendationDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachHandoffRecommendationDetail"),
8
);
GuidanceReviewHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
@ -4271,15 +4297,150 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
*RecommendedLine
);
}
const FString QueuedRunLabel = CachedCoachPanelState.NextQueueSourceLabel.IsEmpty()
const HyperTwistCoachDashboardWidgetInternal::FClosureRecoveryDashboardBias HandoffClosureBias =
HyperTwistCoachDashboardWidgetInternal::DeriveClosureRecoveryDashboardBias(
ClosureRecoveryHistoryEntries
);
const bool bQueueLooksFollowUpOrRecovery =
CachedCoachPanelState.NextQueueSourceLabel.Contains(TEXT("follow-up"))
|| CachedCoachPanelState.NextQueueSourceLabel.Contains(TEXT("queue-recovery"))
|| CachedCoachPanelState.NextQueueSourceLabel.Contains(TEXT("closure_history_follow_up"));
const bool bQueueLooksRecommendedOrPrimary =
CachedCoachPanelState.NextQueueSourceLabel.Contains(TEXT("primary"))
|| CachedCoachPanelState.NextQueueSourceLabel.Contains(TEXT("recommended"))
|| CachedCoachPanelState.NextQueueSourceLabel.Contains(TEXT("closure_history_recommended"));
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::None;
FString CoachHandoffRecommendationStatusLine;
FString CoachHandoffRecommendationDetailLine;
if (!bCanUseCoachHandoff)
{
CoachHandoffRecommendationStatusLine =
bHasLiveAttemptTimer
? TEXT("Recommendation: wait for the live attempt to finish before applying closure-aware handoff.")
: TEXT("Recommendation: finish or clear the current run before applying closure-aware handoff.");
CoachHandoffRecommendationDetailLine =
TEXT("Handoff recommendation detail: closure-history pressure is retained, but handoff choice is paused while the dashboard is busy.");
}
else
{
if (HandoffClosureBias.bHasSignal
&& HandoffClosureBias.ContinuationBias >= HandoffClosureBias.SuppressionBias + 2)
{
if (CachedCoachPanelState.bCanStartQueuedRun && bQueueLooksFollowUpOrRecovery)
{
HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue;
}
else if (CachedCoachPanelState.bCanStartCoachFollowUpRun
&& CachedCoachPanelState.bHasFollowUpGuidance)
{
HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::FollowUp;
}
}
else if (HandoffClosureBias.bHasSignal
&& HandoffClosureBias.SuppressionBias >= HandoffClosureBias.ContinuationBias + 2)
{
if (CachedCoachPanelState.bCanStartQueuedRun && bQueueLooksRecommendedOrPrimary)
{
HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue;
}
else if (CachedCoachPanelState.bCanStartCoachRecommendedRun)
{
HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Recommended;
}
}
if (HandoffRecommendationKind
== HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::None)
{
if (CachedCoachPanelState.bCanStartQueuedRun)
{
HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue;
}
else if (CachedCoachPanelState.bCanStartCoachFollowUpRun
&& CachedCoachPanelState.bHasFollowUpGuidance)
{
HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::FollowUp;
}
else if (CachedCoachPanelState.bCanStartCoachRecommendedRun)
{
HandoffRecommendationKind =
HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Recommended;
}
}
switch (HandoffRecommendationKind)
{
case HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue:
CoachHandoffRecommendationStatusLine = FString::Printf(
TEXT("Recommendation: start the queued coach run next. Source: %s"),
CachedCoachPanelState.NextQueueSourceLabel.IsEmpty()
? TEXT("n/a")
: *CachedCoachPanelState.NextQueueSourceLabel
);
break;
case HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::FollowUp:
CoachHandoffRecommendationStatusLine = TEXT(
"Recommendation: continue with a follow-up coach run next."
);
break;
case HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Recommended:
CoachHandoffRecommendationStatusLine = TEXT(
"Recommendation: redirect into the recommended coach run next."
);
break;
default:
CoachHandoffRecommendationStatusLine =
TEXT("Recommendation: no coach-directed handoff is currently ready.");
break;
}
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"),
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"))
);
}
FString QueuedRunLabel = CachedCoachPanelState.NextQueueSourceLabel.IsEmpty()
? TEXT("Start Queue")
: FString::Printf(TEXT("Queue: %s"), *CachedCoachPanelState.NextQueueSourceLabel);
const FString FollowUpRunLabel = CachedCoachFollowUpBrief.PrimaryActionLabel.IsEmpty()
FString FollowUpRunLabel = CachedCoachFollowUpBrief.PrimaryActionLabel.IsEmpty()
? TEXT("Start Follow-Up")
: CachedCoachFollowUpBrief.PrimaryActionLabel;
const FString RecommendedRunLabel = CachedCoachBrief.PrimaryActionLabel.IsEmpty()
FString RecommendedRunLabel = CachedCoachBrief.PrimaryActionLabel.IsEmpty()
? TEXT("Start Recommended")
: CachedCoachBrief.PrimaryActionLabel;
switch (HandoffRecommendationKind)
{
case HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue:
QueuedRunLabel = FString::Printf(TEXT("Recommended: %s"), *QueuedRunLabel);
break;
case HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::FollowUp:
FollowUpRunLabel = FString::Printf(TEXT("Recommended: %s"), *FollowUpRunLabel);
break;
case HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Recommended:
RecommendedRunLabel = FString::Printf(TEXT("Recommended: %s"), *RecommendedRunLabel);
break;
default:
break;
}
const auto BuildDeckPreviewLine = [](const FHyperTwistTrainingDeck& Deck, const FHyperTwistCoachBrief& Brief, const TCHAR* EmptyPrefix)
{
if (Deck.IsStructurallyValid())
@ -5184,6 +5345,18 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
{
CoachHandoffStatusTextBlock->SetText(FText::FromString(CoachHandoffStatusLine));
}
if (CoachHandoffRecommendationHeaderTextBlock != nullptr)
{
CoachHandoffRecommendationHeaderTextBlock->SetText(FText::FromString(TEXT("[Handoff Recommendation]")));
}
if (CoachHandoffRecommendationStatusTextBlock != nullptr)
{
CoachHandoffRecommendationStatusTextBlock->SetText(FText::FromString(CoachHandoffRecommendationStatusLine));
}
if (CoachHandoffRecommendationDetailTextBlock != nullptr)
{
CoachHandoffRecommendationDetailTextBlock->SetText(FText::FromString(CoachHandoffRecommendationDetailLine));
}
if (GuidanceReviewHeaderTextBlock != nullptr)
{
GuidanceReviewHeaderTextBlock->SetText(FText::FromString(TEXT("[Guidance Review]")));

View file

@ -481,6 +481,15 @@ protected:
UPROPERTY(Transient)
TObjectPtr<UTextBlock> CoachHandoffStatusTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> CoachHandoffRecommendationHeaderTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> CoachHandoffRecommendationStatusTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> CoachHandoffRecommendationDetailTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidanceReviewHeaderTextBlock = nullptr;