Weight dashboard guidance by decision history

This commit is contained in:
axiomlogicnexus 2026-04-27 16:30:42 +02:00
parent 214aeb42b7
commit 059d34d98a
2 changed files with 234 additions and 1 deletions

View file

@ -1290,6 +1290,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
TEXT("CoachGuidanceOutcomeDetail"),
8
);
GuidanceDecisionWeightingHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachGuidanceDecisionWeightingHeader"),
2
);
GuidanceDecisionWeightingStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachGuidanceDecisionWeightingStatus"),
2
);
GuidanceDecisionWeightingDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachGuidanceDecisionWeightingDetail"),
8
);
GuidanceDecisionHistoryHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
@ -2516,6 +2534,143 @@ EHyperTwistCoachDashboardOutcomeAction UHyperTwistCoachDashboardWidget::DeriveGu
: EHyperTwistCoachDashboardOutcomeAction::RedirectCoachGuidance;
}
FHyperTwistCoachDashboardDecisionWeighting UHyperTwistCoachDashboardWidget::BuildGuidanceDecisionWeighting(
const EHyperTwistCoachDashboardOutcomeAction BaseAction
) const
{
FHyperTwistCoachDashboardDecisionWeighting Weighting;
Weighting.BaseAction = BaseAction;
Weighting.WeightedAction = BaseAction;
if (BaseAction == EHyperTwistCoachDashboardOutcomeAction::None)
{
return Weighting;
}
const FHyperTwistTrainingSessionSummary* DisplayedRunRecap = nullptr;
const FHyperTwistCoachBrief* DisplayedGuidanceComparisonBrief = nullptr;
const FHyperTwistTrainingDeck* DisplayedGuidanceComparisonDeck = nullptr;
FString DisplayedGuidanceComparisonSelectedCaseId;
bool bDisplayedGuidanceComparisonSingleCase = false;
if (!ResolveDisplayedGuidanceComparisonContext(
DisplayedRunRecap,
DisplayedGuidanceComparisonBrief,
DisplayedGuidanceComparisonDeck,
DisplayedGuidanceComparisonSelectedCaseId,
bDisplayedGuidanceComparisonSingleCase)
|| DisplayedRunRecap == nullptr
|| DisplayedGuidanceComparisonDeck == nullptr)
{
return Weighting;
}
EHyperTwistCoachDashboardGuidancePreviewLane DisplayedGuidanceComparisonLane =
EHyperTwistCoachDashboardGuidancePreviewLane::None;
if (!ActiveGuidanceLaunchSessionId.IsEmpty()
&& DisplayedRunRecap->TrainingSessionId == ActiveGuidanceLaunchSessionId)
{
DisplayedGuidanceComparisonLane = ActiveGuidanceLaunchLane;
}
else if (!RetainedGuidanceComparisonSessionId.IsEmpty()
&& DisplayedRunRecap->TrainingSessionId == RetainedGuidanceComparisonSessionId)
{
DisplayedGuidanceComparisonLane = RetainedGuidanceComparisonLane;
}
if (DisplayedGuidanceComparisonLane == EHyperTwistCoachDashboardGuidancePreviewLane::None)
{
return Weighting;
}
constexpr int32 MaxMatchedDecisionEntries = 6;
for (int32 Index = GuidanceDecisionHistoryEntries.Num() - 1;
Index >= 0 && Weighting.MatchingDecisionCount < MaxMatchedDecisionEntries;
--Index)
{
const FHyperTwistCoachDashboardDecisionHistoryEntry& Entry = GuidanceDecisionHistoryEntries[Index];
if (Entry.GuidanceLane != DisplayedGuidanceComparisonLane
|| Entry.bSingleCaseScope != bDisplayedGuidanceComparisonSingleCase)
{
continue;
}
if (bDisplayedGuidanceComparisonSingleCase
&& !DisplayedGuidanceComparisonSelectedCaseId.IsEmpty()
&& Entry.SelectedCaseId != DisplayedGuidanceComparisonSelectedCaseId)
{
continue;
}
++Weighting.MatchingDecisionCount;
Weighting.AverageSuccessRate += Entry.SuccessRate;
if (Weighting.LastAppliedAction == EHyperTwistCoachDashboardOutcomeAction::None)
{
Weighting.LastAppliedAction = Entry.AppliedAction;
}
switch (Entry.AppliedAction)
{
case EHyperTwistCoachDashboardOutcomeAction::RepeatSelectedCase:
++Weighting.RepeatSelectedCaseCount;
break;
case EHyperTwistCoachDashboardOutcomeAction::RepeatPreviewDeck:
++Weighting.RepeatPreviewDeckCount;
break;
case EHyperTwistCoachDashboardOutcomeAction::BroadenPreviewDeck:
++Weighting.BroadenPreviewDeckCount;
break;
case EHyperTwistCoachDashboardOutcomeAction::RedirectCoachGuidance:
++Weighting.RedirectCoachGuidanceCount;
break;
default:
break;
}
}
if (Weighting.MatchingDecisionCount <= 0)
{
return Weighting;
}
Weighting.AverageSuccessRate /= static_cast<float>(Weighting.MatchingDecisionCount);
if (bDisplayedGuidanceComparisonSingleCase)
{
if (BaseAction == EHyperTwistCoachDashboardOutcomeAction::BroadenPreviewDeck
&& Weighting.RepeatSelectedCaseCount >= 2
&& Weighting.AverageSuccessRate < 0.90f)
{
Weighting.WeightedAction = EHyperTwistCoachDashboardOutcomeAction::RepeatSelectedCase;
Weighting.bHasOverride = true;
}
else if (BaseAction == EHyperTwistCoachDashboardOutcomeAction::RepeatSelectedCase
&& Weighting.BroadenPreviewDeckCount >= 2
&& Weighting.AverageSuccessRate >= 0.78f)
{
Weighting.WeightedAction = EHyperTwistCoachDashboardOutcomeAction::BroadenPreviewDeck;
Weighting.bHasOverride = true;
}
}
else
{
if (BaseAction == EHyperTwistCoachDashboardOutcomeAction::RedirectCoachGuidance
&& Weighting.RepeatPreviewDeckCount >= 2
&& Weighting.AverageSuccessRate < 0.80f)
{
Weighting.WeightedAction = EHyperTwistCoachDashboardOutcomeAction::RepeatPreviewDeck;
Weighting.bHasOverride = true;
}
else if (BaseAction == EHyperTwistCoachDashboardOutcomeAction::RepeatPreviewDeck
&& Weighting.RedirectCoachGuidanceCount >= 2
&& Weighting.AverageSuccessRate >= 0.72f)
{
Weighting.WeightedAction = EHyperTwistCoachDashboardOutcomeAction::RedirectCoachGuidance;
Weighting.bHasOverride = true;
}
}
return Weighting;
}
FString UHyperTwistCoachDashboardWidget::BuildGuidanceOutcomeActionLabel(
const EHyperTwistCoachDashboardOutcomeAction Action) const
{
@ -3239,7 +3394,13 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
);
}
}
const EHyperTwistCoachDashboardOutcomeAction GuidanceOutcomeAction = DeriveGuidanceOutcomeAction();
const EHyperTwistCoachDashboardOutcomeAction BaseGuidanceOutcomeAction = DeriveGuidanceOutcomeAction();
const FHyperTwistCoachDashboardDecisionWeighting GuidanceDecisionWeighting =
BuildGuidanceDecisionWeighting(BaseGuidanceOutcomeAction);
const EHyperTwistCoachDashboardOutcomeAction GuidanceOutcomeAction =
GuidanceDecisionWeighting.WeightedAction != EHyperTwistCoachDashboardOutcomeAction::None
? GuidanceDecisionWeighting.WeightedAction
: BaseGuidanceOutcomeAction;
const bool bCanRedirectGuidanceOutcome =
CachedCoachPanelState.bCanStartCoachFollowUpRun
|| CachedCoachPanelState.bCanStartQueuedRun
@ -3253,6 +3414,30 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
&& (GuidanceOutcomeAction != EHyperTwistCoachDashboardOutcomeAction::RedirectCoachGuidance
|| bCanRedirectGuidanceOutcome);
const FString GuidanceOutcomeActionLabel = BuildGuidanceOutcomeActionLabel(GuidanceOutcomeAction);
const FString GuidanceDecisionWeightingStatusLine =
GuidanceDecisionWeighting.IsStructurallyValid()
? FString::Printf(
TEXT("Weighting: %d matching decisions | Base: %s | Weighted: %s | Last applied: %s"),
GuidanceDecisionWeighting.MatchingDecisionCount,
*BuildGuidanceOutcomeActionLabel(GuidanceDecisionWeighting.BaseAction),
*BuildGuidanceOutcomeActionLabel(GuidanceDecisionWeighting.WeightedAction),
*BuildGuidanceOutcomeActionLabel(GuidanceDecisionWeighting.LastAppliedAction)
)
: TEXT("Weighting: no matching authored decision history yet; the current outcome action is recap-derived only.");
const FString GuidanceDecisionWeightingDetailLine =
GuidanceDecisionWeighting.IsStructurallyValid()
? FString::Printf(
TEXT("Decision weighting for this lane/scope: repeat case %d | repeat deck %d | broaden %d | redirect %d | Avg success %.2f | %s"),
GuidanceDecisionWeighting.RepeatSelectedCaseCount,
GuidanceDecisionWeighting.RepeatPreviewDeckCount,
GuidanceDecisionWeighting.BroadenPreviewDeckCount,
GuidanceDecisionWeighting.RedirectCoachGuidanceCount,
GuidanceDecisionWeighting.AverageSuccessRate,
GuidanceDecisionWeighting.bHasOverride
? TEXT("Recent authored choices adjusted the current guidance-outcome action.")
: TEXT("Recent authored choices reinforced the recap-derived guidance-outcome action.")
)
: TEXT("Weighting detail: apply outcome actions on the same guidance lane and scope to let the dashboard bias future recommendations.");
const FHyperTwistCoachDashboardDecisionHistoryEntry* SelectedGuidanceDecisionEntry =
SelectedGuidanceDecisionIndex >= 0
&& SelectedGuidanceDecisionIndex < GuidanceDecisionHistoryEntries.Num()
@ -3440,6 +3625,18 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
{
GuidanceOutcomeActionButton->SetIsEnabled(bCanApplyGuidanceOutcomeAction);
}
if (GuidanceDecisionWeightingHeaderTextBlock != nullptr)
{
GuidanceDecisionWeightingHeaderTextBlock->SetText(FText::FromString(TEXT("[Decision Weighting]")));
}
if (GuidanceDecisionWeightingStatusTextBlock != nullptr)
{
GuidanceDecisionWeightingStatusTextBlock->SetText(FText::FromString(GuidanceDecisionWeightingStatusLine));
}
if (GuidanceDecisionWeightingDetailTextBlock != nullptr)
{
GuidanceDecisionWeightingDetailTextBlock->SetText(FText::FromString(GuidanceDecisionWeightingDetailLine));
}
if (GuidanceDecisionHistoryHeaderTextBlock != nullptr)
{
GuidanceDecisionHistoryHeaderTextBlock->SetText(FText::FromString(TEXT("[Decision History]")));

View file

@ -60,6 +60,30 @@ struct FHyperTwistCoachDashboardDecisionHistoryEntry
}
};
struct FHyperTwistCoachDashboardDecisionWeighting
{
int32 MatchingDecisionCount = 0;
int32 RepeatSelectedCaseCount = 0;
int32 RepeatPreviewDeckCount = 0;
int32 BroadenPreviewDeckCount = 0;
int32 RedirectCoachGuidanceCount = 0;
EHyperTwistCoachDashboardOutcomeAction BaseAction =
EHyperTwistCoachDashboardOutcomeAction::None;
EHyperTwistCoachDashboardOutcomeAction WeightedAction =
EHyperTwistCoachDashboardOutcomeAction::None;
EHyperTwistCoachDashboardOutcomeAction LastAppliedAction =
EHyperTwistCoachDashboardOutcomeAction::None;
float AverageSuccessRate = 0.0f;
bool bHasOverride = false;
bool IsStructurallyValid() const
{
return MatchingDecisionCount > 0
&& BaseAction != EHyperTwistCoachDashboardOutcomeAction::None
&& WeightedAction != EHyperTwistCoachDashboardOutcomeAction::None;
}
};
UCLASS(BlueprintType, Blueprintable)
class UNREALHYPERTWIST_API UHyperTwistCoachDashboardWidget : public UHyperTwistTrainingPanelWidget
{
@ -261,6 +285,15 @@ protected:
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidanceOutcomeActionButtonLabel = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidanceDecisionWeightingHeaderTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidanceDecisionWeightingStatusTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidanceDecisionWeightingDetailTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidanceDecisionHistoryHeaderTextBlock = nullptr;
@ -726,6 +759,9 @@ private:
bool& bOutDisplayedGuidanceComparisonSingleCase
) const;
EHyperTwistCoachDashboardOutcomeAction DeriveGuidanceOutcomeAction() const;
FHyperTwistCoachDashboardDecisionWeighting BuildGuidanceDecisionWeighting(
EHyperTwistCoachDashboardOutcomeAction BaseAction
) const;
FString BuildGuidanceOutcomeActionLabel(EHyperTwistCoachDashboardOutcomeAction Action) const;
FHyperTwistTrainingDeck BuildGuidanceOutcomeSelectedCaseDeck() const;
void RecordGuidanceOutcomeDecision(