Preserve queue backed coach brief continuity

This commit is contained in:
axiomlogicnexus 2026-05-06 18:08:19 +02:00
parent 4c789b2b72
commit b08442c335
6 changed files with 285 additions and 14 deletions

View file

@ -3773,16 +3773,29 @@ FHyperTwistCoachSessionQueueSummary UHyperTwistTrainingCoachLibrary::DeriveCoach
FHyperTwistCoachSessionQueueEntry Entry;
Entry.EntryId = EntryId;
Entry.SourceLabel = SourceLabel;
Entry.SourceBriefTrainingSessionId = Brief.TrainingSessionId;
Entry.SourceReviewPlanId = Brief.SourceReviewPlanId;
Entry.CarryForwardPlanId = Brief.CarryForwardPlanId;
Entry.RootPlanId = Brief.RootPlanId;
Entry.FollowUpDepth = FMath::Max(0, Brief.FollowUpDepth);
Entry.Priority = Brief.Priority;
Entry.PriorityScore = Brief.PriorityScore;
Entry.FocusLane = Brief.FocusLane;
Entry.Headline = Brief.Headline;
Entry.PrimaryActionLabel = Brief.PrimaryActionLabel;
Entry.SuggestedMode = Brief.SuggestedMode;
Entry.SuggestedSelectionPolicy = Brief.SuggestedSelectionPolicy;
Entry.FocusDeckId = Brief.FocusDeckId;
Entry.FocusMethodSegmentId = Brief.FocusMethodSegmentId;
Entry.FocusCaseIds = Brief.FocusCaseIds;
Entry.DeferredLaunchCaseCount = FMath::Max(0, Brief.DeferredLaunchCaseCount);
Entry.bNeedsDeferredLaunchRecovery = Brief.bNeedsDeferredLaunchRecovery;
Entry.bHasTightenedLaunchOverflow = Brief.bHasTightenedLaunchOverflow;
Entry.bRequiresFollowUp = bRequiresFollowUp;
Entry.bRequiresArchiveRetentionTuning = bRequiresArchiveRetentionTuning;
Entry.bNeedsCoachReview = Brief.bNeedsCoachReview;
Entry.bNeedsCadenceRecovery = Brief.bNeedsCadenceRecovery;
Entry.bNeedsVarietyRotation = Brief.bNeedsVarietyRotation;
Entry.bPreferRecognitionReplayReview = Brief.bPreferRecognitionReplayReview;
const bool bNarrowedContinuationPreferred =
HyperTwistTrainingCoachLibraryInternal::IsDeferredOverflowDashboardHandoffSource(

View file

@ -4828,17 +4828,32 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
)
{
const FString CaseFingerprint = FString::Join(Entry.FocusCaseIds, TEXT("|"));
return FString::Printf(
TEXT("%s|%s|%s|%s|%d|%d|%s|%d"),
*Entry.SourceLabel,
*Entry.FocusDeckId,
*Entry.FocusMethodSegmentId,
*Entry.Headline,
static_cast<int32>(Entry.SuggestedMode),
static_cast<int32>(Entry.SuggestedSelectionPolicy),
*CaseFingerprint,
Entry.bPreferRecognitionReplayReview ? 1 : 0
);
TArray<FString> FingerprintParts;
FingerprintParts.Reserve(22);
FingerprintParts.Add(Entry.SourceLabel);
FingerprintParts.Add(Entry.SourceBriefTrainingSessionId);
FingerprintParts.Add(Entry.SourceReviewPlanId);
FingerprintParts.Add(Entry.CarryForwardPlanId);
FingerprintParts.Add(Entry.RootPlanId);
FingerprintParts.Add(FString::FromInt(Entry.FollowUpDepth));
FingerprintParts.Add(Entry.FocusLaneLabel);
FingerprintParts.Add(Entry.Headline);
FingerprintParts.Add(Entry.PrimaryActionLabel);
FingerprintParts.Add(Entry.FocusDeckId);
FingerprintParts.Add(Entry.FocusMethodSegmentId);
FingerprintParts.Add(FString::FromInt(static_cast<int32>(Entry.SuggestedMode)));
FingerprintParts.Add(FString::FromInt(static_cast<int32>(Entry.SuggestedSelectionPolicy)));
FingerprintParts.Add(CaseFingerprint);
FingerprintParts.Add(FString::FromInt(Entry.DeferredLaunchCaseCount));
FingerprintParts.Add(Entry.bNeedsDeferredLaunchRecovery ? TEXT("1") : TEXT("0"));
FingerprintParts.Add(Entry.bHasTightenedLaunchOverflow ? TEXT("1") : TEXT("0"));
FingerprintParts.Add(Entry.bRequiresFollowUp ? TEXT("1") : TEXT("0"));
FingerprintParts.Add(Entry.bRequiresArchiveRetentionTuning ? TEXT("1") : TEXT("0"));
FingerprintParts.Add(Entry.bNeedsCoachReview ? TEXT("1") : TEXT("0"));
FingerprintParts.Add(Entry.bNeedsCadenceRecovery ? TEXT("1") : TEXT("0"));
FingerprintParts.Add(Entry.bNeedsVarietyRotation ? TEXT("1") : TEXT("0"));
FingerprintParts.Add(Entry.bPreferRecognitionReplayReview ? TEXT("1") : TEXT("0"));
return FString::Join(FingerprintParts, TEXT("|"));
}
FHyperTwistTrainingCoachSessionQueueState NormalizeCoachSessionQueueState(
@ -4864,6 +4879,8 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
{
FHyperTwistTrainingCoachSessionQueueStateEntry NormalizedEntry = Entry;
NormalizedEntry.FocusCaseIds = DeduplicateCaseIds(Entry.FocusCaseIds);
NormalizedEntry.FollowUpDepth = FMath::Max(0, NormalizedEntry.FollowUpDepth);
NormalizedEntry.DeferredLaunchCaseCount = FMath::Max(0, NormalizedEntry.DeferredLaunchCaseCount);
if (NormalizedEntry.WorkFingerprint.IsEmpty())
{
NormalizedEntry.WorkFingerprint = MakeCoachSessionQueueEntryFingerprint(NormalizedEntry);
@ -20664,19 +20681,34 @@ FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::
FHyperTwistTrainingCoachSessionQueueStateEntry QueueEntry;
QueueEntry.EntryId = SummaryEntry.EntryId;
QueueEntry.SourceLabel = SummaryEntry.SourceLabel;
QueueEntry.SourceBriefTrainingSessionId = SummaryEntry.SourceBriefTrainingSessionId;
QueueEntry.SourceReviewPlanId = SummaryEntry.SourceReviewPlanId;
QueueEntry.CarryForwardPlanId = SummaryEntry.CarryForwardPlanId;
QueueEntry.RootPlanId = SummaryEntry.RootPlanId;
QueueEntry.FollowUpDepth = FMath::Max(0, SummaryEntry.FollowUpDepth);
QueueEntry.SlotIndex = SummaryEntry.SlotIndex;
QueueEntry.PriorityLabel = StaticEnum<EHyperTwistCoachPriority>()->GetNameStringByValue(
static_cast<int64>(SummaryEntry.Priority)
);
QueueEntry.PriorityScore = SummaryEntry.PriorityScore;
QueueEntry.FocusLaneLabel = StaticEnum<EHyperTwistCoachFocusLane>()->GetNameStringByValue(
static_cast<int64>(SummaryEntry.FocusLane)
);
QueueEntry.Headline = SummaryEntry.Headline;
QueueEntry.PrimaryActionLabel = SummaryEntry.PrimaryActionLabel;
QueueEntry.SuggestedMode = SummaryEntry.SuggestedMode;
QueueEntry.SuggestedSelectionPolicy = SummaryEntry.SuggestedSelectionPolicy;
QueueEntry.FocusDeckId = SummaryEntry.FocusDeckId;
QueueEntry.FocusMethodSegmentId = SummaryEntry.FocusMethodSegmentId;
QueueEntry.FocusCaseIds = SummaryEntry.FocusCaseIds;
QueueEntry.DeferredLaunchCaseCount = FMath::Max(0, SummaryEntry.DeferredLaunchCaseCount);
QueueEntry.bNeedsDeferredLaunchRecovery = SummaryEntry.bNeedsDeferredLaunchRecovery;
QueueEntry.bHasTightenedLaunchOverflow = SummaryEntry.bHasTightenedLaunchOverflow;
QueueEntry.bRequiresFollowUp = SummaryEntry.bRequiresFollowUp;
QueueEntry.bRequiresArchiveRetentionTuning = SummaryEntry.bRequiresArchiveRetentionTuning;
QueueEntry.bNeedsCoachReview = SummaryEntry.bNeedsCoachReview;
QueueEntry.bNeedsCadenceRecovery = SummaryEntry.bNeedsCadenceRecovery;
QueueEntry.bNeedsVarietyRotation = SummaryEntry.bNeedsVarietyRotation;
QueueEntry.bPreferRecognitionReplayReview = SummaryEntry.bPreferRecognitionReplayReview;
QueueEntry.bPinned = QueueEntry.bRequiresFollowUp || QueueEntry.bRequiresArchiveRetentionTuning;
QueueEntry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending;

View file

@ -81,6 +81,25 @@ namespace HyperTwistTrainingSubsystemInternal
return TEXT("Unknown");
}
template <typename TEnum>
TEnum EnumValueFromName(const FString& Value, const TEnum Fallback)
{
if (Value.IsEmpty())
{
return Fallback;
}
if (const UEnum* Enum = StaticEnum<TEnum>())
{
const int64 RawValue = Enum->GetValueByNameString(Value);
if (RawValue != INDEX_NONE)
{
return static_cast<TEnum>(RawValue);
}
}
return Fallback;
}
bool ShouldPreferRecognitionReplayReviewForActiveRun(
const FHyperTwistTrainingRunState& RunState,
const FHyperTwistTrainingCoachActionPlan& ActionPlan
@ -1188,19 +1207,36 @@ namespace HyperTwistTrainingSubsystemInternal
FHyperTwistCoachBrief Brief;
Brief.UserId = QueueState.UserId;
Brief.ReferenceUtc = ReferenceUtc;
Brief.TrainingSessionId = QueueEntry.SourceBriefTrainingSessionId;
Brief.SourceReviewPlanId = QueueEntry.SourceReviewPlanId;
Brief.CarryForwardPlanId = QueueEntry.CarryForwardPlanId;
Brief.RootPlanId = !QueueEntry.RootPlanId.IsEmpty()
? QueueEntry.RootPlanId
: QueueEntry.CarryForwardPlanId;
Brief.FollowUpDepth = FMath::Max(0, QueueEntry.FollowUpDepth);
Brief.PriorityScore = QueueEntry.PriorityScore;
Brief.Priority = CoachPriorityFromScore(QueueEntry.PriorityScore);
Brief.FocusLane = QueueEntry.SourceLabel == TEXT("archive-policy")
? EHyperTwistCoachFocusLane::Stability
: EHyperTwistCoachFocusLane::ReviewBacklog;
: EnumValueFromName(
QueueEntry.FocusLaneLabel,
EHyperTwistCoachFocusLane::ReviewBacklog
);
Brief.Headline = QueueEntry.Headline;
Brief.PrimaryActionLabel = ResolveQueueActionLabel(QueueEntry.SourceLabel);
Brief.PrimaryActionLabel = !QueueEntry.PrimaryActionLabel.IsEmpty()
? QueueEntry.PrimaryActionLabel
: ResolveQueueActionLabel(QueueEntry.SourceLabel);
Brief.SuggestedMode = QueueEntry.SuggestedMode;
Brief.SuggestedSelectionPolicy = QueueEntry.SuggestedSelectionPolicy;
Brief.FocusDeckId = QueueEntry.FocusDeckId;
Brief.FocusMethodSegmentId = QueueEntry.FocusMethodSegmentId;
Brief.FocusCaseIds = QueueEntry.FocusCaseIds;
Brief.bNeedsCoachReview = QueueEntry.SuggestedMode == EHyperTwistTrainingDeliveryMode::CoachReviewed;
Brief.DeferredLaunchCaseCount = FMath::Max(0, QueueEntry.DeferredLaunchCaseCount);
Brief.bNeedsDeferredLaunchRecovery = QueueEntry.bNeedsDeferredLaunchRecovery;
Brief.bHasTightenedLaunchOverflow = QueueEntry.bHasTightenedLaunchOverflow;
Brief.bNeedsCoachReview = QueueEntry.bNeedsCoachReview;
Brief.bNeedsCadenceRecovery = QueueEntry.bNeedsCadenceRecovery;
Brief.bNeedsVarietyRotation = QueueEntry.bNeedsVarietyRotation;
Brief.bPreferRecognitionReplayReview = QueueEntry.bPreferRecognitionReplayReview;
return Brief;
}

View file

@ -162,6 +162,21 @@ struct FHyperTwistCoachSessionQueueEntry
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceBriefTrainingSessionId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceReviewPlanId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CarryForwardPlanId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RootPlanId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 FollowUpDepth = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SlotIndex = 0;
@ -171,9 +186,15 @@ struct FHyperTwistCoachSessionQueueEntry
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float PriorityScore = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistCoachFocusLane FocusLane = EHyperTwistCoachFocusLane::ReviewBacklog;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PrimaryActionLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDeliveryMode SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
@ -189,12 +210,30 @@ struct FHyperTwistCoachSessionQueueEntry
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> FocusCaseIds;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 DeferredLaunchCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bNeedsDeferredLaunchRecovery = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasTightenedLaunchOverflow = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bRequiresFollowUp = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bRequiresArchiveRetentionTuning = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bNeedsCoachReview = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bNeedsCadenceRecovery = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bNeedsVarietyRotation = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPreferRecognitionReplayReview = false;
};

View file

@ -4190,9 +4190,24 @@ struct FHyperTwistTrainingCoachSessionQueueStateEntry
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceTrainingSessionId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceBriefTrainingSessionId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceReviewPlanId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CarryForwardPlanId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RootPlanId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StartActionSourceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 FollowUpDepth = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SlotIndex = 0;
@ -4206,9 +4221,15 @@ struct FHyperTwistTrainingCoachSessionQueueStateEntry
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float PriorityScore = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString FocusLaneLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PrimaryActionLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDeliveryMode SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
@ -4224,6 +4245,15 @@ struct FHyperTwistTrainingCoachSessionQueueStateEntry
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> FocusCaseIds;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 DeferredLaunchCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bNeedsDeferredLaunchRecovery = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasTightenedLaunchOverflow = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ScheduledAtUtc;
@ -4251,6 +4281,15 @@ struct FHyperTwistTrainingCoachSessionQueueStateEntry
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bRequiresArchiveRetentionTuning = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bNeedsCoachReview = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bNeedsCadenceRecovery = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bNeedsVarietyRotation = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPreferRecognitionReplayReview = false;

View file

@ -0,0 +1,112 @@
# HyperTwist Phase 4 queue brief continuity packet
Created on `2026-05-06`
Status:
- first-party HyperTwist packet
- bounded Phase `4` coach-to-review validation slice
## Purpose
This packet closes the next end-to-end continuity gap in the live recognition-assisted coach-to-review loop.
The open task is:
- preserve full queue-backed coach-brief semantics when coach guidance is materialized into queue entries and later reconstructed back into action-plan starts
It is not:
- a new recognition transport packet
- a new review-policy heuristic packet
- a new dashboard packet
- a broad queue-system rewrite
## Scope
Bounded lane:
- persist the coach-brief fields that materially shape downstream coach action plans through queue summary and queue state
- reconstruct queue-backed coach briefs from those persisted fields instead of inferring them from delivery mode or source label alone
- keep queue fingerprints aligned with the widened preserved semantics so queue-state continuity remains honest
Specifically preserved through the queue lane:
- source brief training session id
- source review-plan id
- carry-forward/root plan lineage and follow-up depth
- focus lane and primary action label
- deferred-launch recovery state
- coach-review, cadence-recovery, and variety-rotation flags
- recognition-review preference
Out of scope:
- new queue prioritization rules
- new coach-signal derivation
- new replay scoring
- new UI layout work
## Why this was the right next packet
Before this slice:
- recognition replay pressure already flowed into coach guidance
- queue entries already preserved recognition-review preference explicitly
- review-policy continuity and review-policy inspection were already landed
- recognition-biased repeat pressure already escalated correctly into coach intervention
But the queue reconstruction lane still dropped critical coach-brief semantics:
- coach-review intent could collapse to `false` unless suggested mode was explicit `CoachReviewed`
- queued follow-up entries lost carry-forward lineage
- queue-started action plans could lose source review-plan linkage, focus lane, primary action label, and cadence/variety/deferred-launch state
That meant the broad Phase `4` validation lane was still not end-to-end coherent once guidance crossed the queue materialization boundary.
So the next honest move was:
- preserve queue-backed coach brief continuity directly rather than layering more downstream fixes on top of lossy queue reconstruction
## What landed
Primary code changes:
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h`
- widened queue-summary entry structure to carry the coach-brief state needed for downstream continuity
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h`
- widened queue-state entry structure to persist the same continuity fields through repository state
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp`
- queue summary creation now copies the relevant coach-brief semantics into queue entries
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp`
- queue-state construction now preserves those fields and queue fingerprints now include them
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp`
- queue-backed brief reconstruction now restores coach-review intent, review-plan linkage, follow-up lineage, focus lane, action label, deferred-launch state, cadence/variety flags, and recognition-review preference from queue state
## Product effect
Queue-backed coach execution is now materially more coherent:
- recognition-assisted queue entries no longer lose coach-review intent just because they run in `RecognitionAssisted` mode
- queued follow-up guidance now preserves carry-forward/root-plan lineage into action-plan starts
- queue-started action plans now retain the same focus lane and primary action label that shaped the original brief
- deferred-launch, cadence, and variety state now survive the queue materialization boundary instead of resetting during reconstruction
- dashboard queue-preview and action-plan inspection now stay closer to the original coach guidance because they read the reconstructed brief path
## Acceptance criteria
- queue-backed brief reconstruction no longer infers coach-review intent from delivery mode alone
- queue entries preserve follow-up lineage and review-plan linkage needed by downstream action-plan creation
- queue-started action plans retain primary action label, focus lane, deferred-launch state, cadence/variety flags, and recognition-review preference
- queue fingerprinting reflects the widened preserved semantics
- full product build succeeds
## Validation checklist
1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor`
2. confirm queue summary entries preserve the coach-brief fields that materially affect action-plan creation
3. confirm queue-state construction and fingerprinting preserve those widened semantics
4. confirm queue-backed brief reconstruction restores coach-review intent, follow-up lineage, and review-plan linkage without reopening a broader packet
5. confirm dashboard/preview surfaces continue reading the reconstructed queue-backed brief path without compile or runtime-surface regressions
That is the packet.