Show queue recovery outcomes on dashboard
This commit is contained in:
parent
1e104702f3
commit
a4b69211bc
2 changed files with 270 additions and 8 deletions
|
|
@ -259,10 +259,29 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture()
|
|||
return false;
|
||||
}
|
||||
|
||||
const float BeforePressureScore = CachedCoachSchedulePolicySummary.ReschedulePressureScore;
|
||||
const int32 BeforeDeferredEventCount = CachedCoachQueueExecutionSummary.DeferredEventCount;
|
||||
const bool bBeforeBlockedFollowUp = CachedCoachSchedulePolicySummary.bHasBlockedFollowUpDeferrals;
|
||||
const bool bBeforeOutstandingDeferred = CachedCoachQueueExecutionSummary.bHasOutstandingDeferredWork;
|
||||
const bool bBeforeCompletionGap = CachedCoachQueueExecutionSummary.bHasCompletionGap;
|
||||
const FString SourceLabel = CachedCoachPanelState.PreferredGuidanceSourceLabel;
|
||||
|
||||
const FString DeferredEntryId = FindFirstDeferredQueueEntryId();
|
||||
if (!DeferredEntryId.IsEmpty())
|
||||
{
|
||||
return PromoteFirstDeferredQueueEntry();
|
||||
const bool bSucceeded = PromoteFirstDeferredQueueEntry();
|
||||
RecordQueueRecoveryOutcome(
|
||||
SourceLabel,
|
||||
TEXT("promote-deferred-recovery"),
|
||||
BeforePressureScore,
|
||||
BeforeDeferredEventCount,
|
||||
bBeforeBlockedFollowUp,
|
||||
bBeforeOutstandingDeferred,
|
||||
bBeforeCompletionGap,
|
||||
bSucceeded
|
||||
);
|
||||
UpdateDashboardPresentation();
|
||||
return bSucceeded;
|
||||
}
|
||||
|
||||
const bool bQueueRecoveryMemorySource =
|
||||
|
|
@ -291,7 +310,19 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture()
|
|||
&& CachedCoachPanelState.NextQueueSourceLabel == TEXT("primary-brief"));
|
||||
if (bQueueMatchesPreference)
|
||||
{
|
||||
return StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases));
|
||||
const bool bSucceeded = StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases));
|
||||
RecordQueueRecoveryOutcome(
|
||||
SourceLabel,
|
||||
TEXT("launch-queued-recovery"),
|
||||
BeforePressureScore,
|
||||
BeforeDeferredEventCount,
|
||||
bBeforeBlockedFollowUp,
|
||||
bBeforeOutstandingDeferred,
|
||||
bBeforeCompletionGap,
|
||||
bSucceeded
|
||||
);
|
||||
UpdateDashboardPresentation();
|
||||
return bSucceeded;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -299,27 +330,99 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture()
|
|||
&& CachedCoachPanelState.bCanStartCoachFollowUpRun
|
||||
&& CachedCoachPanelState.bHasFollowUpGuidance)
|
||||
{
|
||||
return StartRunAndRefresh(StartCoachFollowUpRun(DefaultCoachMaxCases));
|
||||
const bool bSucceeded = StartRunAndRefresh(StartCoachFollowUpRun(DefaultCoachMaxCases));
|
||||
RecordQueueRecoveryOutcome(
|
||||
SourceLabel,
|
||||
TEXT("launch-follow-up-recovery"),
|
||||
BeforePressureScore,
|
||||
BeforeDeferredEventCount,
|
||||
bBeforeBlockedFollowUp,
|
||||
bBeforeOutstandingDeferred,
|
||||
bBeforeCompletionGap,
|
||||
bSucceeded
|
||||
);
|
||||
UpdateDashboardPresentation();
|
||||
return bSucceeded;
|
||||
}
|
||||
if (bPrefersRecommended && CachedCoachPanelState.bCanStartCoachRecommendedRun)
|
||||
{
|
||||
return StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases));
|
||||
const bool bSucceeded = StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases));
|
||||
RecordQueueRecoveryOutcome(
|
||||
SourceLabel,
|
||||
TEXT("launch-recommended-recovery"),
|
||||
BeforePressureScore,
|
||||
BeforeDeferredEventCount,
|
||||
bBeforeBlockedFollowUp,
|
||||
bBeforeOutstandingDeferred,
|
||||
bBeforeCompletionGap,
|
||||
bSucceeded
|
||||
);
|
||||
UpdateDashboardPresentation();
|
||||
return bSucceeded;
|
||||
}
|
||||
if (CachedCoachPanelState.bCanStartQueuedRun)
|
||||
{
|
||||
return StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases));
|
||||
const bool bSucceeded = StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases));
|
||||
RecordQueueRecoveryOutcome(
|
||||
SourceLabel,
|
||||
TEXT("launch-queued-fallback"),
|
||||
BeforePressureScore,
|
||||
BeforeDeferredEventCount,
|
||||
bBeforeBlockedFollowUp,
|
||||
bBeforeOutstandingDeferred,
|
||||
bBeforeCompletionGap,
|
||||
bSucceeded
|
||||
);
|
||||
UpdateDashboardPresentation();
|
||||
return bSucceeded;
|
||||
}
|
||||
if (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance)
|
||||
{
|
||||
return StartRunAndRefresh(StartCoachFollowUpRun(DefaultCoachMaxCases));
|
||||
const bool bSucceeded = StartRunAndRefresh(StartCoachFollowUpRun(DefaultCoachMaxCases));
|
||||
RecordQueueRecoveryOutcome(
|
||||
SourceLabel,
|
||||
TEXT("launch-follow-up-fallback"),
|
||||
BeforePressureScore,
|
||||
BeforeDeferredEventCount,
|
||||
bBeforeBlockedFollowUp,
|
||||
bBeforeOutstandingDeferred,
|
||||
bBeforeCompletionGap,
|
||||
bSucceeded
|
||||
);
|
||||
UpdateDashboardPresentation();
|
||||
return bSucceeded;
|
||||
}
|
||||
if (CachedCoachPanelState.bCanStartCoachRecommendedRun)
|
||||
{
|
||||
return StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases));
|
||||
const bool bSucceeded = StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases));
|
||||
RecordQueueRecoveryOutcome(
|
||||
SourceLabel,
|
||||
TEXT("launch-recommended-fallback"),
|
||||
BeforePressureScore,
|
||||
BeforeDeferredEventCount,
|
||||
bBeforeBlockedFollowUp,
|
||||
bBeforeOutstandingDeferred,
|
||||
bBeforeCompletionGap,
|
||||
bSucceeded
|
||||
);
|
||||
UpdateDashboardPresentation();
|
||||
return bSucceeded;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingRunState FallbackRunState = ExecutePrimaryCoachAction();
|
||||
return FallbackRunState.Session.IsStructurallyValid();
|
||||
const bool bSucceeded = FallbackRunState.Session.IsStructurallyValid();
|
||||
RecordQueueRecoveryOutcome(
|
||||
SourceLabel,
|
||||
TEXT("launch-primary-fallback"),
|
||||
BeforePressureScore,
|
||||
BeforeDeferredEventCount,
|
||||
bBeforeBlockedFollowUp,
|
||||
bBeforeOutstandingDeferred,
|
||||
bBeforeCompletionGap,
|
||||
bSucceeded
|
||||
);
|
||||
UpdateDashboardPresentation();
|
||||
return bSucceeded;
|
||||
}
|
||||
|
||||
bool UHyperTwistCoachDashboardWidget::SelectPreviousQueueEntry()
|
||||
|
|
@ -1866,6 +1969,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachQueueRecoveryDetail"),
|
||||
2
|
||||
);
|
||||
QueueRecoveryOutcomeHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachQueueRecoveryOutcomeHeader"),
|
||||
2
|
||||
);
|
||||
QueueRecoveryOutcomeStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachQueueRecoveryOutcomeStatus"),
|
||||
2
|
||||
);
|
||||
QueueRecoveryOutcomeDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachQueueRecoveryOutcomeDetail"),
|
||||
8
|
||||
);
|
||||
VerificationStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
|
|
@ -3108,6 +3229,48 @@ void UHyperTwistCoachDashboardWidget::RecordGuidanceOutcomeDecision(
|
|||
}
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::RecordQueueRecoveryOutcome(
|
||||
const FString& SourceLabel,
|
||||
const FString& AppliedActionLabel,
|
||||
const float BeforePressureScore,
|
||||
const int32 BeforeDeferredEventCount,
|
||||
const bool bBeforeBlockedFollowUp,
|
||||
const bool bBeforeOutstandingDeferred,
|
||||
const bool bBeforeCompletionGap,
|
||||
const bool bSucceeded)
|
||||
{
|
||||
QueueRecoveryOutcome.RecordedAtUtc = FDateTime::UtcNow().ToIso8601();
|
||||
QueueRecoveryOutcome.SourceLabel = SourceLabel;
|
||||
QueueRecoveryOutcome.AppliedActionLabel = AppliedActionLabel;
|
||||
QueueRecoveryOutcome.BeforePressureScore = BeforePressureScore;
|
||||
QueueRecoveryOutcome.AfterPressureScore = CachedCoachSchedulePolicySummary.ReschedulePressureScore;
|
||||
QueueRecoveryOutcome.BeforeDeferredEventCount = BeforeDeferredEventCount;
|
||||
QueueRecoveryOutcome.AfterDeferredEventCount = CachedCoachQueueExecutionSummary.DeferredEventCount;
|
||||
QueueRecoveryOutcome.bBeforeBlockedFollowUp = bBeforeBlockedFollowUp;
|
||||
QueueRecoveryOutcome.bAfterBlockedFollowUp = CachedCoachSchedulePolicySummary.bHasBlockedFollowUpDeferrals;
|
||||
QueueRecoveryOutcome.bBeforeOutstandingDeferred = bBeforeOutstandingDeferred;
|
||||
QueueRecoveryOutcome.bAfterOutstandingDeferred = CachedCoachQueueExecutionSummary.bHasOutstandingDeferredWork;
|
||||
QueueRecoveryOutcome.bBeforeCompletionGap = bBeforeCompletionGap;
|
||||
QueueRecoveryOutcome.bAfterCompletionGap = CachedCoachQueueExecutionSummary.bHasCompletionGap;
|
||||
QueueRecoveryOutcome.bSucceeded = bSucceeded;
|
||||
QueueRecoveryOutcome.bReducedFriction = bSucceeded
|
||||
&& (QueueRecoveryOutcome.AfterPressureScore < QueueRecoveryOutcome.BeforePressureScore
|
||||
|| QueueRecoveryOutcome.AfterDeferredEventCount < QueueRecoveryOutcome.BeforeDeferredEventCount
|
||||
|| (!QueueRecoveryOutcome.bAfterBlockedFollowUp && QueueRecoveryOutcome.bBeforeBlockedFollowUp)
|
||||
|| (!QueueRecoveryOutcome.bAfterOutstandingDeferred && QueueRecoveryOutcome.bBeforeOutstandingDeferred)
|
||||
|| (!QueueRecoveryOutcome.bAfterCompletionGap && QueueRecoveryOutcome.bBeforeCompletionGap));
|
||||
QueueRecoveryOutcome.bShiftedFriction = bSucceeded
|
||||
&& !QueueRecoveryOutcome.bReducedFriction
|
||||
&& (
|
||||
!FMath::IsNearlyEqual(QueueRecoveryOutcome.AfterPressureScore, QueueRecoveryOutcome.BeforePressureScore)
|
||||
|| QueueRecoveryOutcome.AfterDeferredEventCount != QueueRecoveryOutcome.BeforeDeferredEventCount
|
||||
|| QueueRecoveryOutcome.bAfterBlockedFollowUp != QueueRecoveryOutcome.bBeforeBlockedFollowUp
|
||||
|| QueueRecoveryOutcome.bAfterOutstandingDeferred != QueueRecoveryOutcome.bBeforeOutstandingDeferred
|
||||
|| QueueRecoveryOutcome.bAfterCompletionGap != QueueRecoveryOutcome.bBeforeCompletionGap
|
||||
);
|
||||
bHasQueueRecoveryOutcome = QueueRecoveryOutcome.IsStructurallyValid();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
||||
{
|
||||
const bool bHasActiveRun = CachedRunState.Session.IsStructurallyValid()
|
||||
|
|
@ -3563,6 +3726,47 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
CachedCoachSchedulePolicySummary.bHasArchiveAwareDeferrals ? TEXT("yes") : TEXT("no"),
|
||||
CachedCoachQueueExecutionSummary.bHasCompletionGap ? TEXT("yes") : TEXT("no")
|
||||
);
|
||||
const FString QueueRecoveryOutcomeStatusLine = !bHasQueueRecoveryOutcome || !QueueRecoveryOutcome.IsStructurallyValid()
|
||||
? TEXT("Outcome: no queue-recovery action has been applied yet.")
|
||||
: (!QueueRecoveryOutcome.bSucceeded
|
||||
? FString::Printf(
|
||||
TEXT("Outcome: %s failed | Source: %s"),
|
||||
*QueueRecoveryOutcome.AppliedActionLabel,
|
||||
QueueRecoveryOutcome.SourceLabel.IsEmpty() ? TEXT("n/a") : *QueueRecoveryOutcome.SourceLabel
|
||||
)
|
||||
: (QueueRecoveryOutcome.bReducedFriction
|
||||
? FString::Printf(
|
||||
TEXT("Outcome: %s reduced queue friction | Source: %s"),
|
||||
*QueueRecoveryOutcome.AppliedActionLabel,
|
||||
QueueRecoveryOutcome.SourceLabel.IsEmpty() ? TEXT("n/a") : *QueueRecoveryOutcome.SourceLabel
|
||||
)
|
||||
: (QueueRecoveryOutcome.bShiftedFriction
|
||||
? FString::Printf(
|
||||
TEXT("Outcome: %s shifted queue friction without a full reduction | Source: %s"),
|
||||
*QueueRecoveryOutcome.AppliedActionLabel,
|
||||
QueueRecoveryOutcome.SourceLabel.IsEmpty() ? TEXT("n/a") : *QueueRecoveryOutcome.SourceLabel
|
||||
)
|
||||
: FString::Printf(
|
||||
TEXT("Outcome: %s applied with no immediate friction change | Source: %s"),
|
||||
*QueueRecoveryOutcome.AppliedActionLabel,
|
||||
QueueRecoveryOutcome.SourceLabel.IsEmpty() ? TEXT("n/a") : *QueueRecoveryOutcome.SourceLabel
|
||||
))));
|
||||
const FString QueueRecoveryOutcomeDetailLine = !bHasQueueRecoveryOutcome || !QueueRecoveryOutcome.IsStructurallyValid()
|
||||
? TEXT("Outcome detail: apply a queue-recovery action to compare before/after pressure, deferred count, blocked follow-up state, and completion-gap state.")
|
||||
: FString::Printf(
|
||||
TEXT("Recorded: %s | Pressure %.2f -> %.2f | Deferred %d -> %d | Blocked follow-up %s -> %s | Outstanding deferred %s -> %s | Completion gap %s -> %s"),
|
||||
*QueueRecoveryOutcome.RecordedAtUtc,
|
||||
QueueRecoveryOutcome.BeforePressureScore,
|
||||
QueueRecoveryOutcome.AfterPressureScore,
|
||||
QueueRecoveryOutcome.BeforeDeferredEventCount,
|
||||
QueueRecoveryOutcome.AfterDeferredEventCount,
|
||||
QueueRecoveryOutcome.bBeforeBlockedFollowUp ? TEXT("yes") : TEXT("no"),
|
||||
QueueRecoveryOutcome.bAfterBlockedFollowUp ? TEXT("yes") : TEXT("no"),
|
||||
QueueRecoveryOutcome.bBeforeOutstandingDeferred ? TEXT("yes") : TEXT("no"),
|
||||
QueueRecoveryOutcome.bAfterOutstandingDeferred ? TEXT("yes") : TEXT("no"),
|
||||
QueueRecoveryOutcome.bBeforeCompletionGap ? TEXT("yes") : TEXT("no"),
|
||||
QueueRecoveryOutcome.bAfterCompletionGap ? TEXT("yes") : TEXT("no")
|
||||
);
|
||||
const FString PreferredDeferredRecoveryEntryId = FindFirstDeferredQueueEntryId();
|
||||
const bool bCanApplyQueueRecovery =
|
||||
!bHasLiveAttemptTimer
|
||||
|
|
@ -4625,6 +4829,18 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
{
|
||||
QueueRecoveryDetailTextBlock->SetText(FText::FromString(QueueRecoveryDetailLine));
|
||||
}
|
||||
if (QueueRecoveryOutcomeHeaderTextBlock != nullptr)
|
||||
{
|
||||
QueueRecoveryOutcomeHeaderTextBlock->SetText(FText::FromString(TEXT("[Queue Recovery Outcome]")));
|
||||
}
|
||||
if (QueueRecoveryOutcomeStatusTextBlock != nullptr)
|
||||
{
|
||||
QueueRecoveryOutcomeStatusTextBlock->SetText(FText::FromString(QueueRecoveryOutcomeStatusLine));
|
||||
}
|
||||
if (QueueRecoveryOutcomeDetailTextBlock != nullptr)
|
||||
{
|
||||
QueueRecoveryOutcomeDetailTextBlock->SetText(FText::FromString(QueueRecoveryOutcomeDetailLine));
|
||||
}
|
||||
if (VerificationStatusTextBlock != nullptr)
|
||||
{
|
||||
VerificationStatusTextBlock->SetText(FText::FromString(CachedCoachPanelState.VerificationStatusLine));
|
||||
|
|
|
|||
|
|
@ -102,6 +102,31 @@ struct FHyperTwistCoachDashboardGuidanceLanePreference
|
|||
}
|
||||
};
|
||||
|
||||
struct FHyperTwistCoachDashboardQueueRecoveryOutcome
|
||||
{
|
||||
FString RecordedAtUtc;
|
||||
FString SourceLabel;
|
||||
FString AppliedActionLabel;
|
||||
float BeforePressureScore = 0.0f;
|
||||
float AfterPressureScore = 0.0f;
|
||||
int32 BeforeDeferredEventCount = 0;
|
||||
int32 AfterDeferredEventCount = 0;
|
||||
bool bBeforeBlockedFollowUp = false;
|
||||
bool bAfterBlockedFollowUp = false;
|
||||
bool bBeforeOutstandingDeferred = false;
|
||||
bool bAfterOutstandingDeferred = false;
|
||||
bool bBeforeCompletionGap = false;
|
||||
bool bAfterCompletionGap = false;
|
||||
bool bSucceeded = false;
|
||||
bool bReducedFriction = false;
|
||||
bool bShiftedFriction = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !RecordedAtUtc.IsEmpty() && !AppliedActionLabel.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class UNREALHYPERTWIST_API UHyperTwistCoachDashboardWidget : public UHyperTwistTrainingPanelWidget
|
||||
{
|
||||
|
|
@ -507,6 +532,15 @@ protected:
|
|||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> QueueRecoveryDetailTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> QueueRecoveryOutcomeHeaderTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> QueueRecoveryOutcomeStatusTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> QueueRecoveryOutcomeDetailTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> VerificationStatusTextBlock = nullptr;
|
||||
|
||||
|
|
@ -814,6 +848,16 @@ private:
|
|||
FHyperTwistCoachDashboardGuidanceLanePreference BuildGuidanceLanePreference() const;
|
||||
FString BuildGuidanceOutcomeActionLabel(EHyperTwistCoachDashboardOutcomeAction Action) const;
|
||||
FHyperTwistTrainingDeck BuildGuidanceOutcomeSelectedCaseDeck() const;
|
||||
void RecordQueueRecoveryOutcome(
|
||||
const FString& SourceLabel,
|
||||
const FString& AppliedActionLabel,
|
||||
float BeforePressureScore,
|
||||
int32 BeforeDeferredEventCount,
|
||||
bool bBeforeBlockedFollowUp,
|
||||
bool bBeforeOutstandingDeferred,
|
||||
bool bBeforeCompletionGap,
|
||||
bool bSucceeded
|
||||
);
|
||||
void RecordGuidanceOutcomeDecision(
|
||||
EHyperTwistCoachDashboardOutcomeAction RecommendedAction,
|
||||
EHyperTwistCoachDashboardOutcomeAction AppliedAction,
|
||||
|
|
@ -860,6 +904,8 @@ private:
|
|||
FHyperTwistTrainingDeck RetainedGuidanceComparisonDeck;
|
||||
FString RetainedGuidanceComparisonSelectedCaseId;
|
||||
TArray<FHyperTwistCoachDashboardDecisionHistoryEntry> GuidanceDecisionHistoryEntries;
|
||||
bool bHasQueueRecoveryOutcome = false;
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome QueueRecoveryOutcome;
|
||||
EHyperTwistCoachDashboardGuidancePreviewLane ActiveGuidancePreviewLane =
|
||||
EHyperTwistCoachDashboardGuidancePreviewLane::None;
|
||||
int32 SelectedGuidancePreviewCaseIndex = INDEX_NONE;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue