Prefer dashboard guidance lane by decision history
This commit is contained in:
parent
059d34d98a
commit
41a828c909
2 changed files with 233 additions and 0 deletions
|
|
@ -144,6 +144,7 @@ void UHyperTwistCoachDashboardWidget::RefreshCoachDashboardView()
|
|||
SyncSelectedQueueEntry();
|
||||
SyncSelectedAttempt();
|
||||
SyncSelectedGuidanceDecisionHistoryEntry();
|
||||
SyncPreferredGuidancePreviewLane(false);
|
||||
UpdateDashboardPresentation();
|
||||
}
|
||||
|
||||
|
|
@ -1308,6 +1309,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
TEXT("CoachGuidanceDecisionWeightingDetail"),
|
||||
8
|
||||
);
|
||||
GuidancePreferenceHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachGuidancePreferenceHeader"),
|
||||
2
|
||||
);
|
||||
GuidancePreferenceStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachGuidancePreferenceStatus"),
|
||||
2
|
||||
);
|
||||
GuidancePreferenceDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachGuidancePreferenceDetail"),
|
||||
8
|
||||
);
|
||||
GuidanceDecisionHistoryHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
|
|
@ -2349,6 +2368,30 @@ void UHyperTwistCoachDashboardWidget::SyncSelectedGuidanceDecisionHistoryEntry()
|
|||
}
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::SyncPreferredGuidancePreviewLane(const bool bForceAdoptPreferredLane)
|
||||
{
|
||||
const FHyperTwistCoachDashboardGuidanceLanePreference Preference = BuildGuidanceLanePreference();
|
||||
if (!Preference.IsStructurallyValid())
|
||||
{
|
||||
if (ActiveGuidancePreviewLane != EHyperTwistCoachDashboardGuidancePreviewLane::None
|
||||
&& GetActiveGuidancePreviewDeck() == nullptr)
|
||||
{
|
||||
ActiveGuidancePreviewLane = EHyperTwistCoachDashboardGuidancePreviewLane::None;
|
||||
SelectedGuidancePreviewCaseIndex = INDEX_NONE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const bool bHasActivePreviewDeck = GetActiveGuidancePreviewDeck() != nullptr;
|
||||
if (bForceAdoptPreferredLane
|
||||
|| ActiveGuidancePreviewLane == EHyperTwistCoachDashboardGuidancePreviewLane::None
|
||||
|| !bHasActivePreviewDeck)
|
||||
{
|
||||
ActiveGuidancePreviewLane = Preference.PreferredLane;
|
||||
SyncSelectedGuidancePreviewCase();
|
||||
}
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingDeck* UHyperTwistCoachDashboardWidget::GetActiveGuidancePreviewDeck() const
|
||||
{
|
||||
switch (ActiveGuidancePreviewLane)
|
||||
|
|
@ -2671,6 +2714,125 @@ FHyperTwistCoachDashboardDecisionWeighting UHyperTwistCoachDashboardWidget::Buil
|
|||
return Weighting;
|
||||
}
|
||||
|
||||
FHyperTwistCoachDashboardGuidanceLanePreference UHyperTwistCoachDashboardWidget::BuildGuidanceLanePreference() const
|
||||
{
|
||||
FHyperTwistCoachDashboardGuidanceLanePreference Preference;
|
||||
const bool bHasRecommendedDeck = CachedCoachRecommendedDeck.IsStructurallyValid();
|
||||
const bool bHasFollowUpDeck = CachedCoachFollowUpDeck.IsStructurallyValid();
|
||||
if (!bHasRecommendedDeck && !bHasFollowUpDeck)
|
||||
{
|
||||
return Preference;
|
||||
}
|
||||
|
||||
if (bHasRecommendedDeck && !bHasFollowUpDeck)
|
||||
{
|
||||
Preference.PreferredLane = EHyperTwistCoachDashboardGuidancePreviewLane::Recommended;
|
||||
return Preference;
|
||||
}
|
||||
|
||||
if (!bHasRecommendedDeck && bHasFollowUpDeck)
|
||||
{
|
||||
Preference.PreferredLane = EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp;
|
||||
return Preference;
|
||||
}
|
||||
|
||||
float RecommendedSuccessRateSum = 0.0f;
|
||||
float FollowUpSuccessRateSum = 0.0f;
|
||||
constexpr int32 MaxDecisionEntriesToInspect = 8;
|
||||
int32 InspectedDecisionEntries = 0;
|
||||
for (int32 Index = GuidanceDecisionHistoryEntries.Num() - 1;
|
||||
Index >= 0 && InspectedDecisionEntries < MaxDecisionEntriesToInspect;
|
||||
--Index)
|
||||
{
|
||||
const FHyperTwistCoachDashboardDecisionHistoryEntry& Entry = GuidanceDecisionHistoryEntries[Index];
|
||||
if (Entry.GuidanceLane == EHyperTwistCoachDashboardGuidancePreviewLane::Recommended)
|
||||
{
|
||||
++Preference.RecommendedDecisionCount;
|
||||
RecommendedSuccessRateSum += Entry.SuccessRate;
|
||||
switch (Entry.AppliedAction)
|
||||
{
|
||||
case EHyperTwistCoachDashboardOutcomeAction::RepeatSelectedCase:
|
||||
case EHyperTwistCoachDashboardOutcomeAction::RepeatPreviewDeck:
|
||||
case EHyperTwistCoachDashboardOutcomeAction::BroadenPreviewDeck:
|
||||
++Preference.RecommendedContinueBias;
|
||||
break;
|
||||
case EHyperTwistCoachDashboardOutcomeAction::RedirectCoachGuidance:
|
||||
Preference.RecommendedContinueBias -= 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
++InspectedDecisionEntries;
|
||||
}
|
||||
else if (Entry.GuidanceLane == EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp)
|
||||
{
|
||||
++Preference.FollowUpDecisionCount;
|
||||
FollowUpSuccessRateSum += Entry.SuccessRate;
|
||||
switch (Entry.AppliedAction)
|
||||
{
|
||||
case EHyperTwistCoachDashboardOutcomeAction::RepeatSelectedCase:
|
||||
case EHyperTwistCoachDashboardOutcomeAction::RepeatPreviewDeck:
|
||||
case EHyperTwistCoachDashboardOutcomeAction::BroadenPreviewDeck:
|
||||
++Preference.FollowUpContinueBias;
|
||||
break;
|
||||
case EHyperTwistCoachDashboardOutcomeAction::RedirectCoachGuidance:
|
||||
Preference.FollowUpContinueBias -= 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
++InspectedDecisionEntries;
|
||||
}
|
||||
}
|
||||
|
||||
if (Preference.RecommendedDecisionCount > 0)
|
||||
{
|
||||
Preference.RecommendedAverageSuccessRate =
|
||||
RecommendedSuccessRateSum / static_cast<float>(Preference.RecommendedDecisionCount);
|
||||
}
|
||||
if (Preference.FollowUpDecisionCount > 0)
|
||||
{
|
||||
Preference.FollowUpAverageSuccessRate =
|
||||
FollowUpSuccessRateSum / static_cast<float>(Preference.FollowUpDecisionCount);
|
||||
}
|
||||
|
||||
Preference.bHasDecisionSignal =
|
||||
Preference.RecommendedDecisionCount > 0 || Preference.FollowUpDecisionCount > 0;
|
||||
|
||||
float RecommendedLaneScore = 1.0f;
|
||||
float FollowUpLaneScore = CachedCoachPanelState.bHasFollowUpGuidance ? 1.25f : 1.0f;
|
||||
if (Preference.RecommendedDecisionCount > 0)
|
||||
{
|
||||
RecommendedLaneScore += static_cast<float>(Preference.RecommendedContinueBias);
|
||||
RecommendedLaneScore += Preference.RecommendedAverageSuccessRate;
|
||||
}
|
||||
if (Preference.FollowUpDecisionCount > 0)
|
||||
{
|
||||
FollowUpLaneScore += static_cast<float>(Preference.FollowUpContinueBias);
|
||||
FollowUpLaneScore += Preference.FollowUpAverageSuccessRate;
|
||||
}
|
||||
|
||||
if (FollowUpLaneScore > RecommendedLaneScore)
|
||||
{
|
||||
Preference.PreferredLane = EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp;
|
||||
}
|
||||
else if (RecommendedLaneScore > FollowUpLaneScore)
|
||||
{
|
||||
Preference.PreferredLane = EHyperTwistCoachDashboardGuidancePreviewLane::Recommended;
|
||||
}
|
||||
else
|
||||
{
|
||||
Preference.PreferredLane =
|
||||
ActiveGuidancePreviewLane != EHyperTwistCoachDashboardGuidancePreviewLane::None
|
||||
? ActiveGuidancePreviewLane
|
||||
: (CachedCoachPanelState.bHasFollowUpGuidance
|
||||
? EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp
|
||||
: EHyperTwistCoachDashboardGuidancePreviewLane::Recommended);
|
||||
}
|
||||
|
||||
return Preference;
|
||||
}
|
||||
|
||||
FString UHyperTwistCoachDashboardWidget::BuildGuidanceOutcomeActionLabel(
|
||||
const EHyperTwistCoachDashboardOutcomeAction Action) const
|
||||
{
|
||||
|
|
@ -2777,6 +2939,7 @@ void UHyperTwistCoachDashboardWidget::RecordGuidanceOutcomeDecision(
|
|||
GuidanceDecisionHistoryEntries.RemoveAt(0, ExcessCount, EAllowShrinking::No);
|
||||
}
|
||||
SelectedGuidanceDecisionIndex = GuidanceDecisionHistoryEntries.Num() - 1;
|
||||
SyncPreferredGuidancePreviewLane(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3153,6 +3316,35 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
: (ActiveGuidancePreviewLane == EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp
|
||||
? TEXT("follow-up")
|
||||
: TEXT("none"));
|
||||
const FHyperTwistCoachDashboardGuidanceLanePreference GuidanceLanePreference =
|
||||
BuildGuidanceLanePreference();
|
||||
const FString PreferredGuidanceLaneLabel =
|
||||
GuidanceLanePreference.PreferredLane == EHyperTwistCoachDashboardGuidancePreviewLane::Recommended
|
||||
? TEXT("recommended")
|
||||
: (GuidanceLanePreference.PreferredLane == EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp
|
||||
? TEXT("follow-up")
|
||||
: TEXT("none"));
|
||||
const FString GuidancePreferenceStatusLine =
|
||||
GuidanceLanePreference.IsStructurallyValid()
|
||||
? FString::Printf(
|
||||
TEXT("Preference: next surfaced lane %s | Active preview: %s | Source: %s"),
|
||||
*PreferredGuidanceLaneLabel,
|
||||
*ActivePreviewLaneLabel,
|
||||
GuidanceLanePreference.bHasDecisionSignal ? TEXT("decision-informed") : TEXT("availability fallback")
|
||||
)
|
||||
: TEXT("Preference: no coach preview lane is currently available.");
|
||||
const FString GuidancePreferenceDetailLine =
|
||||
GuidanceLanePreference.IsStructurallyValid()
|
||||
? FString::Printf(
|
||||
TEXT("Recommended lane: %d decisions | Continue bias %d | Avg success %.2f || Follow-up lane: %d decisions | Continue bias %d | Avg success %.2f | The dashboard adopts this preferred lane whenever it needs to surface the next guidance preview."),
|
||||
GuidanceLanePreference.RecommendedDecisionCount,
|
||||
GuidanceLanePreference.RecommendedContinueBias,
|
||||
GuidanceLanePreference.RecommendedAverageSuccessRate,
|
||||
GuidanceLanePreference.FollowUpDecisionCount,
|
||||
GuidanceLanePreference.FollowUpContinueBias,
|
||||
GuidanceLanePreference.FollowUpAverageSuccessRate
|
||||
)
|
||||
: TEXT("Preference detail: build recommended or follow-up guidance to let the dashboard choose a next surfaced lane.");
|
||||
const FString GuidancePreviewSelectionLine =
|
||||
SelectedGuidancePreviewCase != nullptr && ActivePreviewDeck != nullptr
|
||||
? FString::Printf(
|
||||
|
|
@ -3637,6 +3829,18 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
{
|
||||
GuidanceDecisionWeightingDetailTextBlock->SetText(FText::FromString(GuidanceDecisionWeightingDetailLine));
|
||||
}
|
||||
if (GuidancePreferenceHeaderTextBlock != nullptr)
|
||||
{
|
||||
GuidancePreferenceHeaderTextBlock->SetText(FText::FromString(TEXT("[Guidance Preference]")));
|
||||
}
|
||||
if (GuidancePreferenceStatusTextBlock != nullptr)
|
||||
{
|
||||
GuidancePreferenceStatusTextBlock->SetText(FText::FromString(GuidancePreferenceStatusLine));
|
||||
}
|
||||
if (GuidancePreferenceDetailTextBlock != nullptr)
|
||||
{
|
||||
GuidancePreferenceDetailTextBlock->SetText(FText::FromString(GuidancePreferenceDetailLine));
|
||||
}
|
||||
if (GuidanceDecisionHistoryHeaderTextBlock != nullptr)
|
||||
{
|
||||
GuidanceDecisionHistoryHeaderTextBlock->SetText(FText::FromString(TEXT("[Decision History]")));
|
||||
|
|
|
|||
|
|
@ -84,6 +84,24 @@ struct FHyperTwistCoachDashboardDecisionWeighting
|
|||
}
|
||||
};
|
||||
|
||||
struct FHyperTwistCoachDashboardGuidanceLanePreference
|
||||
{
|
||||
EHyperTwistCoachDashboardGuidancePreviewLane PreferredLane =
|
||||
EHyperTwistCoachDashboardGuidancePreviewLane::None;
|
||||
int32 RecommendedDecisionCount = 0;
|
||||
int32 FollowUpDecisionCount = 0;
|
||||
int32 RecommendedContinueBias = 0;
|
||||
int32 FollowUpContinueBias = 0;
|
||||
float RecommendedAverageSuccessRate = 0.0f;
|
||||
float FollowUpAverageSuccessRate = 0.0f;
|
||||
bool bHasDecisionSignal = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return PreferredLane != EHyperTwistCoachDashboardGuidancePreviewLane::None;
|
||||
}
|
||||
};
|
||||
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class UNREALHYPERTWIST_API UHyperTwistCoachDashboardWidget : public UHyperTwistTrainingPanelWidget
|
||||
{
|
||||
|
|
@ -294,6 +312,15 @@ protected:
|
|||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> GuidanceDecisionWeightingDetailTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> GuidancePreferenceHeaderTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> GuidancePreferenceStatusTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> GuidancePreferenceDetailTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> GuidanceDecisionHistoryHeaderTextBlock = nullptr;
|
||||
|
||||
|
|
@ -739,6 +766,7 @@ private:
|
|||
void SyncRetainedRunRecap();
|
||||
void SyncSelectedGuidancePreviewCase();
|
||||
void SyncSelectedGuidanceDecisionHistoryEntry();
|
||||
void SyncPreferredGuidancePreviewLane(bool bForceAdoptPreferredLane);
|
||||
void UpdateDashboardPresentation();
|
||||
void RefreshLiveAttemptClock();
|
||||
FString BuildDefaultDeferredUntilUtc() const;
|
||||
|
|
@ -762,6 +790,7 @@ private:
|
|||
FHyperTwistCoachDashboardDecisionWeighting BuildGuidanceDecisionWeighting(
|
||||
EHyperTwistCoachDashboardOutcomeAction BaseAction
|
||||
) const;
|
||||
FHyperTwistCoachDashboardGuidanceLanePreference BuildGuidanceLanePreference() const;
|
||||
FString BuildGuidanceOutcomeActionLabel(EHyperTwistCoachDashboardOutcomeAction Action) const;
|
||||
FHyperTwistTrainingDeck BuildGuidanceOutcomeSelectedCaseDeck() const;
|
||||
void RecordGuidanceOutcomeDecision(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue