Derive queue recovery action from coach memory

This commit is contained in:
axiomlogicnexus 2026-04-27 19:07:17 +02:00
parent f494f0707d
commit 42a4f48198
5 changed files with 142 additions and 16 deletions

View file

@ -274,6 +274,9 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture()
CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp;
const bool bPrefersRecommended =
CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended;
const bool bUpstreamPrefersLaunchRecovery =
CachedCoachPanelState.PreferredQueueRecoveryAction
== EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery;
const bool bCanLaunchQueueRecovery =
CachedCoachPanelState.bCanStartQueuedRun
|| (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance)
@ -281,8 +284,9 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture()
const bool bPreferLaunchBeforePromote =
!DeferredEntryId.IsEmpty()
&& bCanLaunchQueueRecovery
&& QueueRecoveryWeighting.PreferredActionKind
== FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery;
&& (QueueRecoveryWeighting.PreferredActionKind
== FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery
|| (!QueueRecoveryWeighting.IsStructurallyValid() && bUpstreamPrefersLaunchRecovery));
auto StartRunAndRefresh = [this](const FHyperTwistTrainingRunState& RunState)
{
@ -3858,16 +3862,27 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
const FString QueueRecoverySourceLabel = CachedCoachPanelState.PreferredGuidanceSourceLabel.IsEmpty()
? TEXT("n/a")
: CachedCoachPanelState.PreferredGuidanceSourceLabel;
const FString QueueRecoveryActionSourceLabel =
CachedCoachPanelState.PreferredQueueRecoveryActionSourceLabel.IsEmpty()
? TEXT("n/a")
: CachedCoachPanelState.PreferredQueueRecoveryActionSourceLabel;
const FHyperTwistCoachDashboardQueueRecoveryWeighting QueueRecoveryWeighting = BuildQueueRecoveryWeighting();
const bool bCanLaunchQueueRecovery =
CachedCoachPanelState.bCanStartQueuedRun
|| (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance)
|| CachedCoachPanelState.bCanStartCoachRecommendedRun;
const bool bUpstreamPrefersPromoteRecovery =
CachedCoachPanelState.PreferredQueueRecoveryAction
== EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred;
const bool bUpstreamPrefersLaunchRecovery =
CachedCoachPanelState.PreferredQueueRecoveryAction
== EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery;
const bool bPreferLaunchRecoveryAction =
!FindFirstDeferredQueueEntryId().IsEmpty()
&& bCanLaunchQueueRecovery
&& QueueRecoveryWeighting.PreferredActionKind
== FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery;
&& (QueueRecoveryWeighting.PreferredActionKind
== FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery
|| (!QueueRecoveryWeighting.IsStructurallyValid() && bUpstreamPrefersLaunchRecovery));
const FString QueueRecoveryWeightingLabel =
QueueRecoveryWeighting.PreferredActionKind
== FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::PromoteDeferred
@ -3876,6 +3891,11 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
== FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery
? TEXT("prefer launch-recovery")
: TEXT("no learned preference"));
const FString QueueRecoveryUpstreamActionLabel = bUpstreamPrefersPromoteRecovery
? TEXT("prefer promote-deferred recovery")
: (bUpstreamPrefersLaunchRecovery
? TEXT("prefer launch-recovery")
: TEXT("no upstream preference"));
const bool bHasQueueRecoveryFriction =
CachedCoachSchedulePolicySummary.bHasBlockedFollowUpDeferrals
|| CachedCoachSchedulePolicySummary.bHasArchiveAwareDeferrals
@ -3884,7 +3904,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|| CachedCoachQueueExecutionSummary.bHasCompletionGap;
const FString QueueRecoveryStatusLine = bQueueRecoveryMemorySource
? FString::Printf(
TEXT("Recovery: %s lane | Source: %s | Action: %s | Mode: %s | Selection: %s | Learned: %s"),
TEXT("Recovery: %s lane | Source: %s | Action: %s | Mode: %s | Selection: %s | Upstream: %s | Learned: %s"),
*QueueRecoveryLaneLabel,
*QueueRecoverySourceLabel,
CachedCoachPanelState.PrimaryActionLabel.IsEmpty()
@ -3892,19 +3912,22 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
: *CachedCoachPanelState.PrimaryActionLabel,
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(CachedCoachPanelState.SuggestedMode),
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(CachedCoachPanelState.SuggestedSelectionPolicy),
*QueueRecoveryUpstreamActionLabel,
*QueueRecoveryWeightingLabel
)
: (bHasQueueRecoveryFriction
? FString::Printf(
TEXT("Recovery: queue friction is present, but no queue-memory recovery override is currently active | Learned: %s"),
TEXT("Recovery: queue friction is present, but no queue-memory recovery override is currently active | Upstream: %s | Learned: %s"),
*QueueRecoveryUpstreamActionLabel,
*QueueRecoveryWeightingLabel
)
: FString::Printf(
TEXT("Recovery: no queue-memory recovery override is currently active | Learned: %s"),
TEXT("Recovery: no queue-memory recovery override is currently active | Upstream: %s | Learned: %s"),
*QueueRecoveryUpstreamActionLabel,
*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 || Learned promote %d/%d reduced, %d failed, avg delta %.2f | launch %d/%d reduced, %d failed, avg delta %.2f"),
TEXT("Deferred events %d | Promoted %d | Manual %d | Pressure %.2f | Dominant reason: %s | Follow-up deferrals: %s | Archive-aware deferrals: %s | Completion gap: %s | Upstream action source: %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,
@ -3915,6 +3938,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
CachedCoachSchedulePolicySummary.bHasBlockedFollowUpDeferrals ? TEXT("yes") : TEXT("no"),
CachedCoachSchedulePolicySummary.bHasArchiveAwareDeferrals ? TEXT("yes") : TEXT("no"),
CachedCoachQueueExecutionSummary.bHasCompletionGap ? TEXT("yes") : TEXT("no"),
*QueueRecoveryActionSourceLabel,
QueueRecoveryWeighting.PromoteReducedCount,
QueueRecoveryWeighting.PromoteCount,
QueueRecoveryWeighting.PromoteFailureCount,

View file

@ -352,6 +352,12 @@ TArray<FHyperTwistCoachSignal> UHyperTwistTrainingCoachLibrary::DeriveCoachSigna
const bool bQueueRecoveryRecommended =
bQueueRecoverySource
&& CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended;
const bool bPromoteQueueRecovery =
CoachMemorySnapshot.PreferredQueueRecoveryAction
== EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred;
const bool bLaunchQueueRecovery =
CoachMemorySnapshot.PreferredQueueRecoveryAction
== EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery;
const float Score = 66.0f
+ FMath::Min(CoachMemorySnapshot.ScheduleExecutionPressureScore * 0.25f, 20.0f)
+ (bArchiveAware ? 5.0f : 0.0f)
@ -364,24 +370,47 @@ TArray<FHyperTwistCoachSignal> UHyperTwistTrainingCoachLibrary::DeriveCoachSigna
? EHyperTwistCoachFocusLane::Stability
: EHyperTwistCoachFocusLane::Cadence;
const FString Headline = bQueueRecoveryFollowUp
? TEXT("Recover the deferred follow-up lane before the queue drifts further")
? (bPromoteQueueRecovery
? TEXT("Promote the deferred follow-up lane back into active queue flow")
: TEXT("Recover the deferred follow-up lane before the queue drifts further"))
: (bQueueRecoveryRecommended
? TEXT("Recover the deferred recommended lane and restore queue cadence")
? (bPromoteQueueRecovery
? TEXT("Promote the deferred recommended lane back into the active queue")
: TEXT("Recover the deferred recommended lane and restore queue cadence"))
: (bBlockedFollowUp
? TEXT("Follow-up scheduling friction is stalling the queue")
? (bPromoteQueueRecovery
? TEXT("Promote deferred work to relieve follow-up scheduling friction")
: TEXT("Follow-up scheduling friction is stalling the queue"))
: (bArchiveAware
? TEXT("Archive-aware scheduling friction needs cleanup")
: TEXT("Coaching queue scheduling needs intervention"))));
: (bLaunchQueueRecovery
? TEXT("Launch recovery work to restore coaching queue cadence")
: TEXT("Coaching queue scheduling needs intervention")))));
const FString DeferralReasonLabel = !CoachMemorySnapshot.DominantScheduleDeferralReasonLabel.IsEmpty()
? CoachMemorySnapshot.DominantScheduleDeferralReasonLabel
: TEXT("none");
const FString RecoverySourceLabel = bQueueRecoverySource
? CoachMemorySnapshot.PreferredGuidanceSourceLabel
: TEXT("none");
const FString RecoveryActionLabel =
CoachMemorySnapshot.PreferredQueueRecoveryAction == EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred
? TEXT("promote-deferred")
: (CoachMemorySnapshot.PreferredQueueRecoveryAction
== EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery
? TEXT("launch-recovery")
: TEXT("none"));
const FString RecoveryActionSourceLabel =
CoachMemorySnapshot.PreferredQueueRecoveryActionSourceLabel.IsEmpty()
? TEXT("none")
: CoachMemorySnapshot.PreferredQueueRecoveryActionSourceLabel;
const FString SignalId = bQueueRecoveryFollowUp
? TEXT("schedule_friction_follow_up_recovery")
? (bPromoteQueueRecovery
? TEXT("schedule_friction_follow_up_promote_recovery")
: TEXT("schedule_friction_follow_up_recovery"))
: (bQueueRecoveryRecommended
? TEXT("schedule_friction_recommended_recovery")
? (bPromoteQueueRecovery
? TEXT("schedule_friction_recommended_promote_recovery")
: TEXT("schedule_friction_recommended_recovery"))
: TEXT("schedule_friction"));
const EHyperTwistTrainingDeliveryMode SuggestedMode = bQueueRecoveryFollowUp
? EHyperTwistTrainingDeliveryMode::CoachReviewed
@ -404,14 +433,16 @@ TArray<FHyperTwistCoachSignal> UHyperTwistTrainingCoachLibrary::DeriveCoachSigna
Score,
Headline,
FString::Printf(
TEXT("deferred=%d, manual=%d, archiveAware=%d, blockedFollowUp=%d, completionGap=%d, deferralReason=%s, recoverySource=%s"),
TEXT("deferred=%d, manual=%d, archiveAware=%d, blockedFollowUp=%d, completionGap=%d, deferralReason=%s, recoverySource=%s, recoveryAction=%s, recoveryActionSource=%s"),
CoachMemorySnapshot.ScheduleDeferredEventCount,
CoachMemorySnapshot.ScheduleManualRescheduleEventCount,
CoachMemorySnapshot.ScheduleArchiveAwareEventCount,
CoachMemorySnapshot.ScheduleBlockedFollowUpEventCount,
CoachMemorySnapshot.bHasQueueExecutionCompletionGap ? 1 : 0,
*DeferralReasonLabel,
*RecoverySourceLabel),
*RecoverySourceLabel,
*RecoveryActionLabel,
*RecoveryActionSourceLabel),
SuggestedMode,
SuggestedSelectionPolicy);
Signal.SuggestedDeckId = CoachMemorySnapshot.FocusDeckId;

View file

@ -5217,6 +5217,43 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der
DominantDeferredLane == EHyperTwistTrainingCoachGuidanceLane::Recommended
|| (DominantCompletedLane == EHyperTwistTrainingCoachGuidanceLane::Recommended
&& QueueExecutionSummary.PromotedEventCount > 0);
const float PromoteRecoveryActionPressure =
(QueueExecutionSummary.bHasOutstandingDeferredWork ? 4.0f : 0.0f)
+ FMath::Min(static_cast<float>(QueueExecutionSummary.DeferredEventCount) * 0.75f, 3.0f)
+ FMath::Min(static_cast<float>(QueueExecutionSummary.PromotedEventCount), 3.0f)
+ (DominantDeferredLane != EHyperTwistTrainingCoachGuidanceLane::None ? 1.5f : 0.0f)
- (QueueExecutionSummary.bHasCompletionGap ? 1.0f : 0.0f);
const float LaunchRecoveryActionPressure =
(QueueExecutionSummary.bHasCompletionGap ? 4.0f : 0.0f)
+ (QueueExecutionSummary.bHasBlockedFollowUpFriction ? 2.5f : 0.0f)
+ (QueueExecutionSummary.bHasRepeatManualRescheduleFriction ? 2.0f : 0.0f)
+ (QueueExecutionSummary.bHasArchiveAwareFriction ? 1.0f : 0.0f)
+ FMath::Min(static_cast<float>(QueueExecutionSummary.CompletedEventCount), 3.0f)
+ FMath::Min(static_cast<float>(QueueExecutionSummary.ManualRescheduleEventCount) * 0.5f, 2.0f);
if (PromoteRecoveryActionPressure > 0.0f || LaunchRecoveryActionPressure > 0.0f)
{
if (PromoteRecoveryActionPressure > LaunchRecoveryActionPressure)
{
Snapshot.PreferredQueueRecoveryAction =
EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred;
Snapshot.PreferredQueueRecoveryActionSourceLabel = TEXT("queue-history-promote-pressure");
}
else if (LaunchRecoveryActionPressure > PromoteRecoveryActionPressure)
{
Snapshot.PreferredQueueRecoveryAction =
EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery;
Snapshot.PreferredQueueRecoveryActionSourceLabel = TEXT("queue-history-launch-pressure");
}
else
{
Snapshot.PreferredQueueRecoveryAction =
QueueExecutionSummary.bHasOutstandingDeferredWork
&& !QueueExecutionSummary.bHasCompletionGap
? EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred
: EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery;
Snapshot.PreferredQueueRecoveryActionSourceLabel = TEXT("queue-history-balanced-pressure");
}
}
if (bQueueHistoryPrefersFollowUp)
{
@ -5364,6 +5401,15 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der
: EHyperTwistTrainingCoachGuidanceLane::FollowUp;
Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-default");
}
if (Snapshot.PreferredQueueRecoveryAction == EHyperTwistTrainingQueueRecoveryAction::None
&& Snapshot.bNeedsScheduleFrictionRecovery)
{
Snapshot.PreferredQueueRecoveryAction = Snapshot.bHasOutstandingDeferredScheduleWork
&& !Snapshot.bHasQueueExecutionCompletionGap
? EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred
: EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery;
Snapshot.PreferredQueueRecoveryActionSourceLabel = TEXT("coach-memory-schedule-default");
}
return Snapshot;
}

View file

@ -322,6 +322,9 @@ FHyperTwistTrainingCoachPanelState UHyperTwistTrainingSubsystem::GetActiveCoachP
PanelState.SignalCount = ActiveCoachSignals.Num();
PanelState.PreferredGuidanceLane = ActiveCoachMemorySnapshot.PreferredGuidanceLane;
PanelState.PreferredGuidanceSourceLabel = ActiveCoachMemorySnapshot.PreferredGuidanceSourceLabel;
PanelState.PreferredQueueRecoveryAction = ActiveCoachMemorySnapshot.PreferredQueueRecoveryAction;
PanelState.PreferredQueueRecoveryActionSourceLabel =
ActiveCoachMemorySnapshot.PreferredQueueRecoveryActionSourceLabel;
PanelState.bRepositoryVerificationPassed = ActiveRepositoryRoundTripVerification.bDidRoundTripPass;
PanelState.bRepositoryVerificationNeedsAttention = ActiveRepositoryRoundTripVerification.ResolvedPath.IsEmpty()
|| !ActiveRepositoryRoundTripVerification.bDidRoundTripPass;

View file

@ -32,6 +32,14 @@ enum class EHyperTwistTrainingCoachGuidanceLane : uint8
FollowUp UMETA(DisplayName = "Follow-Up")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingQueueRecoveryAction : uint8
{
None UMETA(DisplayName = "None"),
PromoteDeferred UMETA(DisplayName = "Promote Deferred"),
LaunchRecovery UMETA(DisplayName = "Launch Recovery")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingPromptKind : uint8
{
@ -1837,6 +1845,13 @@ struct FHyperTwistTrainingCoachMemorySnapshot
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreferredGuidanceSourceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingQueueRecoveryAction PreferredQueueRecoveryAction =
EHyperTwistTrainingQueueRecoveryAction::None;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreferredQueueRecoveryActionSourceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RecommendedGuidancePlanCount = 0;
@ -4281,6 +4296,13 @@ struct FHyperTwistTrainingCoachPanelState
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreferredGuidanceSourceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingQueueRecoveryAction PreferredQueueRecoveryAction =
EHyperTwistTrainingQueueRecoveryAction::None;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreferredQueueRecoveryActionSourceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SignalCount = 0;