Weight queue recovery by dashboard outcomes
This commit is contained in:
parent
a4b69211bc
commit
f494f0707d
2 changed files with 375 additions and 144 deletions
|
|
@ -265,31 +265,24 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture()
|
|||
const bool bBeforeOutstandingDeferred = CachedCoachQueueExecutionSummary.bHasOutstandingDeferredWork;
|
||||
const bool bBeforeCompletionGap = CachedCoachQueueExecutionSummary.bHasCompletionGap;
|
||||
const FString SourceLabel = CachedCoachPanelState.PreferredGuidanceSourceLabel;
|
||||
const FHyperTwistCoachDashboardQueueRecoveryWeighting QueueRecoveryWeighting = BuildQueueRecoveryWeighting();
|
||||
|
||||
const FString DeferredEntryId = FindFirstDeferredQueueEntryId();
|
||||
if (!DeferredEntryId.IsEmpty())
|
||||
{
|
||||
const bool bSucceeded = PromoteFirstDeferredQueueEntry();
|
||||
RecordQueueRecoveryOutcome(
|
||||
SourceLabel,
|
||||
TEXT("promote-deferred-recovery"),
|
||||
BeforePressureScore,
|
||||
BeforeDeferredEventCount,
|
||||
bBeforeBlockedFollowUp,
|
||||
bBeforeOutstandingDeferred,
|
||||
bBeforeCompletionGap,
|
||||
bSucceeded
|
||||
);
|
||||
UpdateDashboardPresentation();
|
||||
return bSucceeded;
|
||||
}
|
||||
|
||||
const bool bQueueRecoveryMemorySource =
|
||||
CachedCoachPanelState.PreferredGuidanceSourceLabel.StartsWith(TEXT("coach-memory-queue-"));
|
||||
const bool bPrefersFollowUp =
|
||||
CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp;
|
||||
const bool bPrefersRecommended =
|
||||
CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended;
|
||||
const bool bCanLaunchQueueRecovery =
|
||||
CachedCoachPanelState.bCanStartQueuedRun
|
||||
|| (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance)
|
||||
|| CachedCoachPanelState.bCanStartCoachRecommendedRun;
|
||||
const bool bPreferLaunchBeforePromote =
|
||||
!DeferredEntryId.IsEmpty()
|
||||
&& bCanLaunchQueueRecovery
|
||||
&& QueueRecoveryWeighting.PreferredActionKind
|
||||
== FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery;
|
||||
|
||||
auto StartRunAndRefresh = [this](const FHyperTwistTrainingRunState& RunState)
|
||||
{
|
||||
|
|
@ -299,130 +292,145 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture()
|
|||
UpdateDashboardPresentation();
|
||||
return RunState.Session.IsStructurallyValid();
|
||||
};
|
||||
|
||||
if (bQueueRecoveryMemorySource && CachedCoachPanelState.bCanStartQueuedRun)
|
||||
{
|
||||
const bool bQueueMatchesPreference =
|
||||
(!bPrefersFollowUp && !bPrefersRecommended)
|
||||
|| (bPrefersFollowUp
|
||||
&& CachedCoachPanelState.NextQueueSourceLabel == TEXT("follow-up-brief"))
|
||||
|| (bPrefersRecommended
|
||||
&& CachedCoachPanelState.NextQueueSourceLabel == TEXT("primary-brief"));
|
||||
if (bQueueMatchesPreference)
|
||||
{
|
||||
const bool bSucceeded = StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases));
|
||||
RecordQueueRecoveryOutcome(
|
||||
SourceLabel,
|
||||
TEXT("launch-queued-recovery"),
|
||||
BeforePressureScore,
|
||||
BeforeDeferredEventCount,
|
||||
bBeforeBlockedFollowUp,
|
||||
bBeforeOutstandingDeferred,
|
||||
bBeforeCompletionGap,
|
||||
bSucceeded
|
||||
);
|
||||
UpdateDashboardPresentation();
|
||||
return bSucceeded;
|
||||
}
|
||||
}
|
||||
|
||||
if (bPrefersFollowUp
|
||||
&& CachedCoachPanelState.bCanStartCoachFollowUpRun
|
||||
&& CachedCoachPanelState.bHasFollowUpGuidance)
|
||||
{
|
||||
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)
|
||||
{
|
||||
const bool bSucceeded = StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases));
|
||||
RecordQueueRecoveryOutcome(
|
||||
SourceLabel,
|
||||
TEXT("launch-recommended-recovery"),
|
||||
BeforePressureScore,
|
||||
BeforeDeferredEventCount,
|
||||
bBeforeBlockedFollowUp,
|
||||
bBeforeOutstandingDeferred,
|
||||
bBeforeCompletionGap,
|
||||
bSucceeded
|
||||
);
|
||||
UpdateDashboardPresentation();
|
||||
return bSucceeded;
|
||||
}
|
||||
if (CachedCoachPanelState.bCanStartQueuedRun)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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();
|
||||
const bool bSucceeded = FallbackRunState.Session.IsStructurallyValid();
|
||||
RecordQueueRecoveryOutcome(
|
||||
SourceLabel,
|
||||
TEXT("launch-primary-fallback"),
|
||||
auto RecordAndFinalizeOutcome = [
|
||||
this,
|
||||
&SourceLabel,
|
||||
BeforePressureScore,
|
||||
BeforeDeferredEventCount,
|
||||
bBeforeBlockedFollowUp,
|
||||
bBeforeOutstandingDeferred,
|
||||
bBeforeCompletionGap,
|
||||
bSucceeded
|
||||
);
|
||||
UpdateDashboardPresentation();
|
||||
return bSucceeded;
|
||||
bBeforeCompletionGap
|
||||
](
|
||||
const TCHAR* AppliedActionLabel,
|
||||
const FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind ActionKind,
|
||||
const bool bSucceeded)
|
||||
{
|
||||
RecordQueueRecoveryOutcome(
|
||||
SourceLabel,
|
||||
AppliedActionLabel,
|
||||
ActionKind,
|
||||
BeforePressureScore,
|
||||
BeforeDeferredEventCount,
|
||||
bBeforeBlockedFollowUp,
|
||||
bBeforeOutstandingDeferred,
|
||||
bBeforeCompletionGap,
|
||||
bSucceeded
|
||||
);
|
||||
UpdateDashboardPresentation();
|
||||
return bSucceeded;
|
||||
};
|
||||
auto TryPromoteRecovery = [&]() -> bool
|
||||
{
|
||||
if (DeferredEntryId.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool bSucceeded = PromoteFirstDeferredQueueEntry();
|
||||
return RecordAndFinalizeOutcome(
|
||||
TEXT("promote-deferred-recovery"),
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::PromoteDeferred,
|
||||
bSucceeded
|
||||
);
|
||||
};
|
||||
auto TryLaunchRecovery = [&]() -> bool
|
||||
{
|
||||
if (bQueueRecoveryMemorySource && CachedCoachPanelState.bCanStartQueuedRun)
|
||||
{
|
||||
const bool bQueueMatchesPreference =
|
||||
(!bPrefersFollowUp && !bPrefersRecommended)
|
||||
|| (bPrefersFollowUp
|
||||
&& CachedCoachPanelState.NextQueueSourceLabel == TEXT("follow-up-brief"))
|
||||
|| (bPrefersRecommended
|
||||
&& CachedCoachPanelState.NextQueueSourceLabel == TEXT("primary-brief"));
|
||||
if (bQueueMatchesPreference)
|
||||
{
|
||||
const bool bSucceeded = StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases));
|
||||
return RecordAndFinalizeOutcome(
|
||||
TEXT("launch-queued-recovery"),
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery,
|
||||
bSucceeded
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (bPrefersFollowUp
|
||||
&& CachedCoachPanelState.bCanStartCoachFollowUpRun
|
||||
&& CachedCoachPanelState.bHasFollowUpGuidance)
|
||||
{
|
||||
const bool bSucceeded = StartRunAndRefresh(StartCoachFollowUpRun(DefaultCoachMaxCases));
|
||||
return RecordAndFinalizeOutcome(
|
||||
TEXT("launch-follow-up-recovery"),
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery,
|
||||
bSucceeded
|
||||
);
|
||||
}
|
||||
if (bPrefersRecommended && CachedCoachPanelState.bCanStartCoachRecommendedRun)
|
||||
{
|
||||
const bool bSucceeded = StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases));
|
||||
return RecordAndFinalizeOutcome(
|
||||
TEXT("launch-recommended-recovery"),
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery,
|
||||
bSucceeded
|
||||
);
|
||||
}
|
||||
if (CachedCoachPanelState.bCanStartQueuedRun)
|
||||
{
|
||||
const bool bSucceeded = StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases));
|
||||
return RecordAndFinalizeOutcome(
|
||||
TEXT("launch-queued-fallback"),
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery,
|
||||
bSucceeded
|
||||
);
|
||||
}
|
||||
if (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance)
|
||||
{
|
||||
const bool bSucceeded = StartRunAndRefresh(StartCoachFollowUpRun(DefaultCoachMaxCases));
|
||||
return RecordAndFinalizeOutcome(
|
||||
TEXT("launch-follow-up-fallback"),
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery,
|
||||
bSucceeded
|
||||
);
|
||||
}
|
||||
if (CachedCoachPanelState.bCanStartCoachRecommendedRun)
|
||||
{
|
||||
const bool bSucceeded = StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases));
|
||||
return RecordAndFinalizeOutcome(
|
||||
TEXT("launch-recommended-fallback"),
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery,
|
||||
bSucceeded
|
||||
);
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingRunState FallbackRunState = ExecutePrimaryCoachAction();
|
||||
const bool bSucceeded = FallbackRunState.Session.IsStructurallyValid();
|
||||
return RecordAndFinalizeOutcome(
|
||||
TEXT("launch-primary-fallback"),
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery,
|
||||
bSucceeded
|
||||
);
|
||||
};
|
||||
|
||||
if (bPreferLaunchBeforePromote)
|
||||
{
|
||||
const bool bLaunchSucceeded = TryLaunchRecovery();
|
||||
if (bLaunchSucceeded)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!DeferredEntryId.IsEmpty())
|
||||
{
|
||||
return TryPromoteRecovery();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DeferredEntryId.IsEmpty())
|
||||
{
|
||||
return TryPromoteRecovery();
|
||||
}
|
||||
|
||||
return TryLaunchRecovery();
|
||||
}
|
||||
|
||||
bool UHyperTwistCoachDashboardWidget::SelectPreviousQueueEntry()
|
||||
|
|
@ -3119,6 +3127,151 @@ FHyperTwistCoachDashboardGuidanceLanePreference UHyperTwistCoachDashboardWidget:
|
|||
return Preference;
|
||||
}
|
||||
|
||||
FHyperTwistCoachDashboardQueueRecoveryWeighting UHyperTwistCoachDashboardWidget::BuildQueueRecoveryWeighting() const
|
||||
{
|
||||
FHyperTwistCoachDashboardQueueRecoveryWeighting Weighting;
|
||||
float PromoteDeltaAccumulator = 0.0f;
|
||||
float LaunchDeltaAccumulator = 0.0f;
|
||||
|
||||
auto ConsumeOutcome = [
|
||||
&Weighting,
|
||||
&PromoteDeltaAccumulator,
|
||||
&LaunchDeltaAccumulator
|
||||
](const FHyperTwistCoachDashboardQueueRecoveryOutcome& Outcome)
|
||||
{
|
||||
if (!Outcome.IsStructurallyValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const float PressureDelta = Outcome.BeforePressureScore - Outcome.AfterPressureScore;
|
||||
switch (Outcome.ActionKind)
|
||||
{
|
||||
case FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::PromoteDeferred:
|
||||
++Weighting.PromoteCount;
|
||||
PromoteDeltaAccumulator += PressureDelta;
|
||||
if (Outcome.bReducedFriction)
|
||||
{
|
||||
++Weighting.PromoteReducedCount;
|
||||
}
|
||||
if (!Outcome.bSucceeded)
|
||||
{
|
||||
++Weighting.PromoteFailureCount;
|
||||
}
|
||||
break;
|
||||
case FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery:
|
||||
++Weighting.LaunchCount;
|
||||
LaunchDeltaAccumulator += PressureDelta;
|
||||
if (Outcome.bReducedFriction)
|
||||
{
|
||||
++Weighting.LaunchReducedCount;
|
||||
}
|
||||
if (!Outcome.bSucceeded)
|
||||
{
|
||||
++Weighting.LaunchFailureCount;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
for (const FHyperTwistCoachDashboardQueueRecoveryOutcome& Outcome : QueueRecoveryOutcomeHistory)
|
||||
{
|
||||
ConsumeOutcome(Outcome);
|
||||
}
|
||||
|
||||
if (QueueRecoveryOutcome.IsStructurallyValid()
|
||||
&& !QueueRecoveryOutcomeHistory.ContainsByPredicate(
|
||||
[this](const FHyperTwistCoachDashboardQueueRecoveryOutcome& Candidate)
|
||||
{
|
||||
return Candidate.RecordedAtUtc == QueueRecoveryOutcome.RecordedAtUtc
|
||||
&& Candidate.AppliedActionLabel == QueueRecoveryOutcome.AppliedActionLabel
|
||||
&& Candidate.ActionKind == QueueRecoveryOutcome.ActionKind;
|
||||
}))
|
||||
{
|
||||
ConsumeOutcome(QueueRecoveryOutcome);
|
||||
}
|
||||
|
||||
if (Weighting.PromoteCount > 0)
|
||||
{
|
||||
Weighting.PromoteAveragePressureDelta =
|
||||
PromoteDeltaAccumulator / static_cast<float>(Weighting.PromoteCount);
|
||||
}
|
||||
if (Weighting.LaunchCount > 0)
|
||||
{
|
||||
Weighting.LaunchAveragePressureDelta =
|
||||
LaunchDeltaAccumulator / static_cast<float>(Weighting.LaunchCount);
|
||||
}
|
||||
|
||||
Weighting.bHasSignal = Weighting.PromoteCount > 0 || Weighting.LaunchCount > 0;
|
||||
if (!Weighting.bHasSignal)
|
||||
{
|
||||
return Weighting;
|
||||
}
|
||||
|
||||
auto ScoreAction = [](const int32 Count, const int32 ReducedCount, const int32 FailureCount, const float AverageDelta)
|
||||
{
|
||||
if (Count <= 0)
|
||||
{
|
||||
return -1000000.0f;
|
||||
}
|
||||
|
||||
const int32 NeutralCount = FMath::Max(Count - ReducedCount - FailureCount, 0);
|
||||
return static_cast<float>(ReducedCount) * 3.0f
|
||||
+ static_cast<float>(NeutralCount) * 0.5f
|
||||
+ AverageDelta
|
||||
- static_cast<float>(FailureCount) * 2.0f;
|
||||
};
|
||||
|
||||
const float PromoteScore = ScoreAction(
|
||||
Weighting.PromoteCount,
|
||||
Weighting.PromoteReducedCount,
|
||||
Weighting.PromoteFailureCount,
|
||||
Weighting.PromoteAveragePressureDelta
|
||||
);
|
||||
const float LaunchScore = ScoreAction(
|
||||
Weighting.LaunchCount,
|
||||
Weighting.LaunchReducedCount,
|
||||
Weighting.LaunchFailureCount,
|
||||
Weighting.LaunchAveragePressureDelta
|
||||
);
|
||||
|
||||
if (PromoteScore > LaunchScore)
|
||||
{
|
||||
Weighting.PreferredActionKind =
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::PromoteDeferred;
|
||||
}
|
||||
else if (LaunchScore > PromoteScore)
|
||||
{
|
||||
Weighting.PreferredActionKind =
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int32 Index = QueueRecoveryOutcomeHistory.Num() - 1; Index >= 0; --Index)
|
||||
{
|
||||
const FHyperTwistCoachDashboardQueueRecoveryOutcome& Outcome = QueueRecoveryOutcomeHistory[Index];
|
||||
if (Outcome.IsStructurallyValid()
|
||||
&& Outcome.ActionKind != FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::None)
|
||||
{
|
||||
Weighting.PreferredActionKind = Outcome.ActionKind;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Weighting.PreferredActionKind
|
||||
== FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::None)
|
||||
{
|
||||
Weighting.PreferredActionKind =
|
||||
Weighting.PromoteCount >= Weighting.LaunchCount
|
||||
? FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::PromoteDeferred
|
||||
: FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery;
|
||||
}
|
||||
}
|
||||
|
||||
return Weighting;
|
||||
}
|
||||
|
||||
FString UHyperTwistCoachDashboardWidget::BuildGuidanceOutcomeActionLabel(
|
||||
const EHyperTwistCoachDashboardOutcomeAction Action) const
|
||||
{
|
||||
|
|
@ -3232,6 +3385,7 @@ void UHyperTwistCoachDashboardWidget::RecordGuidanceOutcomeDecision(
|
|||
void UHyperTwistCoachDashboardWidget::RecordQueueRecoveryOutcome(
|
||||
const FString& SourceLabel,
|
||||
const FString& AppliedActionLabel,
|
||||
const FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind ActionKind,
|
||||
const float BeforePressureScore,
|
||||
const int32 BeforeDeferredEventCount,
|
||||
const bool bBeforeBlockedFollowUp,
|
||||
|
|
@ -3242,6 +3396,7 @@ void UHyperTwistCoachDashboardWidget::RecordQueueRecoveryOutcome(
|
|||
QueueRecoveryOutcome.RecordedAtUtc = FDateTime::UtcNow().ToIso8601();
|
||||
QueueRecoveryOutcome.SourceLabel = SourceLabel;
|
||||
QueueRecoveryOutcome.AppliedActionLabel = AppliedActionLabel;
|
||||
QueueRecoveryOutcome.ActionKind = ActionKind;
|
||||
QueueRecoveryOutcome.BeforePressureScore = BeforePressureScore;
|
||||
QueueRecoveryOutcome.AfterPressureScore = CachedCoachSchedulePolicySummary.ReschedulePressureScore;
|
||||
QueueRecoveryOutcome.BeforeDeferredEventCount = BeforeDeferredEventCount;
|
||||
|
|
@ -3269,6 +3424,16 @@ void UHyperTwistCoachDashboardWidget::RecordQueueRecoveryOutcome(
|
|||
|| QueueRecoveryOutcome.bAfterCompletionGap != QueueRecoveryOutcome.bBeforeCompletionGap
|
||||
);
|
||||
bHasQueueRecoveryOutcome = QueueRecoveryOutcome.IsStructurallyValid();
|
||||
if (bHasQueueRecoveryOutcome)
|
||||
{
|
||||
QueueRecoveryOutcomeHistory.Add(QueueRecoveryOutcome);
|
||||
constexpr int32 MaxRetainedQueueRecoveryOutcomes = 16;
|
||||
if (QueueRecoveryOutcomeHistory.Num() > MaxRetainedQueueRecoveryOutcomes)
|
||||
{
|
||||
const int32 ExcessCount = QueueRecoveryOutcomeHistory.Num() - MaxRetainedQueueRecoveryOutcomes;
|
||||
QueueRecoveryOutcomeHistory.RemoveAt(0, ExcessCount, EAllowShrinking::No);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
||||
|
|
@ -3693,6 +3858,24 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
const FString QueueRecoverySourceLabel = CachedCoachPanelState.PreferredGuidanceSourceLabel.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: CachedCoachPanelState.PreferredGuidanceSourceLabel;
|
||||
const FHyperTwistCoachDashboardQueueRecoveryWeighting QueueRecoveryWeighting = BuildQueueRecoveryWeighting();
|
||||
const bool bCanLaunchQueueRecovery =
|
||||
CachedCoachPanelState.bCanStartQueuedRun
|
||||
|| (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance)
|
||||
|| CachedCoachPanelState.bCanStartCoachRecommendedRun;
|
||||
const bool bPreferLaunchRecoveryAction =
|
||||
!FindFirstDeferredQueueEntryId().IsEmpty()
|
||||
&& bCanLaunchQueueRecovery
|
||||
&& QueueRecoveryWeighting.PreferredActionKind
|
||||
== FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery;
|
||||
const FString QueueRecoveryWeightingLabel =
|
||||
QueueRecoveryWeighting.PreferredActionKind
|
||||
== FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::PromoteDeferred
|
||||
? TEXT("prefer promote-deferred recovery")
|
||||
: (QueueRecoveryWeighting.PreferredActionKind
|
||||
== FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery
|
||||
? TEXT("prefer launch-recovery")
|
||||
: TEXT("no learned preference"));
|
||||
const bool bHasQueueRecoveryFriction =
|
||||
CachedCoachSchedulePolicySummary.bHasBlockedFollowUpDeferrals
|
||||
|| CachedCoachSchedulePolicySummary.bHasArchiveAwareDeferrals
|
||||
|
|
@ -3701,20 +3884,27 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
|| CachedCoachQueueExecutionSummary.bHasCompletionGap;
|
||||
const FString QueueRecoveryStatusLine = bQueueRecoveryMemorySource
|
||||
? FString::Printf(
|
||||
TEXT("Recovery: %s lane | Source: %s | Action: %s | Mode: %s | Selection: %s"),
|
||||
TEXT("Recovery: %s lane | Source: %s | Action: %s | Mode: %s | Selection: %s | Learned: %s"),
|
||||
*QueueRecoveryLaneLabel,
|
||||
*QueueRecoverySourceLabel,
|
||||
CachedCoachPanelState.PrimaryActionLabel.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *CachedCoachPanelState.PrimaryActionLabel,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(CachedCoachPanelState.SuggestedMode),
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(CachedCoachPanelState.SuggestedSelectionPolicy)
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(CachedCoachPanelState.SuggestedSelectionPolicy),
|
||||
*QueueRecoveryWeightingLabel
|
||||
)
|
||||
: (bHasQueueRecoveryFriction
|
||||
? TEXT("Recovery: queue friction is present, but no queue-memory recovery override is currently active.")
|
||||
: TEXT("Recovery: no queue-memory recovery override is currently active."));
|
||||
? FString::Printf(
|
||||
TEXT("Recovery: queue friction is present, but no queue-memory recovery override is currently active | Learned: %s"),
|
||||
*QueueRecoveryWeightingLabel
|
||||
)
|
||||
: FString::Printf(
|
||||
TEXT("Recovery: no queue-memory recovery override is currently active | Learned: %s"),
|
||||
*QueueRecoveryWeightingLabel
|
||||
));
|
||||
const FString QueueRecoveryDetailLine = FString::Printf(
|
||||
TEXT("Deferred events %d | Promoted %d | Manual %d | Pressure %.2f | Dominant reason: %s | Follow-up deferrals: %s | Archive-aware deferrals: %s | Completion gap: %s"),
|
||||
TEXT("Deferred events %d | Promoted %d | Manual %d | Pressure %.2f | Dominant reason: %s | Follow-up deferrals: %s | Archive-aware deferrals: %s | Completion gap: %s || Learned promote %d/%d reduced, %d failed, avg delta %.2f | launch %d/%d reduced, %d failed, avg delta %.2f"),
|
||||
CachedCoachQueueExecutionSummary.DeferredEventCount,
|
||||
CachedCoachQueueExecutionSummary.PromotedEventCount,
|
||||
CachedCoachQueueExecutionSummary.ManualRescheduleEventCount,
|
||||
|
|
@ -3724,7 +3914,15 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
: *CachedCoachSchedulePolicySummary.DominantDeferralReasonLabel,
|
||||
CachedCoachSchedulePolicySummary.bHasBlockedFollowUpDeferrals ? TEXT("yes") : TEXT("no"),
|
||||
CachedCoachSchedulePolicySummary.bHasArchiveAwareDeferrals ? TEXT("yes") : TEXT("no"),
|
||||
CachedCoachQueueExecutionSummary.bHasCompletionGap ? TEXT("yes") : TEXT("no")
|
||||
CachedCoachQueueExecutionSummary.bHasCompletionGap ? TEXT("yes") : TEXT("no"),
|
||||
QueueRecoveryWeighting.PromoteReducedCount,
|
||||
QueueRecoveryWeighting.PromoteCount,
|
||||
QueueRecoveryWeighting.PromoteFailureCount,
|
||||
QueueRecoveryWeighting.PromoteAveragePressureDelta,
|
||||
QueueRecoveryWeighting.LaunchReducedCount,
|
||||
QueueRecoveryWeighting.LaunchCount,
|
||||
QueueRecoveryWeighting.LaunchFailureCount,
|
||||
QueueRecoveryWeighting.LaunchAveragePressureDelta
|
||||
);
|
||||
const FString QueueRecoveryOutcomeStatusLine = !bHasQueueRecoveryOutcome || !QueueRecoveryOutcome.IsStructurallyValid()
|
||||
? TEXT("Outcome: no queue-recovery action has been applied yet.")
|
||||
|
|
@ -3781,7 +3979,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
|| (!bQueueRecoveryMemorySource
|
||||
&& (CachedCoachPanelState.bCanStartCoachFollowUpRun
|
||||
|| CachedCoachPanelState.bCanStartCoachRecommendedRun)));
|
||||
const FString QueueRecoveryActionLabel = !PreferredDeferredRecoveryEntryId.IsEmpty()
|
||||
const FString QueueRecoveryActionLabel =
|
||||
!PreferredDeferredRecoveryEntryId.IsEmpty() && !bPreferLaunchRecoveryAction
|
||||
? (bQueueRecoveryFollowUp
|
||||
? TEXT("Promote Follow-Up Recovery")
|
||||
: (bQueueRecoveryRecommended
|
||||
|
|
|
|||
|
|
@ -104,9 +104,17 @@ struct FHyperTwistCoachDashboardGuidanceLanePreference
|
|||
|
||||
struct FHyperTwistCoachDashboardQueueRecoveryOutcome
|
||||
{
|
||||
enum class EActionKind : uint8
|
||||
{
|
||||
None,
|
||||
PromoteDeferred,
|
||||
LaunchRecovery
|
||||
};
|
||||
|
||||
FString RecordedAtUtc;
|
||||
FString SourceLabel;
|
||||
FString AppliedActionLabel;
|
||||
EActionKind ActionKind = EActionKind::None;
|
||||
float BeforePressureScore = 0.0f;
|
||||
float AfterPressureScore = 0.0f;
|
||||
int32 BeforeDeferredEventCount = 0;
|
||||
|
|
@ -127,6 +135,27 @@ struct FHyperTwistCoachDashboardQueueRecoveryOutcome
|
|||
}
|
||||
};
|
||||
|
||||
struct FHyperTwistCoachDashboardQueueRecoveryWeighting
|
||||
{
|
||||
int32 PromoteCount = 0;
|
||||
int32 PromoteReducedCount = 0;
|
||||
int32 PromoteFailureCount = 0;
|
||||
int32 LaunchCount = 0;
|
||||
int32 LaunchReducedCount = 0;
|
||||
int32 LaunchFailureCount = 0;
|
||||
float PromoteAveragePressureDelta = 0.0f;
|
||||
float LaunchAveragePressureDelta = 0.0f;
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind PreferredActionKind =
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::None;
|
||||
bool bHasSignal = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return bHasSignal
|
||||
&& PreferredActionKind != FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::None;
|
||||
}
|
||||
};
|
||||
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class UNREALHYPERTWIST_API UHyperTwistCoachDashboardWidget : public UHyperTwistTrainingPanelWidget
|
||||
{
|
||||
|
|
@ -846,11 +875,13 @@ private:
|
|||
EHyperTwistCoachDashboardOutcomeAction BaseAction
|
||||
) const;
|
||||
FHyperTwistCoachDashboardGuidanceLanePreference BuildGuidanceLanePreference() const;
|
||||
FHyperTwistCoachDashboardQueueRecoveryWeighting BuildQueueRecoveryWeighting() const;
|
||||
FString BuildGuidanceOutcomeActionLabel(EHyperTwistCoachDashboardOutcomeAction Action) const;
|
||||
FHyperTwistTrainingDeck BuildGuidanceOutcomeSelectedCaseDeck() const;
|
||||
void RecordQueueRecoveryOutcome(
|
||||
const FString& SourceLabel,
|
||||
const FString& AppliedActionLabel,
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind ActionKind,
|
||||
float BeforePressureScore,
|
||||
int32 BeforeDeferredEventCount,
|
||||
bool bBeforeBlockedFollowUp,
|
||||
|
|
@ -906,6 +937,7 @@ private:
|
|||
TArray<FHyperTwistCoachDashboardDecisionHistoryEntry> GuidanceDecisionHistoryEntries;
|
||||
bool bHasQueueRecoveryOutcome = false;
|
||||
FHyperTwistCoachDashboardQueueRecoveryOutcome QueueRecoveryOutcome;
|
||||
TArray<FHyperTwistCoachDashboardQueueRecoveryOutcome> QueueRecoveryOutcomeHistory;
|
||||
EHyperTwistCoachDashboardGuidancePreviewLane ActiveGuidancePreviewLane =
|
||||
EHyperTwistCoachDashboardGuidancePreviewLane::None;
|
||||
int32 SelectedGuidancePreviewCaseIndex = INDEX_NONE;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue