Retain drill follow-up learning on dashboard

This commit is contained in:
axiomlogicnexus 2026-04-29 02:49:34 +02:00
parent ce4b46b156
commit 64c92dc978
2 changed files with 502 additions and 1 deletions

View file

@ -687,6 +687,7 @@ void UHyperTwistCoachDashboardWidget::RefreshCoachDashboardView()
SyncSelectedHandoffHistoryEntry();
SyncSelectedQueueSuppressionHistoryEntry();
SyncSelectedClosureRecoveryHistoryEntry();
SyncSelectedMethodDrillFollowUpHistoryEntry();
SyncPreferredGuidancePreviewLane(false);
UpdateDashboardPresentation();
}
@ -1689,6 +1690,47 @@ bool UHyperTwistCoachDashboardWidget::SelectNextClosureRecoveryHistoryEntry()
return true;
}
bool UHyperTwistCoachDashboardWidget::SelectPreviousMethodDrillFollowUpHistoryEntry()
{
if (MethodDrillFollowUpHistoryEntries.Num() <= 0)
{
SelectedMethodDrillFollowUpHistoryIndex = INDEX_NONE;
return false;
}
if (SelectedMethodDrillFollowUpHistoryIndex == INDEX_NONE)
{
SyncSelectedMethodDrillFollowUpHistoryEntry();
return SelectedMethodDrillFollowUpHistoryIndex != INDEX_NONE;
}
SelectedMethodDrillFollowUpHistoryIndex =
(SelectedMethodDrillFollowUpHistoryIndex - 1 + MethodDrillFollowUpHistoryEntries.Num())
% MethodDrillFollowUpHistoryEntries.Num();
UpdateDashboardPresentation();
return true;
}
bool UHyperTwistCoachDashboardWidget::SelectNextMethodDrillFollowUpHistoryEntry()
{
if (MethodDrillFollowUpHistoryEntries.Num() <= 0)
{
SelectedMethodDrillFollowUpHistoryIndex = INDEX_NONE;
return false;
}
if (SelectedMethodDrillFollowUpHistoryIndex == INDEX_NONE)
{
SyncSelectedMethodDrillFollowUpHistoryEntry();
return SelectedMethodDrillFollowUpHistoryIndex != INDEX_NONE;
}
SelectedMethodDrillFollowUpHistoryIndex =
(SelectedMethodDrillFollowUpHistoryIndex + 1) % MethodDrillFollowUpHistoryEntries.Num();
UpdateDashboardPresentation();
return true;
}
bool UHyperTwistCoachDashboardWidget::StartLiveAttemptTimer()
{
if (!CachedRunState.Session.IsStructurallyValid() || !CachedRunState.CurrentSelection.TrainingCase.IsStructurallyValid())
@ -1954,7 +1996,12 @@ void UHyperTwistCoachDashboardWidget::HandleStartPreviewCaseClicked()
void UHyperTwistCoachDashboardWidget::HandleStartMethodDrillFollowUpClicked()
{
StartActiveMethodDrillFollowUpRun(DefaultSessionId + TEXT("_method_drill_follow_up"));
const FHyperTwistTrainingRunState RunState =
StartActiveMethodDrillFollowUpRun(DefaultSessionId + TEXT("_method_drill_follow_up"));
if (RunState.Session.IsStructurallyValid())
{
CaptureActiveMethodDrillFollowUpLaunchContext(RunState.Session.TrainingSessionId);
}
SyncRetainedRunRecap();
SyncSelectedQueueEntry();
SyncSelectedAttempt();
@ -2020,6 +2067,16 @@ void UHyperTwistCoachDashboardWidget::HandleNextClosureRecoveryClicked()
SelectNextClosureRecoveryHistoryEntry();
}
void UHyperTwistCoachDashboardWidget::HandlePreviousMethodDrillFollowUpHistoryClicked()
{
SelectPreviousMethodDrillFollowUpHistoryEntry();
}
void UHyperTwistCoachDashboardWidget::HandleNextMethodDrillFollowUpHistoryClicked()
{
SelectNextMethodDrillFollowUpHistoryEntry();
}
void UHyperTwistCoachDashboardWidget::HandleClearRunClicked()
{
ClearActiveTrainingRun();
@ -2463,6 +2520,57 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
);
StartMethodDrillFollowUpButtonLabel = StartMethodDrillFollowUpLabelRaw;
}
MethodDrillFollowUpHistoryHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachMethodDrillFollowUpHistoryHeader"),
2
);
MethodDrillFollowUpHistoryStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachMethodDrillFollowUpHistoryStatus"),
2
);
MethodDrillFollowUpHistoryDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachMethodDrillFollowUpHistoryDetail"),
8
);
UHorizontalBox* MethodDrillHistoryNavRow = WidgetTree->ConstructWidget<UHorizontalBox>(
UHorizontalBox::StaticClass(),
TEXT("CoachDashboardMethodDrillHistoryNavRow")
);
if (MethodDrillHistoryNavRow != nullptr)
{
if (UVerticalBoxSlot* MethodDrillHistoryNavRowSlot = RootLayout->AddChildToVerticalBox(MethodDrillHistoryNavRow))
{
MethodDrillHistoryNavRowSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 0.0f));
}
UTextBlock* PreviousMethodDrillHistoryLabelRaw = nullptr;
PreviousMethodDrillFollowUpHistoryButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
WidgetTree,
MethodDrillHistoryNavRow,
TEXT("PreviousMethodDrillFollowUpHistoryButton"),
TEXT("PreviousMethodDrillFollowUpHistoryButtonLabel"),
TEXT("Prev Drill Follow-Up"),
PreviousMethodDrillHistoryLabelRaw
);
PreviousMethodDrillFollowUpHistoryButtonLabel = PreviousMethodDrillHistoryLabelRaw;
UTextBlock* NextMethodDrillHistoryLabelRaw = nullptr;
NextMethodDrillFollowUpHistoryButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
WidgetTree,
MethodDrillHistoryNavRow,
TEXT("NextMethodDrillFollowUpHistoryButton"),
TEXT("NextMethodDrillFollowUpHistoryButtonLabel"),
TEXT("Next Drill Follow-Up"),
NextMethodDrillHistoryLabelRaw
);
NextMethodDrillFollowUpHistoryButtonLabel = NextMethodDrillHistoryLabelRaw;
}
UHorizontalBox* RunRecapActionRow = WidgetTree->ConstructWidget<UHorizontalBox>(
UHorizontalBox::StaticClass(),
TEXT("CoachDashboardRunRecapActions")
@ -3464,6 +3572,20 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
&UHyperTwistCoachDashboardWidget::HandleStartMethodDrillFollowUpClicked
);
}
if (PreviousMethodDrillFollowUpHistoryButton != nullptr)
{
PreviousMethodDrillFollowUpHistoryButton->OnClicked.AddDynamic(
this,
&UHyperTwistCoachDashboardWidget::HandlePreviousMethodDrillFollowUpHistoryClicked
);
}
if (NextMethodDrillFollowUpHistoryButton != nullptr)
{
NextMethodDrillFollowUpHistoryButton->OnClicked.AddDynamic(
this,
&UHyperTwistCoachDashboardWidget::HandleNextMethodDrillFollowUpHistoryClicked
);
}
if (CompleteRunButton != nullptr)
{
CompleteRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked);
@ -3641,6 +3763,16 @@ void UHyperTwistCoachDashboardWidget::SyncRetainedRunRecap()
RetainedGuidanceComparisonDeck = ActiveGuidanceLaunchDeck;
RetainedGuidanceComparisonSelectedCaseId = ActiveGuidanceLaunchSelectedCaseId;
}
if (!ActiveMethodDrillFollowUpLaunchSessionId.IsEmpty()
&& ActiveMethodDrillFollowUpLaunchSessionId == RetainedRunRecapSessionId
&& ActiveMethodDrillFollowUpLaunchDiagnosticPacket.IsStructurallyValid())
{
RecordMethodDrillFollowUpHistoryEntry(RetainedRunRecapSummary);
ActiveMethodDrillFollowUpLaunchSessionId.Reset();
ActiveMethodDrillFollowUpLaunchDiagnosticPacket =
FHyperTwistTrainingMethodDrillDiagnosticPacket();
ActiveMethodDrillFollowUpLaunchTemplate = FHyperTwistTrainingSessionTemplate();
}
}
}
@ -3720,6 +3852,21 @@ void UHyperTwistCoachDashboardWidget::SyncSelectedClosureRecoveryHistoryEntry()
}
}
void UHyperTwistCoachDashboardWidget::SyncSelectedMethodDrillFollowUpHistoryEntry()
{
if (MethodDrillFollowUpHistoryEntries.Num() <= 0)
{
SelectedMethodDrillFollowUpHistoryIndex = INDEX_NONE;
return;
}
if (SelectedMethodDrillFollowUpHistoryIndex < 0
|| SelectedMethodDrillFollowUpHistoryIndex >= MethodDrillFollowUpHistoryEntries.Num())
{
SelectedMethodDrillFollowUpHistoryIndex = MethodDrillFollowUpHistoryEntries.Num() - 1;
}
}
void UHyperTwistCoachDashboardWidget::SyncPreferredGuidancePreviewLane(const bool bForceAdoptPreferredLane)
{
const FHyperTwistCoachDashboardGuidanceLanePreference Preference = BuildGuidanceLanePreference();
@ -3793,6 +3940,15 @@ void UHyperTwistCoachDashboardWidget::CaptureActiveGuidanceLaunchContext(
}
}
void UHyperTwistCoachDashboardWidget::CaptureActiveMethodDrillFollowUpLaunchContext(
const FString& SessionId
)
{
ActiveMethodDrillFollowUpLaunchSessionId = SessionId;
ActiveMethodDrillFollowUpLaunchDiagnosticPacket = CachedMethodDrillDiagnosticPacket;
ActiveMethodDrillFollowUpLaunchTemplate = CachedMethodDrillFollowUpTemplate;
}
FHyperTwistTrainingDeck UHyperTwistCoachDashboardWidget::BuildSelectedGuidancePreviewCaseDeck() const
{
const FHyperTwistTrainingDeck* Deck = GetActiveGuidancePreviewDeck();
@ -4522,6 +4678,74 @@ FHyperTwistCoachDashboardQueueRecoveryWeighting UHyperTwistCoachDashboardWidget:
return Weighting;
}
FHyperTwistCoachDashboardMethodDrillFollowUpWeighting
UHyperTwistCoachDashboardWidget::BuildMethodDrillFollowUpWeighting() const
{
FHyperTwistCoachDashboardMethodDrillFollowUpWeighting Weighting;
if (!CachedMethodDrillDiagnosticPacket.IsStructurallyValid())
{
return Weighting;
}
constexpr int32 MaxMatchedHistoryEntries = 6;
for (int32 Index = MethodDrillFollowUpHistoryEntries.Num() - 1;
Index >= 0 && Weighting.MatchingHistoryCount < MaxMatchedHistoryEntries;
--Index)
{
const FHyperTwistCoachDashboardMethodDrillFollowUpHistoryEntry& Entry =
MethodDrillFollowUpHistoryEntries[Index];
if (Entry.DiagnosticState != CachedMethodDrillDiagnosticPacket.DiagnosticState
|| Entry.RecommendedReviewGrade
!= CachedMethodDrillDiagnosticPacket.RecommendedReviewGrade)
{
continue;
}
++Weighting.MatchingHistoryCount;
Weighting.AverageSuccessRate += Entry.SuccessRate;
Weighting.AverageMistakes += static_cast<float>(Entry.TotalMistakes);
const bool bStrongOutcome =
Entry.SuccessRate >= 0.72f
&& Entry.TotalMistakes <= FMath::Max(1, Entry.CompletedAttemptCount);
const bool bWeakOutcome =
Entry.SuccessRate < 0.50f
|| Entry.TotalMistakes > FMath::Max(2, Entry.CompletedAttemptCount * 2);
if (bStrongOutcome)
{
++Weighting.StrongOutcomeCount;
Weighting.ContinueNarrowBias += 2;
}
if (Entry.bBoundaryPending || Entry.bBoundaryRepeatBurden)
{
++Weighting.ContinueNarrowBias;
}
if (bWeakOutcome)
{
++Weighting.WeakOutcomeCount;
Weighting.RedirectCoachBias += 2;
}
if (!Entry.bBoundaryPending && Entry.SuccessRate < 0.65f)
{
++Weighting.RedirectCoachBias;
}
}
if (Weighting.MatchingHistoryCount <= 0)
{
return Weighting;
}
Weighting.AverageSuccessRate /= static_cast<float>(Weighting.MatchingHistoryCount);
Weighting.AverageMistakes /= static_cast<float>(Weighting.MatchingHistoryCount);
Weighting.bPreferDrillFollowUp =
Weighting.ContinueNarrowBias >= Weighting.RedirectCoachBias + 2;
Weighting.bPreferCoachHandoff =
Weighting.RedirectCoachBias >= Weighting.ContinueNarrowBias + 2;
return Weighting;
}
FString UHyperTwistCoachDashboardWidget::BuildGuidanceOutcomeActionLabel(
const EHyperTwistCoachDashboardOutcomeAction Action) const
{
@ -4941,6 +5165,71 @@ void UHyperTwistCoachDashboardWidget::RecordClosureRecoverySnapshot()
SelectedClosureRecoveryIndex = ClosureRecoveryHistoryEntries.Num() - 1;
}
void UHyperTwistCoachDashboardWidget::RecordMethodDrillFollowUpHistoryEntry(
const FHyperTwistTrainingSessionSummary& CompletedRunRecap
)
{
if (CompletedRunRecap.TrainingSessionId.IsEmpty()
|| !ActiveMethodDrillFollowUpLaunchDiagnosticPacket.IsStructurallyValid()
|| !ActiveMethodDrillFollowUpLaunchTemplate.IsStructurallyValid())
{
return;
}
FHyperTwistCoachDashboardMethodDrillFollowUpHistoryEntry Entry;
Entry.RecordedAtUtc = FDateTime::UtcNow().ToIso8601();
Entry.SourceRunId = ActiveMethodDrillFollowUpLaunchDiagnosticPacket.RunId;
Entry.SourceDrillId = ActiveMethodDrillFollowUpLaunchDiagnosticPacket.DrillId;
Entry.SourceSessionId = CompletedRunRecap.TrainingSessionId;
Entry.StageLabel = ActiveMethodDrillFollowUpLaunchDiagnosticPacket.StageLabel;
Entry.DiagnosticState = ActiveMethodDrillFollowUpLaunchDiagnosticPacket.DiagnosticState;
Entry.RecommendedReviewGrade =
ActiveMethodDrillFollowUpLaunchDiagnosticPacket.RecommendedReviewGrade;
Entry.TemplateKind = ActiveMethodDrillFollowUpLaunchTemplate.TemplateKind;
Entry.SuggestedMode = ActiveMethodDrillFollowUpLaunchTemplate.SuggestedMode;
Entry.SuggestedSelectionPolicy =
ActiveMethodDrillFollowUpLaunchTemplate.SuggestedSelectionPolicy;
Entry.FocusCaseCount = ActiveMethodDrillFollowUpLaunchDiagnosticPacket.FocusCaseIds.Num();
Entry.LaunchedCaseCount = CompletedRunRecap.CompletedAttemptCount > 0
? FMath::Max(CompletedRunRecap.CompletedAttemptCount, 1)
: ActiveMethodDrillFollowUpLaunchTemplate.RecommendedCaseCount;
Entry.CompletedAttemptCount = CompletedRunRecap.CompletedAttemptCount;
Entry.TotalMistakes = CompletedRunRecap.TotalMistakes;
Entry.SuccessRate = CompletedRunRecap.SuccessRate;
Entry.bBoundaryPending =
ActiveMethodDrillFollowUpLaunchDiagnosticPacket.bBoundaryTriggeredByPendingCases;
Entry.bBoundaryRepeatBurden =
ActiveMethodDrillFollowUpLaunchDiagnosticPacket.bBoundaryTriggeredByRepeatBurden;
Entry.bBoundaryAssistance =
ActiveMethodDrillFollowUpLaunchDiagnosticPacket.bBoundaryTriggeredByAssistance;
if (!Entry.IsStructurallyValid())
{
return;
}
const bool bAlreadyRecorded = MethodDrillFollowUpHistoryEntries.ContainsByPredicate(
[&Entry](const FHyperTwistCoachDashboardMethodDrillFollowUpHistoryEntry& Candidate)
{
return Candidate.SourceRunId == Entry.SourceRunId
&& Candidate.SourceSessionId == Entry.SourceSessionId;
}
);
if (bAlreadyRecorded)
{
return;
}
MethodDrillFollowUpHistoryEntries.Add(Entry);
constexpr int32 MaxRetainedMethodDrillFollowUpHistoryEntries = 16;
if (MethodDrillFollowUpHistoryEntries.Num() > MaxRetainedMethodDrillFollowUpHistoryEntries)
{
const int32 ExcessCount =
MethodDrillFollowUpHistoryEntries.Num() - MaxRetainedMethodDrillFollowUpHistoryEntries;
MethodDrillFollowUpHistoryEntries.RemoveAt(0, ExcessCount, EAllowShrinking::No);
}
SelectedMethodDrillFollowUpHistoryIndex = MethodDrillFollowUpHistoryEntries.Num() - 1;
}
void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
{
const bool bHasActiveRun = CachedRunState.Session.IsStructurallyValid()
@ -5294,6 +5583,80 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
{
StartMethodDrillFollowUpLabel = TEXT("No Drill Follow-Up");
}
const FHyperTwistCoachDashboardMethodDrillFollowUpWeighting MethodDrillFollowUpWeighting =
BuildMethodDrillFollowUpWeighting();
const FHyperTwistCoachDashboardMethodDrillFollowUpHistoryEntry* SelectedMethodDrillFollowUpHistoryEntry =
SelectedMethodDrillFollowUpHistoryIndex >= 0
&& SelectedMethodDrillFollowUpHistoryIndex < MethodDrillFollowUpHistoryEntries.Num()
? &MethodDrillFollowUpHistoryEntries[SelectedMethodDrillFollowUpHistoryIndex]
: nullptr;
const FString MethodDrillOutcomePreferenceLine = !MethodDrillFollowUpWeighting.IsStructurallyValid()
? TEXT("no retained drill follow-up history yet")
: (MethodDrillFollowUpWeighting.bPreferDrillFollowUp
? TEXT("retained drill history favors staying narrow with drill follow-up")
: (MethodDrillFollowUpWeighting.bPreferCoachHandoff
? TEXT("retained drill history favors handing control back to broader coach guidance")
: TEXT("retained drill history is balanced")));
const FString MethodDrillFollowUpHistoryStatusLine =
SelectedMethodDrillFollowUpHistoryEntry != nullptr
? FString::Printf(
TEXT("Drill follow-up history: %d entries | selected %d/%d | Stage: %s | review %s | Recorded: %s"),
MethodDrillFollowUpHistoryEntries.Num(),
SelectedMethodDrillFollowUpHistoryIndex + 1,
MethodDrillFollowUpHistoryEntries.Num(),
SelectedMethodDrillFollowUpHistoryEntry->StageLabel.IsEmpty()
? TEXT("n/a")
: *SelectedMethodDrillFollowUpHistoryEntry->StageLabel,
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
SelectedMethodDrillFollowUpHistoryEntry->RecommendedReviewGrade),
*SelectedMethodDrillFollowUpHistoryEntry->RecordedAtUtc)
: TEXT("Drill follow-up history: no retained drill follow-up outcomes recorded yet.");
const FString MethodDrillFollowUpHistoryDetailLine =
SelectedMethodDrillFollowUpHistoryEntry != nullptr
? FString::Printf(
TEXT("State: %s | template: %s | mode: %s | selection: %s | focus %d | launched %d | success %.2f | attempts %d | mistakes %d | pending: %s | repeat burden: %s | assistance: %s"),
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
SelectedMethodDrillFollowUpHistoryEntry->DiagnosticState),
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
SelectedMethodDrillFollowUpHistoryEntry->TemplateKind),
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
SelectedMethodDrillFollowUpHistoryEntry->SuggestedMode),
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
SelectedMethodDrillFollowUpHistoryEntry->SuggestedSelectionPolicy),
SelectedMethodDrillFollowUpHistoryEntry->FocusCaseCount,
SelectedMethodDrillFollowUpHistoryEntry->LaunchedCaseCount,
SelectedMethodDrillFollowUpHistoryEntry->SuccessRate,
SelectedMethodDrillFollowUpHistoryEntry->CompletedAttemptCount,
SelectedMethodDrillFollowUpHistoryEntry->TotalMistakes,
SelectedMethodDrillFollowUpHistoryEntry->bBoundaryPending ? TEXT("yes") : TEXT("no"),
SelectedMethodDrillFollowUpHistoryEntry->bBoundaryRepeatBurden ? TEXT("yes") : TEXT("no"),
SelectedMethodDrillFollowUpHistoryEntry->bBoundaryAssistance ? TEXT("yes") : TEXT("no"))
: TEXT("Drill follow-up history detail: once drill-derived follow-up runs complete, their recap outcomes will accumulate here and feed later drill-vs-handoff judgment.");
if (MethodDrillFollowUpWeighting.IsStructurallyValid())
{
MethodDrillDiagnosticsStatusLine = FString::Printf(
TEXT("%s | history: %s"),
*MethodDrillDiagnosticsStatusLine,
*MethodDrillOutcomePreferenceLine
);
MethodDrillDiagnosticsDetailLine = FString::Printf(
TEXT("%s || History match %d | strong %d | weak %d | avg success %.2f | avg mistakes %.2f"),
*MethodDrillDiagnosticsDetailLine,
MethodDrillFollowUpWeighting.MatchingHistoryCount,
MethodDrillFollowUpWeighting.StrongOutcomeCount,
MethodDrillFollowUpWeighting.WeakOutcomeCount,
MethodDrillFollowUpWeighting.AverageSuccessRate,
MethodDrillFollowUpWeighting.AverageMistakes
);
}
if (bCanLaunchMethodDrillFollowUp && MethodDrillFollowUpWeighting.bPreferDrillFollowUp)
{
StartMethodDrillFollowUpLabel = TEXT("Launch Preferred Drill Follow-Up");
}
else if (bCanLaunchMethodDrillFollowUp && MethodDrillFollowUpWeighting.bPreferCoachHandoff)
{
StartMethodDrillFollowUpLabel = TEXT("Drill Follow-Up Optional");
}
const bool bHasCurrentRunRecap = bHasActiveRun
&& (CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed
|| CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Aborted);
@ -5716,6 +6079,14 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
HandoffHistoryBias.FollowUpRecommendationCount,
HandoffHistoryBias.RecommendedRecommendationCount
);
if (bCanLaunchMethodDrillFollowUp && MethodDrillFollowUpWeighting.IsStructurallyValid())
{
CoachHandoffRecommendationDetailLine += MethodDrillFollowUpWeighting.bPreferDrillFollowUp
? TEXT(" | Drill history says stay narrow with drill-derived follow-up before broader coach handoff.")
: (MethodDrillFollowUpWeighting.bPreferCoachHandoff
? TEXT(" | Drill history says broader coach handoff is now stronger than another narrow drill follow-up.")
: TEXT(" | Drill history is balanced against broader coach handoff."));
}
}
RecordCoachHandoffHistorySnapshot(
HyperTwistCoachDashboardWidgetInternal::DescribeHandoffRecommendationKind(
@ -6897,6 +7268,41 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
{
StartMethodDrillFollowUpButton->SetIsEnabled(bCanLaunchMethodDrillFollowUp);
}
if (MethodDrillFollowUpHistoryHeaderTextBlock != nullptr)
{
MethodDrillFollowUpHistoryHeaderTextBlock->SetText(FText::FromString(
TEXT("[Method Drill Follow-Up History]")));
}
if (MethodDrillFollowUpHistoryStatusTextBlock != nullptr)
{
MethodDrillFollowUpHistoryStatusTextBlock->SetText(FText::FromString(
MethodDrillFollowUpHistoryStatusLine));
}
if (MethodDrillFollowUpHistoryDetailTextBlock != nullptr)
{
MethodDrillFollowUpHistoryDetailTextBlock->SetText(FText::FromString(
MethodDrillFollowUpHistoryDetailLine));
}
if (PreviousMethodDrillFollowUpHistoryButtonLabel != nullptr)
{
PreviousMethodDrillFollowUpHistoryButtonLabel->SetText(FText::FromString(
TEXT("Prev Drill Follow-Up")));
}
if (PreviousMethodDrillFollowUpHistoryButton != nullptr)
{
PreviousMethodDrillFollowUpHistoryButton->SetIsEnabled(
MethodDrillFollowUpHistoryEntries.Num() > 1);
}
if (NextMethodDrillFollowUpHistoryButtonLabel != nullptr)
{
NextMethodDrillFollowUpHistoryButtonLabel->SetText(FText::FromString(
TEXT("Next Drill Follow-Up")));
}
if (NextMethodDrillFollowUpHistoryButton != nullptr)
{
NextMethodDrillFollowUpHistoryButton->SetIsEnabled(
MethodDrillFollowUpHistoryEntries.Num() > 1);
}
if (StartQueuedRunButtonLabel != nullptr)
{
StartQueuedRunButtonLabel->SetText(FText::FromString(QueuedRunLabel));

View file

@ -267,6 +267,57 @@ struct FHyperTwistCoachDashboardHandoffHistoryEntry
}
};
struct FHyperTwistCoachDashboardMethodDrillFollowUpHistoryEntry
{
FString RecordedAtUtc;
FString SourceRunId;
FString SourceDrillId;
FString SourceSessionId;
FString StageLabel;
EHyperTwistTrainingMethodDrillDiagnosticState DiagnosticState =
EHyperTwistTrainingMethodDrillDiagnosticState::Unknown;
EHyperTwistTrainingReviewGrade RecommendedReviewGrade = EHyperTwistTrainingReviewGrade::Good;
EHyperTwistTrainingSessionTemplateKind TemplateKind =
EHyperTwistTrainingSessionTemplateKind::ReviewPlan;
EHyperTwistTrainingDeliveryMode SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy =
EHyperTwistTrainingSelectionPolicy::Weighted;
int32 FocusCaseCount = 0;
int32 LaunchedCaseCount = 0;
int32 CompletedAttemptCount = 0;
int32 TotalMistakes = 0;
float SuccessRate = 0.0f;
bool bBoundaryPending = false;
bool bBoundaryRepeatBurden = false;
bool bBoundaryAssistance = false;
bool IsStructurallyValid() const
{
return !RecordedAtUtc.IsEmpty()
&& !SourceRunId.IsEmpty()
&& !SourceDrillId.IsEmpty()
&& !SourceSessionId.IsEmpty();
}
};
struct FHyperTwistCoachDashboardMethodDrillFollowUpWeighting
{
int32 MatchingHistoryCount = 0;
int32 StrongOutcomeCount = 0;
int32 WeakOutcomeCount = 0;
int32 ContinueNarrowBias = 0;
int32 RedirectCoachBias = 0;
float AverageSuccessRate = 0.0f;
float AverageMistakes = 0.0f;
bool bPreferDrillFollowUp = false;
bool bPreferCoachHandoff = false;
bool IsStructurallyValid() const
{
return MatchingHistoryCount > 0;
}
};
UCLASS(BlueprintType, Blueprintable)
class UNREALHYPERTWIST_API UHyperTwistCoachDashboardWidget : public UHyperTwistTrainingPanelWidget
{
@ -318,6 +369,9 @@ public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
int32 SelectedClosureRecoveryIndex = INDEX_NONE;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
int32 SelectedMethodDrillFollowUpHistoryIndex = INDEX_NONE;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
bool bHasLiveAttemptTimer = false;
@ -423,6 +477,12 @@ public:
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
bool SelectNextClosureRecoveryHistoryEntry();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
bool SelectPreviousMethodDrillFollowUpHistoryEntry();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
bool SelectNextMethodDrillFollowUpHistoryEntry();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
bool StartLiveAttemptTimer();
@ -612,6 +672,27 @@ protected:
UPROPERTY(Transient)
TObjectPtr<UTextBlock> StartMethodDrillFollowUpButtonLabel = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> MethodDrillFollowUpHistoryHeaderTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> MethodDrillFollowUpHistoryStatusTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> MethodDrillFollowUpHistoryDetailTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UButton> PreviousMethodDrillFollowUpHistoryButton = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> PreviousMethodDrillFollowUpHistoryButtonLabel = nullptr;
UPROPERTY(Transient)
TObjectPtr<UButton> NextMethodDrillFollowUpHistoryButton = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> NextMethodDrillFollowUpHistoryButtonLabel = nullptr;
UPROPERTY(Transient)
TObjectPtr<UButton> RepeatRunButton = nullptr;
@ -1050,6 +1131,12 @@ protected:
UFUNCTION()
void HandleNextClosureRecoveryClicked();
UFUNCTION()
void HandlePreviousMethodDrillFollowUpHistoryClicked();
UFUNCTION()
void HandleNextMethodDrillFollowUpHistoryClicked();
UFUNCTION()
void HandleClearRunClicked();
@ -1108,6 +1195,7 @@ private:
void SyncSelectedHandoffHistoryEntry();
void SyncSelectedQueueSuppressionHistoryEntry();
void SyncSelectedClosureRecoveryHistoryEntry();
void SyncSelectedMethodDrillFollowUpHistoryEntry();
void SyncPreferredGuidancePreviewLane(bool bForceAdoptPreferredLane);
void UpdateDashboardPresentation();
void RefreshLiveAttemptClock();
@ -1136,6 +1224,7 @@ private:
) const;
FHyperTwistCoachDashboardGuidanceLanePreference BuildGuidanceLanePreference() const;
FHyperTwistCoachDashboardQueueRecoveryWeighting BuildQueueRecoveryWeighting() const;
FHyperTwistCoachDashboardMethodDrillFollowUpWeighting BuildMethodDrillFollowUpWeighting() const;
FString BuildGuidanceOutcomeActionLabel(EHyperTwistCoachDashboardOutcomeAction Action) const;
FHyperTwistTrainingDeck BuildGuidanceOutcomeSelectedCaseDeck() const;
void RecordQueueRecoveryOutcome(
@ -1164,6 +1253,7 @@ private:
);
void RecordQueueSuppressionSnapshot();
void RecordClosureRecoverySnapshot();
void RecordMethodDrillFollowUpHistoryEntry(const FHyperTwistTrainingSessionSummary& CompletedRunRecap);
void RecordGuidanceOutcomeDecision(
EHyperTwistCoachDashboardOutcomeAction RecommendedAction,
EHyperTwistCoachDashboardOutcomeAction AppliedAction,
@ -1181,6 +1271,7 @@ private:
bool bSingleCaseLaunch,
const FString& SelectedCaseId
);
void CaptureActiveMethodDrillFollowUpLaunchContext(const FString& SessionId);
bool IsLiveAttemptInspectionPhase() const;
int32 GetMaxManualSplitCaptureCount() const;
const FHyperTwistTrainingSplitPhaseDefinition* FindNextSplitPhaseToCapture() const;
@ -1203,6 +1294,9 @@ private:
FHyperTwistCoachBrief ActiveGuidanceLaunchBrief;
FHyperTwistTrainingDeck ActiveGuidanceLaunchDeck;
FString ActiveGuidanceLaunchSelectedCaseId;
FString ActiveMethodDrillFollowUpLaunchSessionId;
FHyperTwistTrainingMethodDrillDiagnosticPacket ActiveMethodDrillFollowUpLaunchDiagnosticPacket;
FHyperTwistTrainingSessionTemplate ActiveMethodDrillFollowUpLaunchTemplate;
bool bHasRetainedGuidanceComparison = false;
FString RetainedGuidanceComparisonSessionId;
EHyperTwistCoachDashboardGuidancePreviewLane RetainedGuidanceComparisonLane =
@ -1213,6 +1307,7 @@ private:
FString RetainedGuidanceComparisonSelectedCaseId;
TArray<FHyperTwistCoachDashboardDecisionHistoryEntry> GuidanceDecisionHistoryEntries;
TArray<FHyperTwistCoachDashboardHandoffHistoryEntry> HandoffHistoryEntries;
TArray<FHyperTwistCoachDashboardMethodDrillFollowUpHistoryEntry> MethodDrillFollowUpHistoryEntries;
bool bHasQueueRecoveryOutcome = false;
FHyperTwistCoachDashboardQueueRecoveryOutcome QueueRecoveryOutcome;
TArray<FHyperTwistCoachDashboardQueueRecoveryOutcome> QueueRecoveryOutcomeHistory;