Compare recap against coach guidance

This commit is contained in:
axiomlogicnexus 2026-04-27 15:36:27 +02:00
parent f3339e8ff4
commit 2b01fdd8c4
2 changed files with 203 additions and 2 deletions

View file

@ -382,12 +382,21 @@ FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::StartActiveGuidance
: (ActiveGuidancePreviewLane == EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp
? TEXT("_preview_follow_up")
: TEXT("_preview"));
const FString SessionId = DefaultSessionId + SessionSuffix;
const FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck(
*Deck,
DefaultUserId,
DefaultSessionId + SessionSuffix,
SessionId,
DefaultMode
);
CaptureActiveGuidanceLaunchContext(
RunState.Session.IsStructurallyValid() ? RunState.Session.TrainingSessionId : SessionId,
*Deck,
false,
SelectedGuidancePreviewCaseIndex >= 0 && SelectedGuidancePreviewCaseIndex < Deck->Cases.Num()
? Deck->Cases[SelectedGuidancePreviewCaseIndex].CaseId
: FString()
);
SyncRetainedRunRecap();
SyncSelectedQueueEntry();
SyncSelectedAttempt();
@ -410,12 +419,19 @@ FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::StartSelectedGuidan
: (ActiveGuidancePreviewLane == EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp
? TEXT("_preview_case_follow_up")
: TEXT("_preview_case"));
const FString SessionId = DefaultSessionId + SessionSuffix;
const FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck(
Deck,
DefaultUserId,
DefaultSessionId + SessionSuffix,
SessionId,
DefaultMode
);
CaptureActiveGuidanceLaunchContext(
RunState.Session.IsStructurallyValid() ? RunState.Session.TrainingSessionId : SessionId,
Deck,
true,
Deck.Cases.Num() > 0 ? Deck.Cases[0].CaseId : FString()
);
SyncRetainedRunRecap();
SyncSelectedQueueEntry();
SyncSelectedAttempt();
@ -464,6 +480,13 @@ void UHyperTwistCoachDashboardWidget::ClearRetainedRunRecap()
RetainedRunRecapUserId.Reset();
RetainedRunRecapSessionId.Reset();
RetainedRunRecapMode = EHyperTwistTrainingDeliveryMode::Timer;
bHasRetainedGuidanceComparison = false;
RetainedGuidanceComparisonSessionId.Reset();
RetainedGuidanceComparisonLane = EHyperTwistCoachDashboardGuidancePreviewLane::None;
bRetainedGuidanceComparisonSingleCase = false;
RetainedGuidanceComparisonBrief = FHyperTwistCoachBrief();
RetainedGuidanceComparisonDeck = FHyperTwistTrainingDeck();
RetainedGuidanceComparisonSelectedCaseId.Reset();
UpdateDashboardPresentation();
}
@ -1011,6 +1034,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
TEXT("CoachRunRecapDetail"),
2
);
GuidanceComparisonHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachGuidanceComparisonHeader"),
2
);
GuidanceComparisonStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachGuidanceComparisonStatus"),
2
);
GuidanceComparisonDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachGuidanceComparisonDetail"),
8
);
CoachHandoffHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
@ -1943,6 +1984,18 @@ void UHyperTwistCoachDashboardWidget::SyncRetainedRunRecap()
RetainedRunRecapUserId = CachedRunState.Session.UserId;
RetainedRunRecapSessionId = CachedRunState.Session.TrainingSessionId;
RetainedRunRecapMode = CachedRunState.Session.Mode;
if (!ActiveGuidanceLaunchSessionId.IsEmpty()
&& ActiveGuidanceLaunchSessionId == RetainedRunRecapSessionId
&& ActiveGuidanceLaunchLane != EHyperTwistCoachDashboardGuidancePreviewLane::None)
{
bHasRetainedGuidanceComparison = true;
RetainedGuidanceComparisonSessionId = ActiveGuidanceLaunchSessionId;
RetainedGuidanceComparisonLane = ActiveGuidanceLaunchLane;
bRetainedGuidanceComparisonSingleCase = bActiveGuidanceLaunchSingleCase;
RetainedGuidanceComparisonBrief = ActiveGuidanceLaunchBrief;
RetainedGuidanceComparisonDeck = ActiveGuidanceLaunchDeck;
RetainedGuidanceComparisonSelectedCaseId = ActiveGuidanceLaunchSelectedCaseId;
}
}
}
@ -1992,6 +2045,25 @@ const FHyperTwistCoachBrief* UHyperTwistCoachDashboardWidget::GetActiveGuidanceP
}
}
void UHyperTwistCoachDashboardWidget::CaptureActiveGuidanceLaunchContext(
const FString& SessionId,
const FHyperTwistTrainingDeck& Deck,
const bool bSingleCaseLaunch,
const FString& SelectedCaseId
)
{
ActiveGuidanceLaunchSessionId = SessionId;
ActiveGuidanceLaunchLane = ActiveGuidancePreviewLane;
bActiveGuidanceLaunchSingleCase = bSingleCaseLaunch;
ActiveGuidanceLaunchDeck = Deck;
ActiveGuidanceLaunchSelectedCaseId = SelectedCaseId;
ActiveGuidanceLaunchBrief = FHyperTwistCoachBrief();
if (const FHyperTwistCoachBrief* ActiveBrief = GetActiveGuidancePreviewBrief())
{
ActiveGuidanceLaunchBrief = *ActiveBrief;
}
}
FHyperTwistTrainingDeck UHyperTwistCoachDashboardWidget::BuildSelectedGuidancePreviewCaseDeck() const
{
const FHyperTwistTrainingDeck* Deck = GetActiveGuidancePreviewDeck();
@ -2225,6 +2297,12 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
const FHyperTwistTrainingSessionSummary* DisplayedRunRecap = bHasCurrentRunRecap
? &CachedRunSummary
: (bHasRetainedRunRecap ? &RetainedRunRecapSummary : nullptr);
const FHyperTwistTrainingDeck* DisplayedRunRecapDeck = bHasCurrentRunRecap
? &CachedRunState.ActiveDeck
: (bHasRetainedRunRecap ? &RetainedRunRecapDeck : nullptr);
const EHyperTwistTrainingDeliveryMode DisplayedRunRecapMode = bHasCurrentRunRecap
? CachedRunState.Session.Mode
: RetainedRunRecapMode;
const bool bHasDisplayedRunRecap = DisplayedRunRecap != nullptr
&& !DisplayedRunRecap->TrainingSessionId.IsEmpty();
const FString RunRecapSourceLabel = bHasCurrentRunRecap
@ -2236,6 +2314,36 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|| CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed
|| CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Aborted);
const bool bCanDismissStoredRecap = !bHasCurrentRunRecap && bHasRetainedRunRecap;
const bool bHasActiveGuidanceComparison =
bHasCurrentRunRecap
&& DisplayedRunRecap != nullptr
&& !ActiveGuidanceLaunchSessionId.IsEmpty()
&& DisplayedRunRecap->TrainingSessionId == ActiveGuidanceLaunchSessionId
&& ActiveGuidanceLaunchLane != EHyperTwistCoachDashboardGuidancePreviewLane::None;
const bool bHasDisplayedRetainedGuidanceComparison =
!bHasCurrentRunRecap
&& bHasRetainedGuidanceComparison
&& DisplayedRunRecap != nullptr
&& !RetainedGuidanceComparisonSessionId.IsEmpty()
&& DisplayedRunRecap->TrainingSessionId == RetainedGuidanceComparisonSessionId
&& RetainedGuidanceComparisonLane != EHyperTwistCoachDashboardGuidancePreviewLane::None;
const FHyperTwistCoachBrief* DisplayedGuidanceComparisonBrief = bHasActiveGuidanceComparison
? &ActiveGuidanceLaunchBrief
: (bHasDisplayedRetainedGuidanceComparison ? &RetainedGuidanceComparisonBrief : nullptr);
const FHyperTwistTrainingDeck* DisplayedGuidanceComparisonDeck = bHasActiveGuidanceComparison
? &ActiveGuidanceLaunchDeck
: (bHasDisplayedRetainedGuidanceComparison ? &RetainedGuidanceComparisonDeck : nullptr);
const FString DisplayedGuidanceComparisonSelectedCaseId = bHasActiveGuidanceComparison
? ActiveGuidanceLaunchSelectedCaseId
: (bHasDisplayedRetainedGuidanceComparison ? RetainedGuidanceComparisonSelectedCaseId : FString());
const EHyperTwistCoachDashboardGuidancePreviewLane DisplayedGuidanceComparisonLane = bHasActiveGuidanceComparison
? ActiveGuidanceLaunchLane
: (bHasDisplayedRetainedGuidanceComparison
? RetainedGuidanceComparisonLane
: EHyperTwistCoachDashboardGuidancePreviewLane::None);
const bool bDisplayedGuidanceComparisonSingleCase = bHasActiveGuidanceComparison
? bActiveGuidanceLaunchSingleCase
: (bHasDisplayedRetainedGuidanceComparison ? bRetainedGuidanceComparisonSingleCase : false);
const bool bCanUseCoachHandoff = !bHasLiveAttemptTimer && !bHasActiveRun;
FString CoachHandoffStatusLine;
if (bHasLiveAttemptTimer)
@ -2456,6 +2564,57 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
*SignalSummaryLine
)
: TEXT("Rationale detail: unavailable until a guidance preview lane is active.");
const FString GuidanceComparisonLaneLabel =
DisplayedGuidanceComparisonLane == EHyperTwistCoachDashboardGuidancePreviewLane::Recommended
? TEXT("recommended")
: (DisplayedGuidanceComparisonLane == EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp
? TEXT("follow-up")
: TEXT("none"));
const bool bDisplayedComparisonSelectedCaseMatchesLastCase =
DisplayedRunRecap != nullptr
&& !DisplayedGuidanceComparisonSelectedCaseId.IsEmpty()
&& DisplayedRunRecap->LastCaseId == DisplayedGuidanceComparisonSelectedCaseId;
const int32 GuidanceComparisonExpectedCaseCount =
DisplayedGuidanceComparisonDeck != nullptr ? DisplayedGuidanceComparisonDeck->Cases.Num() : 0;
const FString GuidanceComparisonStatusLine =
DisplayedRunRecap != nullptr
&& DisplayedGuidanceComparisonBrief != nullptr
&& DisplayedGuidanceComparisonDeck != nullptr
? FString::Printf(
TEXT("Comparison: %s recap vs %s preview | Scope: %s | Focus lane: %s | Expected cases: %d | Completed attempts: %d | Success rate: %.2f"),
*RunRecapSourceLabel,
*GuidanceComparisonLaneLabel,
bDisplayedGuidanceComparisonSingleCase ? TEXT("selected case") : TEXT("full preview deck"),
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(DisplayedGuidanceComparisonBrief->FocusLane),
GuidanceComparisonExpectedCaseCount,
DisplayedRunRecap->CompletedAttemptCount,
DisplayedRunRecap->SuccessRate
)
: TEXT("Comparison: complete a run launched from previewed guidance to compare the recap against the original coach intent.");
const FString GuidanceComparisonDetailLine =
DisplayedRunRecap != nullptr
&& DisplayedGuidanceComparisonBrief != nullptr
&& DisplayedGuidanceComparisonDeck != nullptr
? FString::Printf(
TEXT("Launch brief: %s | Action: %s | Preview deck: %s | Actual deck: %s | Mode expected/actual: %s / %s | Selection expected/actual: %s / %s | Selected case: %s | Last case matched: %s | Focus cases: %d | Last case: %s"),
DisplayedGuidanceComparisonBrief->Headline.IsEmpty() ? TEXT("n/a") : *DisplayedGuidanceComparisonBrief->Headline,
DisplayedGuidanceComparisonBrief->PrimaryActionLabel.IsEmpty() ? TEXT("n/a") : *DisplayedGuidanceComparisonBrief->PrimaryActionLabel,
DisplayedGuidanceComparisonDeck->Title.IsEmpty() ? *DisplayedGuidanceComparisonDeck->DeckId : *DisplayedGuidanceComparisonDeck->Title,
DisplayedRunRecapDeck != nullptr
? (DisplayedRunRecapDeck->Title.IsEmpty() ? *DisplayedRunRecapDeck->DeckId : *DisplayedRunRecapDeck->Title)
: TEXT("n/a"),
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(DisplayedGuidanceComparisonBrief->SuggestedMode),
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(DisplayedRunRecapMode),
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(DisplayedGuidanceComparisonBrief->SuggestedSelectionPolicy),
DisplayedRunRecapDeck != nullptr
? *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(DisplayedRunRecapDeck->SelectionPolicy)
: TEXT("n/a"),
DisplayedGuidanceComparisonSelectedCaseId.IsEmpty() ? TEXT("n/a") : *DisplayedGuidanceComparisonSelectedCaseId,
bDisplayedComparisonSelectedCaseMatchesLastCase ? TEXT("yes") : TEXT("no"),
DisplayedGuidanceComparisonBrief->FocusCaseIds.Num(),
DisplayedRunRecap->LastCaseId.IsEmpty() ? TEXT("n/a") : *DisplayedRunRecap->LastCaseId
)
: TEXT("Comparison detail: no preview-launched recap is currently selected.");
const bool bCanStartPreviewDeck =
!bHasLiveAttemptTimer && !bHasActiveRun && ActivePreviewDeck != nullptr && ActivePreviewDeck->IsStructurallyValid();
const bool bCanStartPreviewCase = bCanStartPreviewDeck && SelectedGuidancePreviewCase != nullptr;
@ -2572,6 +2731,18 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
));
}
}
if (GuidanceComparisonHeaderTextBlock != nullptr)
{
GuidanceComparisonHeaderTextBlock->SetText(FText::FromString(TEXT("[Guidance Comparison]")));
}
if (GuidanceComparisonStatusTextBlock != nullptr)
{
GuidanceComparisonStatusTextBlock->SetText(FText::FromString(GuidanceComparisonStatusLine));
}
if (GuidanceComparisonDetailTextBlock != nullptr)
{
GuidanceComparisonDetailTextBlock->SetText(FText::FromString(GuidanceComparisonDetailLine));
}
if (RepeatRunButtonLabel != nullptr)
{
RepeatRunButtonLabel->SetText(FText::FromString(TEXT("Repeat Run")));

View file

@ -188,6 +188,15 @@ protected:
UPROPERTY(Transient)
TObjectPtr<UTextBlock> RunRecapDetailTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidanceComparisonHeaderTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidanceComparisonStatusTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> GuidanceComparisonDetailTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> CoachHandoffHeaderTextBlock = nullptr;
@ -614,6 +623,12 @@ private:
const FHyperTwistTrainingDeck* GetActiveGuidancePreviewDeck() const;
const FHyperTwistCoachBrief* GetActiveGuidancePreviewBrief() const;
FHyperTwistTrainingDeck BuildSelectedGuidancePreviewCaseDeck() const;
void CaptureActiveGuidanceLaunchContext(
const FString& SessionId,
const FHyperTwistTrainingDeck& Deck,
bool bSingleCaseLaunch,
const FString& SelectedCaseId
);
bool IsLiveAttemptInspectionPhase() const;
int32 GetMaxManualSplitCaptureCount() const;
const FHyperTwistTrainingSplitPhaseDefinition* FindNextSplitPhaseToCapture() const;
@ -629,6 +644,21 @@ private:
FString RetainedRunRecapUserId;
FString RetainedRunRecapSessionId;
EHyperTwistTrainingDeliveryMode RetainedRunRecapMode = EHyperTwistTrainingDeliveryMode::Timer;
FString ActiveGuidanceLaunchSessionId;
EHyperTwistCoachDashboardGuidancePreviewLane ActiveGuidanceLaunchLane =
EHyperTwistCoachDashboardGuidancePreviewLane::None;
bool bActiveGuidanceLaunchSingleCase = false;
FHyperTwistCoachBrief ActiveGuidanceLaunchBrief;
FHyperTwistTrainingDeck ActiveGuidanceLaunchDeck;
FString ActiveGuidanceLaunchSelectedCaseId;
bool bHasRetainedGuidanceComparison = false;
FString RetainedGuidanceComparisonSessionId;
EHyperTwistCoachDashboardGuidancePreviewLane RetainedGuidanceComparisonLane =
EHyperTwistCoachDashboardGuidancePreviewLane::None;
bool bRetainedGuidanceComparisonSingleCase = false;
FHyperTwistCoachBrief RetainedGuidanceComparisonBrief;
FHyperTwistTrainingDeck RetainedGuidanceComparisonDeck;
FString RetainedGuidanceComparisonSelectedCaseId;
EHyperTwistCoachDashboardGuidancePreviewLane ActiveGuidancePreviewLane =
EHyperTwistCoachDashboardGuidancePreviewLane::None;
int32 SelectedGuidancePreviewCaseIndex = INDEX_NONE;