Close Phase 3 live schedule-policy feedback loop
This commit is contained in:
parent
adf90c7d5c
commit
8c202cba97
4 changed files with 365 additions and 0 deletions
|
|
@ -4335,6 +4335,251 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithQueue
|
|||
return Brief;
|
||||
}
|
||||
|
||||
FHyperTwistCoachSignal UHyperTwistTrainingCoachLibrary::DeriveSchedulePolicyFeedbackSignal(
|
||||
const FHyperTwistCoachSchedulePolicySummary& SchedulePolicySummary,
|
||||
const FHyperTwistTrainingCoachSessionQueueExecutionSummary& QueueExecutionSummary,
|
||||
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
|
||||
const FHyperTwistCoachBrief& CurrentBrief)
|
||||
{
|
||||
if (!SchedulePolicySummary.IsStructurallyValid() || !QueueExecutionSummary.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistCoachSignal();
|
||||
}
|
||||
|
||||
const bool bManualRescheduleReview = SchedulePolicySummary.bNeedsManualRescheduleReview;
|
||||
const bool bArchiveAwareDeferrals = SchedulePolicySummary.bHasArchiveAwareDeferrals;
|
||||
const bool bBlockedFollowUpDeferrals = SchedulePolicySummary.bHasBlockedFollowUpDeferrals;
|
||||
const bool bOutstandingDeferredWork = QueueExecutionSummary.bHasOutstandingDeferredWork;
|
||||
const bool bCompletionGap = QueueExecutionSummary.bHasCompletionGap;
|
||||
const bool bRepeatManualReschedule = QueueExecutionSummary.bHasRepeatManualRescheduleFriction;
|
||||
const bool bHighPolicyPressure = SchedulePolicySummary.ReschedulePressureScore >= 25.0f;
|
||||
const bool bHighExecutionPressure = QueueExecutionSummary.ExecutionPressureScore >= 25.0f;
|
||||
const bool bNeedsFeedback =
|
||||
bHighPolicyPressure
|
||||
|| bHighExecutionPressure
|
||||
|| bManualRescheduleReview
|
||||
|| bArchiveAwareDeferrals
|
||||
|| bBlockedFollowUpDeferrals
|
||||
|| bOutstandingDeferredWork
|
||||
|| bCompletionGap
|
||||
|| bRepeatManualReschedule;
|
||||
if (!bNeedsFeedback)
|
||||
{
|
||||
return FHyperTwistCoachSignal();
|
||||
}
|
||||
|
||||
const bool bHistoryPrefersFollowUp =
|
||||
SchedulePolicySummary.bFollowUpContinuationFromRecoveryHistory;
|
||||
const bool bHistoryPrefersRecommended =
|
||||
SchedulePolicySummary.bBorderlineFollowUpSuppressedByRecoveryHistory;
|
||||
const bool bPreferFollowUpRecovery =
|
||||
bBlockedFollowUpDeferrals
|
||||
|| bHistoryPrefersFollowUp
|
||||
|| CoachMemorySnapshot.PreferredGuidanceLane
|
||||
== EHyperTwistTrainingCoachGuidanceLane::FollowUp;
|
||||
const bool bPreferRecommendedRecovery =
|
||||
!bPreferFollowUpRecovery
|
||||
&& (
|
||||
bHistoryPrefersRecommended
|
||||
|| CoachMemorySnapshot.PreferredGuidanceLane
|
||||
== EHyperTwistTrainingCoachGuidanceLane::Recommended
|
||||
);
|
||||
const bool bCompetingGuidancePressure =
|
||||
CoachMemorySnapshot.RecommendedGuidanceContinueBias > 0
|
||||
&& CoachMemorySnapshot.FollowUpGuidanceContinueBias > 0;
|
||||
|
||||
const FString SignalId = bPreferFollowUpRecovery
|
||||
? TEXT("recover_follow_up_schedule_friction")
|
||||
: (bPreferRecommendedRecovery
|
||||
? TEXT("recover_recommended_schedule_friction")
|
||||
: (bArchiveAwareDeferrals
|
||||
? TEXT("stabilize_archive_aware_schedule")
|
||||
: (bManualRescheduleReview
|
||||
? TEXT("review_manual_schedule_friction")
|
||||
: (bCompletionGap
|
||||
? TEXT("recover_schedule_completion_gap")
|
||||
: TEXT("recover_schedule_friction")))));
|
||||
const FString Headline =
|
||||
bBlockedFollowUpDeferrals
|
||||
? (bCompletionGap
|
||||
? TEXT("Live queue friction is still blocking follow-up completion")
|
||||
: TEXT("Live queue friction is still holding follow-up work back"))
|
||||
: (bManualRescheduleReview
|
||||
? TEXT("Manual reschedule pressure is still shaping the next coaching run")
|
||||
: (bArchiveAwareDeferrals
|
||||
? TEXT("Archive-aware deferrals are still shaping the next coaching run")
|
||||
: (bCompletionGap
|
||||
? TEXT("Live queue execution still needs a tighter completion pass")
|
||||
: TEXT("Deferred queue work still needs schedule recovery"))));
|
||||
const FString Detail = FString::Printf(
|
||||
TEXT("policyPressure=%.2f, executionPressure=%.2f, immediate=%d, pending=%d, deferred=%d, manualRescheduleEntries=%d, manualRescheduleEvents=%d, completionGap=%s, outstandingDeferred=%s, blockedFollowUp=%s, archiveAware=%s, dominantDeferral=%s, closure=%s"),
|
||||
SchedulePolicySummary.ReschedulePressureScore,
|
||||
QueueExecutionSummary.ExecutionPressureScore,
|
||||
SchedulePolicySummary.ImmediateEntryCount,
|
||||
SchedulePolicySummary.PendingEntryCount,
|
||||
SchedulePolicySummary.DeferredEntryCount,
|
||||
SchedulePolicySummary.ManualRescheduleEntryCount,
|
||||
QueueExecutionSummary.ManualRescheduleEventCount,
|
||||
bCompletionGap ? TEXT("yes") : TEXT("no"),
|
||||
bOutstandingDeferredWork ? TEXT("yes") : TEXT("no"),
|
||||
bBlockedFollowUpDeferrals ? TEXT("yes") : TEXT("no"),
|
||||
bArchiveAwareDeferrals ? TEXT("yes") : TEXT("no"),
|
||||
SchedulePolicySummary.DominantDeferralReasonLabel.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *SchedulePolicySummary.DominantDeferralReasonLabel,
|
||||
SchedulePolicySummary.ClosurePolicyLine.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *SchedulePolicySummary.ClosurePolicyLine
|
||||
);
|
||||
const float Score = FMath::Clamp(
|
||||
59.0f
|
||||
+ FMath::Min(SchedulePolicySummary.ReschedulePressureScore * 0.22f, 18.0f)
|
||||
+ FMath::Min(QueueExecutionSummary.ExecutionPressureScore * 0.18f, 14.0f)
|
||||
+ (bBlockedFollowUpDeferrals ? 7.0f : 0.0f)
|
||||
+ (bManualRescheduleReview ? 5.0f : 0.0f)
|
||||
+ (bArchiveAwareDeferrals ? 4.0f : 0.0f)
|
||||
+ (bOutstandingDeferredWork ? 5.0f : 0.0f)
|
||||
+ (bCompletionGap ? 5.0f : 0.0f)
|
||||
+ (bRepeatManualReschedule ? 4.0f : 0.0f),
|
||||
0.0f,
|
||||
100.0f
|
||||
);
|
||||
const EHyperTwistCoachFocusLane FocusLane =
|
||||
bArchiveAwareDeferrals && !bBlockedFollowUpDeferrals
|
||||
? EHyperTwistCoachFocusLane::Stability
|
||||
: EHyperTwistCoachFocusLane::Cadence;
|
||||
const EHyperTwistTrainingDeliveryMode SuggestedMode =
|
||||
bPreferFollowUpRecovery
|
||||
? EHyperTwistTrainingDeliveryMode::CoachReviewed
|
||||
: ((CurrentBrief.UserId == SchedulePolicySummary.UserId
|
||||
&& CurrentBrief.SuggestedMode != EHyperTwistTrainingDeliveryMode::Timer)
|
||||
? CurrentBrief.SuggestedMode
|
||||
: CoachMemorySnapshot.DominantCoachSuggestedMode);
|
||||
const EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy =
|
||||
bPreferFollowUpRecovery
|
||||
? EHyperTwistTrainingSelectionPolicy::Scripted
|
||||
: ((bCompletionGap || bOutstandingDeferredWork)
|
||||
? EHyperTwistTrainingSelectionPolicy::Weighted
|
||||
: ((CurrentBrief.UserId == SchedulePolicySummary.UserId
|
||||
&& CurrentBrief.SuggestedSelectionPolicy
|
||||
!= EHyperTwistTrainingSelectionPolicy::Weighted)
|
||||
? CurrentBrief.SuggestedSelectionPolicy
|
||||
: CoachMemorySnapshot.DominantCoachSelectionPolicy));
|
||||
|
||||
FHyperTwistCoachSignal Signal = HyperTwistTrainingCoachLibraryInternal::MakeSignal(
|
||||
SignalId,
|
||||
FocusLane,
|
||||
Score,
|
||||
Headline,
|
||||
Detail,
|
||||
SuggestedMode,
|
||||
SuggestedSelectionPolicy
|
||||
);
|
||||
Signal.SuggestedDeckId = !CurrentBrief.FocusDeckId.IsEmpty()
|
||||
? CurrentBrief.FocusDeckId
|
||||
: SchedulePolicySummary.FocusDeckId;
|
||||
Signal.SuggestedMethodSegmentId = !CurrentBrief.FocusMethodSegmentId.IsEmpty()
|
||||
? CurrentBrief.FocusMethodSegmentId
|
||||
: SchedulePolicySummary.FocusMethodSegmentId;
|
||||
Signal.SuggestedCaseIds = CurrentBrief.FocusCaseIds.Num() > 0
|
||||
? CurrentBrief.FocusCaseIds
|
||||
: (CoachMemorySnapshot.SchedulePressureCaseIds.Num() > 0
|
||||
? CoachMemorySnapshot.SchedulePressureCaseIds
|
||||
: CoachMemorySnapshot.DueCaseIds);
|
||||
HyperTwistTrainingCoachLibraryInternal::ApplyGuidancePreferenceToSignal(
|
||||
Signal,
|
||||
bPreferFollowUpRecovery
|
||||
? EHyperTwistTrainingCoachGuidanceLane::FollowUp
|
||||
: (bPreferRecommendedRecovery
|
||||
? EHyperTwistTrainingCoachGuidanceLane::Recommended
|
||||
: CoachMemorySnapshot.PreferredGuidanceLane),
|
||||
CoachMemorySnapshot,
|
||||
bCompetingGuidancePressure
|
||||
);
|
||||
return Signal;
|
||||
}
|
||||
|
||||
FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithSchedulePolicyFeedbackSignal(
|
||||
const FHyperTwistCoachBrief& BaseBrief,
|
||||
const FHyperTwistCoachSignal& SchedulePolicyFeedbackSignal)
|
||||
{
|
||||
if (SchedulePolicyFeedbackSignal.SignalId.IsEmpty())
|
||||
{
|
||||
return BaseBrief;
|
||||
}
|
||||
|
||||
FHyperTwistCoachBrief Brief = BaseBrief;
|
||||
if (!Brief.Signals.ContainsByPredicate(
|
||||
[&SchedulePolicyFeedbackSignal](const FHyperTwistCoachSignal& Candidate)
|
||||
{
|
||||
return Candidate.SignalId == SchedulePolicyFeedbackSignal.SignalId;
|
||||
}))
|
||||
{
|
||||
Brief.Signals.Add(SchedulePolicyFeedbackSignal);
|
||||
}
|
||||
HyperTwistTrainingCoachLibraryInternal::SortSignals(Brief.Signals);
|
||||
|
||||
const bool bPolicyFacingBrief =
|
||||
Brief.FocusLane == EHyperTwistCoachFocusLane::Cadence
|
||||
|| Brief.FocusLane == EHyperTwistCoachFocusLane::Stability
|
||||
|| Brief.bNeedsCadenceRecovery;
|
||||
const bool bShouldPromotePolicySignal =
|
||||
bPolicyFacingBrief
|
||||
&& SchedulePolicyFeedbackSignal.Score >= Brief.PriorityScore - 3.5f;
|
||||
if (bShouldPromotePolicySignal)
|
||||
{
|
||||
for (FHyperTwistCoachSignal& Signal : Brief.Signals)
|
||||
{
|
||||
if (Signal.SignalId == SchedulePolicyFeedbackSignal.SignalId)
|
||||
{
|
||||
Signal.Score = FMath::Clamp(
|
||||
FMath::Max(Signal.Score, Brief.Signals[0].Score + 0.5f),
|
||||
0.0f,
|
||||
100.0f
|
||||
);
|
||||
Signal.Priority = HyperTwistTrainingCoachLibraryInternal::PriorityFromScore(Signal.Score);
|
||||
Signal.bNeedsImmediateAttention = Signal.Priority == EHyperTwistCoachPriority::Critical;
|
||||
break;
|
||||
}
|
||||
}
|
||||
HyperTwistTrainingCoachLibraryInternal::SortSignals(Brief.Signals);
|
||||
}
|
||||
|
||||
if (Brief.Signals.Num() > 0)
|
||||
{
|
||||
const FString OriginalDeckId = Brief.FocusDeckId;
|
||||
const FString OriginalMethodSegmentId = Brief.FocusMethodSegmentId;
|
||||
const TArray<FString> OriginalCaseIds = Brief.FocusCaseIds;
|
||||
const FString OriginalPrimaryActionLabel = Brief.PrimaryActionLabel;
|
||||
HyperTwistTrainingCoachLibraryInternal::ApplyPrimarySignalToBrief(Brief, Brief.Signals[0]);
|
||||
if (Brief.FocusDeckId.IsEmpty())
|
||||
{
|
||||
Brief.FocusDeckId = OriginalDeckId;
|
||||
}
|
||||
if (Brief.FocusMethodSegmentId.IsEmpty())
|
||||
{
|
||||
Brief.FocusMethodSegmentId = OriginalMethodSegmentId;
|
||||
}
|
||||
if (Brief.FocusCaseIds.Num() <= 0)
|
||||
{
|
||||
Brief.FocusCaseIds = OriginalCaseIds;
|
||||
}
|
||||
if (bShouldPromotePolicySignal
|
||||
&& Brief.PrimaryActionLabel == SchedulePolicyFeedbackSignal.SignalId
|
||||
&& (!OriginalPrimaryActionLabel.IsEmpty()
|
||||
&& (OriginalPrimaryActionLabel.Contains(TEXT("schedule"))
|
||||
|| OriginalPrimaryActionLabel.Contains(TEXT("recovery"))
|
||||
|| OriginalPrimaryActionLabel.Contains(TEXT("promote_deferred"))
|
||||
|| OriginalPrimaryActionLabel.Contains(TEXT("reschedule"))
|
||||
|| OriginalPrimaryActionLabel.Contains(TEXT("archive")))))
|
||||
{
|
||||
Brief.PrimaryActionLabel = OriginalPrimaryActionLabel;
|
||||
}
|
||||
}
|
||||
|
||||
return Brief;
|
||||
}
|
||||
|
||||
FHyperTwistCoachSignal UHyperTwistTrainingCoachLibrary::DeriveClosureRecoveryHistorySignal(
|
||||
const FHyperTwistTrainingCoachActionClosureSummary& ClosureSummary,
|
||||
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
|
||||
|
|
|
|||
|
|
@ -4143,6 +4143,21 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
|
|||
ActiveCoachSessionQueueState.QueueId,
|
||||
ActiveLearnerDeckStateSummary.ReferenceUtc
|
||||
);
|
||||
const FHyperTwistCoachSignal SchedulePolicyFeedbackSignal =
|
||||
UHyperTwistTrainingCoachLibrary::DeriveSchedulePolicyFeedbackSignal(
|
||||
ActiveCoachSchedulePolicySummary,
|
||||
ActiveCoachSessionQueueExecutionSummary,
|
||||
ActiveCoachMemorySnapshot,
|
||||
ActiveCoachBrief
|
||||
);
|
||||
if (!SchedulePolicyFeedbackSignal.SignalId.IsEmpty())
|
||||
{
|
||||
ActiveCoachBrief = UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithSchedulePolicyFeedbackSignal(
|
||||
ActiveCoachBrief,
|
||||
SchedulePolicyFeedbackSignal
|
||||
);
|
||||
ActiveCoachSignals = ActiveCoachBrief.Signals;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -487,6 +487,20 @@ public:
|
|||
const FHyperTwistCoachSignal& QueueSuppressionSignal
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistCoachSignal DeriveSchedulePolicyFeedbackSignal(
|
||||
const FHyperTwistCoachSchedulePolicySummary& SchedulePolicySummary,
|
||||
const FHyperTwistTrainingCoachSessionQueueExecutionSummary& QueueExecutionSummary,
|
||||
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
|
||||
const FHyperTwistCoachBrief& CurrentBrief
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistCoachBrief RefineCoachBriefWithSchedulePolicyFeedbackSignal(
|
||||
const FHyperTwistCoachBrief& BaseBrief,
|
||||
const FHyperTwistCoachSignal& SchedulePolicyFeedbackSignal
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistCoachSignal DeriveClosureRecoveryHistorySignal(
|
||||
const FHyperTwistTrainingCoachActionClosureSummary& ClosureSummary,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
# HyperTwist Phase 3 schedule-policy feedback closure packet
|
||||
|
||||
Created on `2026-05-06`
|
||||
|
||||
Status:
|
||||
|
||||
- first-party HyperTwist packet
|
||||
- bounded coaching-stack closure slice
|
||||
|
||||
## Purpose
|
||||
|
||||
This packet defines the next logical Phase 3 closure task after the linked-state rebuild closure packet landed.
|
||||
|
||||
The open task is:
|
||||
|
||||
- fold resolved live coach queue schedule-policy and execution summaries back into coach brief shaping
|
||||
- close the remaining one-way gap where current queue policy/execution truth is derived, surfaced, and persisted, but does not directly refine the active coach brief in the same refresh pass
|
||||
|
||||
It is not:
|
||||
|
||||
- a new queue model
|
||||
- a broad coaching heuristic rewrite
|
||||
- a dashboard flow redesign
|
||||
- a donor widening task
|
||||
|
||||
## Scope
|
||||
|
||||
Bounded lane:
|
||||
|
||||
- final first-party schedule-friction feedback closure between resolved coach queue state and coach brief shaping
|
||||
|
||||
Required result:
|
||||
|
||||
- the subsystem derives the current coach brief first, resolves queue state from that brief, derives live schedule-policy and queue-execution summaries, and then runs a second-pass coach brief refinement from those resolved summaries
|
||||
- the feedback signal uses current first-party queue policy/execution truth, not only previously persisted memory-derived friction fields
|
||||
- the resulting brief keeps existing launch/recovery action labels when they are already more specific than the feedback signal id
|
||||
|
||||
Out of scope:
|
||||
|
||||
- rebuilding queue state more than once per refresh
|
||||
- changing queue creation heuristics
|
||||
- changing generated-mode executor behavior
|
||||
- changing external plugin copies
|
||||
|
||||
## Current owned product truth
|
||||
|
||||
These pieces are already live in first-party code:
|
||||
|
||||
- coach memory snapshot derivation from repository state
|
||||
- coach brief derivation from learner/review/coach-memory inputs
|
||||
- queue summary, queue state, queue horizon, schedule policy, and queue execution derivation
|
||||
- dashboard surfaces for queue policy/execution truth
|
||||
|
||||
What was still missing:
|
||||
|
||||
- a same-pass feedback loop from the resolved live queue schedule/execution summaries back into the active coach brief
|
||||
- explicit live schedule-policy pressure participating in coach signal ordering after queue state resolution
|
||||
|
||||
## Implementation target
|
||||
|
||||
The slice should do the following:
|
||||
|
||||
1. derive a live schedule-policy feedback signal from:
|
||||
- `FHyperTwistCoachSchedulePolicySummary`
|
||||
- `FHyperTwistTrainingCoachSessionQueueExecutionSummary`
|
||||
- current coach memory preference state
|
||||
- current coach brief
|
||||
2. refine the active coach brief with that signal after queue state, horizon, policy, and execution summaries are resolved
|
||||
3. preserve existing launch/recovery labels when the prior brief was already carrying a more specific schedule-facing action label
|
||||
|
||||
## Suggested file ownership
|
||||
|
||||
Primary edit surfaces:
|
||||
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h`
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp`
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp`
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- a new second-pass schedule-policy feedback signal is available in first-party coach derivation
|
||||
- `RefreshRepositoryViews()` refines `ActiveCoachBrief` from resolved queue policy/execution summaries in the same refresh pass
|
||||
- the full UE build still succeeds
|
||||
|
||||
## Validation checklist
|
||||
|
||||
1. build `UnrealHyperTwist.sln`
|
||||
2. confirm the new signal compiles through coach library and subsystem consumers
|
||||
3. confirm the active coach brief now receives live schedule-policy / execution feedback after queue state resolution
|
||||
|
||||
That is the packet.
|
||||
Loading…
Add table
Reference in a new issue