Explain coach guidance on dashboard

This commit is contained in:
axiomlogicnexus 2026-04-27 15:30:53 +02:00
parent e6322c0088
commit f3339e8ff4
2 changed files with 136 additions and 0 deletions

View file

@ -1029,6 +1029,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
TEXT("CoachGuidanceReviewHeader"),
2
);
GuidanceRationaleHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachGuidanceRationaleHeader"),
2
);
GuidanceRationaleStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachGuidanceRationaleStatus"),
2
);
GuidanceRationaleDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachGuidanceRationaleDetail"),
8
);
GuidancePreviewSelectionTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
@ -1957,6 +1975,23 @@ const FHyperTwistTrainingDeck* UHyperTwistCoachDashboardWidget::GetActiveGuidanc
}
}
const FHyperTwistCoachBrief* UHyperTwistCoachDashboardWidget::GetActiveGuidancePreviewBrief() const
{
switch (ActiveGuidancePreviewLane)
{
case EHyperTwistCoachDashboardGuidancePreviewLane::Recommended:
return CachedCoachBrief.Headline.IsEmpty() && CachedCoachBrief.PrimaryActionLabel.IsEmpty()
? nullptr
: &CachedCoachBrief;
case EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp:
return CachedCoachFollowUpBrief.Headline.IsEmpty() && CachedCoachFollowUpBrief.PrimaryActionLabel.IsEmpty()
? nullptr
: &CachedCoachFollowUpBrief;
default:
return nullptr;
}
}
FHyperTwistTrainingDeck UHyperTwistCoachDashboardWidget::BuildSelectedGuidancePreviewCaseDeck() const
{
const FHyperTwistTrainingDeck* Deck = GetActiveGuidancePreviewDeck();
@ -2313,6 +2348,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
&& SelectedGuidancePreviewCaseIndex < ActivePreviewDeck->Cases.Num()
? &ActivePreviewDeck->Cases[SelectedGuidancePreviewCaseIndex]
: nullptr;
const FHyperTwistCoachBrief* ActivePreviewBrief = GetActiveGuidancePreviewBrief();
const FString ActivePreviewLaneLabel =
ActiveGuidancePreviewLane == EHyperTwistCoachDashboardGuidancePreviewLane::Recommended
? TEXT("recommended")
@ -2342,6 +2378,84 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
SelectedGuidancePreviewCase->CanonicalNotation.IsEmpty() ? TEXT("n/a") : *SelectedGuidancePreviewCase->CanonicalNotation
)
: TEXT("Preview case: none selected yet.");
bool bSelectedPreviewCaseIsExplicitFocus = false;
int32 SelectedPreviewCaseSignalCount = 0;
FString SelectedPreviewCaseSignalLine = TEXT("none");
FString SignalSummaryLine = TEXT("none");
if (ActivePreviewBrief != nullptr)
{
if (SelectedGuidancePreviewCase != nullptr)
{
bSelectedPreviewCaseIsExplicitFocus =
ActivePreviewBrief->FocusCaseIds.Contains(SelectedGuidancePreviewCase->CaseId);
}
TArray<FString> SignalSummaryParts;
SignalSummaryParts.Reserve(FMath::Min(ActivePreviewBrief->Signals.Num(), 2));
for (const FHyperTwistCoachSignal& Signal : ActivePreviewBrief->Signals)
{
if (SelectedGuidancePreviewCase != nullptr
&& Signal.SuggestedCaseIds.Contains(SelectedGuidancePreviewCase->CaseId))
{
++SelectedPreviewCaseSignalCount;
if (SelectedPreviewCaseSignalLine == TEXT("none"))
{
SelectedPreviewCaseSignalLine = FString::Printf(
TEXT("%s | %s"),
Signal.Headline.IsEmpty() ? TEXT("signal") : *Signal.Headline,
Signal.Detail.IsEmpty() ? TEXT("no detail") : *Signal.Detail
);
}
}
if (SignalSummaryParts.Num() < 2)
{
SignalSummaryParts.Add(FString::Printf(
TEXT("%s (%.2f)"),
Signal.Headline.IsEmpty() ? TEXT("signal") : *Signal.Headline,
Signal.Score
));
}
}
if (SignalSummaryParts.Num() > 0)
{
SignalSummaryLine = FString::Join(SignalSummaryParts, TEXT(" | "));
}
}
const FString GuidanceRationaleStatusLine =
ActivePreviewBrief != nullptr
? FString::Printf(
TEXT("Rationale: %s lane | Priority: %s (%.2f) | Focus: %s | Mode: %s | Selection: %s"),
*ActivePreviewLaneLabel,
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(ActivePreviewBrief->Priority),
ActivePreviewBrief->PriorityScore,
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(ActivePreviewBrief->FocusLane),
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(ActivePreviewBrief->SuggestedMode),
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(ActivePreviewBrief->SuggestedSelectionPolicy)
)
: FString::Printf(
TEXT("Rationale: %s lane | build a recommended or follow-up preview to inspect the coach rationale"),
*ActivePreviewLaneLabel
);
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"),
ActivePreviewBrief->Headline.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->Headline,
ActivePreviewBrief->PrimaryActionLabel.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->PrimaryActionLabel,
ActivePreviewBrief->FocusDeckId.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->FocusDeckId,
ActivePreviewBrief->FocusMethodSegmentId.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->FocusMethodSegmentId,
ActivePreviewBrief->FocusCaseIds.Num(),
ActivePreviewBrief->bNeedsCoachReview ? TEXT("yes") : TEXT("no"),
ActivePreviewBrief->bNeedsCadenceRecovery ? TEXT("yes") : TEXT("no"),
ActivePreviewBrief->bNeedsVarietyRotation ? TEXT("yes") : TEXT("no"),
bSelectedPreviewCaseIsExplicitFocus ? TEXT("yes") : TEXT("no"),
SelectedPreviewCaseSignalCount,
*SelectedPreviewCaseSignalLine,
*SignalSummaryLine
)
: TEXT("Rationale detail: unavailable until a guidance preview lane is active.");
const bool bCanStartPreviewDeck =
!bHasLiveAttemptTimer && !bHasActiveRun && ActivePreviewDeck != nullptr && ActivePreviewDeck->IsStructurallyValid();
const bool bCanStartPreviewCase = bCanStartPreviewDeck && SelectedGuidancePreviewCase != nullptr;
@ -2486,6 +2600,18 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
{
GuidanceReviewHeaderTextBlock->SetText(FText::FromString(TEXT("[Guidance Review]")));
}
if (GuidanceRationaleHeaderTextBlock != nullptr)
{
GuidanceRationaleHeaderTextBlock->SetText(FText::FromString(TEXT("[Guidance Rationale]")));
}
if (GuidanceRationaleStatusTextBlock != nullptr)
{
GuidanceRationaleStatusTextBlock->SetText(FText::FromString(GuidanceRationaleStatusLine));
}
if (GuidanceRationaleDetailTextBlock != nullptr)
{
GuidanceRationaleDetailTextBlock->SetText(FText::FromString(GuidanceRationaleDetailLine));
}
if (GuidancePreviewSelectionTextBlock != nullptr)
{
GuidancePreviewSelectionTextBlock->SetText(FText::FromString(GuidancePreviewSelectionLine));

View file

@ -197,6 +197,15 @@ protected:
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidanceReviewHeaderTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidanceRationaleHeaderTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidanceRationaleStatusTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidanceRationaleDetailTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidancePreviewSelectionTextBlock = nullptr;
@ -603,6 +612,7 @@ private:
const FHyperTwistTrainingCoachSessionQueueStateEntry* FindSelectedQueueEntry() const;
const FHyperTwistTrainingTimingPolicy* FindActiveTimingPolicy() const;
const FHyperTwistTrainingDeck* GetActiveGuidancePreviewDeck() const;
const FHyperTwistCoachBrief* GetActiveGuidancePreviewBrief() const;
FHyperTwistTrainingDeck BuildSelectedGuidancePreviewCaseDeck() const;
bool IsLiveAttemptInspectionPhase() const;
int32 GetMaxManualSplitCaptureCount() const;