Persist coach guidance lane preference

This commit is contained in:
axiomlogicnexus 2026-04-27 16:47:20 +02:00
parent 41a828c909
commit 49f7c11840
4 changed files with 149 additions and 5 deletions

View file

@ -2799,8 +2799,18 @@ FHyperTwistCoachDashboardGuidanceLanePreference UHyperTwistCoachDashboardWidget:
Preference.bHasDecisionSignal =
Preference.RecommendedDecisionCount > 0 || Preference.FollowUpDecisionCount > 0;
float RecommendedLaneScore = 1.0f;
float FollowUpLaneScore = CachedCoachPanelState.bHasFollowUpGuidance ? 1.25f : 1.0f;
const EHyperTwistCoachDashboardGuidancePreviewLane MemoryPreferredLane =
CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp
? EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp
: (CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended
? EHyperTwistCoachDashboardGuidancePreviewLane::Recommended
: EHyperTwistCoachDashboardGuidancePreviewLane::None);
float RecommendedLaneScore =
MemoryPreferredLane == EHyperTwistCoachDashboardGuidancePreviewLane::Recommended ? 1.5f : 1.0f;
float FollowUpLaneScore =
MemoryPreferredLane == EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp
? 1.75f
: (CachedCoachPanelState.bHasFollowUpGuidance ? 1.25f : 1.0f);
if (Preference.RecommendedDecisionCount > 0)
{
RecommendedLaneScore += static_cast<float>(Preference.RecommendedContinueBias);
@ -2823,11 +2833,13 @@ FHyperTwistCoachDashboardGuidanceLanePreference UHyperTwistCoachDashboardWidget:
else
{
Preference.PreferredLane =
ActiveGuidancePreviewLane != EHyperTwistCoachDashboardGuidancePreviewLane::None
MemoryPreferredLane != EHyperTwistCoachDashboardGuidancePreviewLane::None
? MemoryPreferredLane
: (ActiveGuidancePreviewLane != EHyperTwistCoachDashboardGuidancePreviewLane::None
? ActiveGuidancePreviewLane
: (CachedCoachPanelState.bHasFollowUpGuidance
? EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp
: EHyperTwistCoachDashboardGuidancePreviewLane::Recommended);
: EHyperTwistCoachDashboardGuidancePreviewLane::Recommended));
}
return Preference;
@ -3324,13 +3336,18 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
: (GuidanceLanePreference.PreferredLane == EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp
? TEXT("follow-up")
: TEXT("none"));
const FString GuidancePreferenceSourceLabel = GuidanceLanePreference.bHasDecisionSignal
? TEXT("decision-informed")
: (CachedCoachPanelState.PreferredGuidanceSourceLabel.IsEmpty()
? TEXT("availability fallback")
: CachedCoachPanelState.PreferredGuidanceSourceLabel);
const FString GuidancePreferenceStatusLine =
GuidanceLanePreference.IsStructurallyValid()
? FString::Printf(
TEXT("Preference: next surfaced lane %s | Active preview: %s | Source: %s"),
*PreferredGuidanceLaneLabel,
*ActivePreviewLaneLabel,
GuidanceLanePreference.bHasDecisionSignal ? TEXT("decision-informed") : TEXT("availability fallback")
*GuidancePreferenceSourceLabel
)
: TEXT("Preference: no coach preview lane is currently available.");
const FString GuidancePreferenceDetailLine =

View file

@ -4851,6 +4851,12 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der
Snapshot.RepeatRecommendationSequenceCount = RecommendationHistory.RepeatRecommendationSequenceCount;
Snapshot.DominantCoachSuggestedMode = RecommendationHistory.DominantSuggestedMode;
Snapshot.DominantCoachSelectionPolicy = RecommendationHistory.DominantSelectionPolicy;
Snapshot.PreferredGuidanceLane = RecommendationHistory.PreferredGuidanceLane;
Snapshot.PreferredGuidanceSourceLabel = TEXT("recommendation-history");
Snapshot.RecommendedGuidancePlanCount = RecommendationHistory.RecommendedLanePlanCount;
Snapshot.FollowUpGuidancePlanCount = RecommendationHistory.FollowUpLanePlanCount;
Snapshot.RecommendedGuidanceContinueBias = RecommendationHistory.RecommendedLaneContinueBias;
Snapshot.FollowUpGuidanceContinueBias = RecommendationHistory.FollowUpLaneContinueBias;
Snapshot.bHasRepeatRecommendationPressure = RecommendationHistory.bHasRepeatRecommendationPressure;
if (!RecommendationHistory.FocusDeckId.IsEmpty()
@ -5169,6 +5175,22 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der
Snapshot.ReferenceUtc
) > 3.0;
if (Snapshot.bHasBlockedFollowUpScheduleFriction
|| Snapshot.ScheduleBlockedFollowUpEventCount > 0
|| Snapshot.FollowUpReadyCoachSequenceCount > 0)
{
Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::FollowUp;
Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-follow-up");
}
else if (Snapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::None)
{
Snapshot.PreferredGuidanceLane = Snapshot.bHasRepeatRecommendationPressure
|| Snapshot.RecurringCoachCaseIds.Num() > 0
? EHyperTwistTrainingCoachGuidanceLane::Recommended
: EHyperTwistTrainingCoachGuidanceLane::FollowUp;
Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-default");
}
return Snapshot;
}
@ -7751,6 +7773,18 @@ FHyperTwistTrainingCoachRecommendationHistorySummary UHyperTwistTrainingReposito
}
Summary.PlanCount += 1;
const bool bIsFollowUpLanePlan =
NormalizedPlan.FollowUpDepth > 0 || !NormalizedPlan.ParentPlanId.IsEmpty();
if (bIsFollowUpLanePlan)
{
Summary.FollowUpLanePlanCount += 1;
Summary.FollowUpLaneContinueBias += 1;
}
else
{
Summary.RecommendedLanePlanCount += 1;
Summary.RecommendedLaneContinueBias += 1;
}
FollowUpDepthAccumulator += static_cast<float>(NormalizedPlan.FollowUpDepth);
ModeCountMap.FindOrAdd(NormalizedPlan.SuggestedMode) += 1;
PolicyCountMap.FindOrAdd(NormalizedPlan.SuggestedSelectionPolicy) += 1;
@ -7887,6 +7921,8 @@ FHyperTwistTrainingCoachRecommendationHistorySummary UHyperTwistTrainingReposito
Summary.SequenceCount += 1;
Summary.EscalatedSequenceCount += ClosureSummary.bNeedsEscalation ? 1 : 0;
Summary.FollowUpReadySequenceCount += ClosureSummary.bShouldSpawnFollowUp ? 1 : 0;
Summary.FollowUpLaneContinueBias += ClosureSummary.bShouldSpawnFollowUp ? 2 : 0;
Summary.FollowUpLaneContinueBias += ClosureSummary.bNeedsEscalation ? 2 : 0;
ClosureScoreAccumulator += ClosureSummary.ClosureScore;
bool bHasRecurringCaseInSequence = false;
@ -7902,6 +7938,9 @@ FHyperTwistTrainingCoachRecommendationHistorySummary UHyperTwistTrainingReposito
}
}
Summary.RepeatRecommendationSequenceCount += bHasRecurringCaseInSequence ? 1 : 0;
Summary.RecommendedLaneContinueBias += bHasRecurringCaseInSequence ? 1 : 0;
Summary.RecommendedLaneContinueBias +=
(ClosureSummary.bSequenceClosed && !ClosureSummary.bShouldSpawnFollowUp) ? 1 : 0;
if (ClosureSummary.bShouldArchiveResolvedCases)
{
@ -7980,6 +8019,28 @@ FHyperTwistTrainingCoachRecommendationHistorySummary UHyperTwistTrainingReposito
|| (Summary.bHasRepeatRecommendationPressure
&& Summary.DominantSuggestedMode == EHyperTwistTrainingDeliveryMode::CoachReviewed);
const bool bHasFollowUpLaneSignal =
Summary.FollowUpLanePlanCount > 0
|| Summary.FollowUpReadySequenceCount > 0
|| Summary.EscalatedSequenceCount > 0
|| Summary.AverageFollowUpDepth >= 1.0f;
const float RecommendedLaneScore =
1.0f
+ static_cast<float>(Summary.RecommendedLaneContinueBias)
+ static_cast<float>(Summary.RepeatRecommendationSequenceCount) * 1.5f
+ (Summary.bHasRepeatRecommendationPressure ? 2.0f : 0.0f)
+ (Summary.DominantSuggestedMode == EHyperTwistTrainingDeliveryMode::CoachReviewed ? 1.0f : 0.0f);
const float FollowUpLaneScore =
1.0f
+ static_cast<float>(Summary.FollowUpLaneContinueBias)
+ static_cast<float>(Summary.FollowUpReadySequenceCount) * 2.0f
+ static_cast<float>(Summary.EscalatedSequenceCount) * 1.5f
+ Summary.AverageFollowUpDepth;
Summary.PreferredGuidanceLane =
bHasFollowUpLaneSignal && FollowUpLaneScore > RecommendedLaneScore
? EHyperTwistTrainingCoachGuidanceLane::FollowUp
: EHyperTwistTrainingCoachGuidanceLane::Recommended;
return Summary;
}

View file

@ -276,6 +276,8 @@ FHyperTwistTrainingCoachPanelState UHyperTwistTrainingSubsystem::GetActiveCoachP
PanelState.bHasFollowUpGuidance =
!ActiveCoachFollowUpBrief.UserId.IsEmpty() && !ActiveCoachFollowUpBrief.Headline.IsEmpty();
PanelState.SignalCount = ActiveCoachSignals.Num();
PanelState.PreferredGuidanceLane = ActiveCoachMemorySnapshot.PreferredGuidanceLane;
PanelState.PreferredGuidanceSourceLabel = ActiveCoachMemorySnapshot.PreferredGuidanceSourceLabel;
PanelState.bRepositoryVerificationPassed = ActiveRepositoryRoundTripVerification.bDidRoundTripPass;
PanelState.bRepositoryVerificationNeedsAttention = ActiveRepositoryRoundTripVerification.ResolvedPath.IsEmpty()
|| !ActiveRepositoryRoundTripVerification.bDidRoundTripPass;
@ -320,6 +322,9 @@ FHyperTwistTrainingCoachPanelState UHyperTwistTrainingSubsystem::GetActiveCoachP
3
).IsStructurallyValid());
const bool bPreferFollowUpGuidance =
PanelState.bHasFollowUpGuidance
&& ActiveCoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp;
if (NextRunnableEntry != nullptr)
{
PanelState.Headline = NextRunnableEntry->Headline;
@ -335,6 +340,17 @@ FHyperTwistTrainingCoachPanelState UHyperTwistTrainingSubsystem::GetActiveCoachP
PanelState.SuggestedMode = NextRunnableEntry->SuggestedMode;
PanelState.SuggestedSelectionPolicy = NextRunnableEntry->SuggestedSelectionPolicy;
}
else if (bPreferFollowUpGuidance)
{
PanelState.Headline = ActiveCoachFollowUpBrief.Headline;
PanelState.SecondaryLine = ActiveCoachFollowUpBrief.PrimaryActionLabel;
PanelState.PrimaryActionLabel = ActiveCoachFollowUpBrief.PrimaryActionLabel;
PanelState.FocusDeckId = ActiveCoachFollowUpBrief.FocusDeckId;
PanelState.FocusMethodSegmentId = ActiveCoachFollowUpBrief.FocusMethodSegmentId;
PanelState.FocusCaseCount = ActiveCoachFollowUpBrief.FocusCaseIds.Num();
PanelState.SuggestedMode = ActiveCoachFollowUpBrief.SuggestedMode;
PanelState.SuggestedSelectionPolicy = ActiveCoachFollowUpBrief.SuggestedSelectionPolicy;
}
else if (PanelState.bHasCoachGuidance)
{
PanelState.Headline = ActiveCoachBrief.Headline;

View file

@ -24,6 +24,14 @@ enum class EHyperTwistTrainingSelectionPolicy : uint8
Scripted UMETA(DisplayName = "Scripted")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingCoachGuidanceLane : uint8
{
None UMETA(DisplayName = "None"),
Recommended UMETA(DisplayName = "Recommended"),
FollowUp UMETA(DisplayName = "Follow-Up")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingPromptKind : uint8
{
@ -1810,6 +1818,10 @@ struct FHyperTwistTrainingCoachMemorySnapshot
EHyperTwistTrainingSelectionPolicy DominantCoachSelectionPolicy =
EHyperTwistTrainingSelectionPolicy::Weighted;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingCoachGuidanceLane PreferredGuidanceLane =
EHyperTwistTrainingCoachGuidanceLane::None;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float ArchiveStabilityScore = 0.0f;
@ -1822,6 +1834,21 @@ struct FHyperTwistTrainingCoachMemorySnapshot
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DominantScheduleDeferralReasonLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreferredGuidanceSourceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RecommendedGuidancePlanCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 FollowUpGuidancePlanCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RecommendedGuidanceContinueBias = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 FollowUpGuidanceContinueBias = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bNeedsCoachIntervention = false;
@ -3681,6 +3708,10 @@ struct FHyperTwistTrainingCoachRecommendationHistorySummary
EHyperTwistTrainingSelectionPolicy DominantSelectionPolicy =
EHyperTwistTrainingSelectionPolicy::Weighted;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingCoachGuidanceLane PreferredGuidanceLane =
EHyperTwistTrainingCoachGuidanceLane::None;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SequenceCount = 0;
@ -3696,6 +3727,18 @@ struct FHyperTwistTrainingCoachRecommendationHistorySummary
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 FollowUpReadySequenceCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RecommendedLanePlanCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 FollowUpLanePlanCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RecommendedLaneContinueBias = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 FollowUpLaneContinueBias = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RecurringCaseCount = 0;
@ -4224,6 +4267,13 @@ struct FHyperTwistTrainingCoachPanelState
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingCoachGuidanceLane PreferredGuidanceLane =
EHyperTwistTrainingCoachGuidanceLane::None;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreferredGuidanceSourceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SignalCount = 0;