From 8d740cb997562965d034085498149349a6955e72 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Sun, 26 Apr 2026 03:40:16 +0200 Subject: [PATCH] Refine coach archive retention and horizon logic --- .../HyperTwistContractLibrary.cpp | 170 ++ .../HyperTwistTrainingCoachLibrary.cpp | 102 +- .../HyperTwistTrainingRepositoryLibrary.cpp | 1464 +++++++++++++++++ .../HyperTwistTrainingRuntimeLibrary.cpp | 72 + .../HyperTwistTrainingSubsystem.cpp | 71 + .../HyperTwistContractLibrary.h | 18 + .../HyperTwistTrainingCoachLibrary.h | 9 + .../HyperTwistTrainingRepositoryLibrary.h | 42 + .../HyperTwistTrainingRuntimeLibrary.h | 24 + .../HyperTwistTrainingSubsystem.h | 36 + .../HyperTwistTrainingTypes.h | 538 ++++++ 11 files changed, 2545 insertions(+), 1 deletion(-) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp index dbd85c2..4d258d9 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp @@ -1113,6 +1113,176 @@ FHyperTwistTrainingCoachActionPlanOutcomeSummary UHyperTwistContractLibrary::Mak ); } +FHyperTwistTrainingCoachActionSequenceSummary UHyperTwistContractLibrary::MakeSampleTrainingCoachActionSequenceSummary() +{ + FHyperTwistTrainingRepositoryState RepositoryState = MakeSampleTrainingRepositoryState(); + const FHyperTwistTrainingCoachActionPlan RootPlan = MakeSampleTrainingCoachActionPlan(); + const FHyperTwistCoachBrief FollowUpBrief = MakeSampleTrainingCoachFollowUpBrief(); + const FHyperTwistTrainingCoachActionPlan FollowUpPlan = + UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan( + FollowUpBrief, + TEXT("coach_follow_up_training_session_02"), + TEXT("2026-04-28T12:20:00Z") + ); + if (FollowUpPlan.IsStructurallyValid()) + { + RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachActionPlan( + RepositoryState, + FollowUpPlan + ); + return UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionSequenceSummary( + RepositoryState, + FollowUpPlan, + TEXT("2026-04-28T12:20:00Z") + ); + } + + return UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionSequenceSummary( + RepositoryState, + RootPlan, + TEXT("2026-04-28T12:20:00Z") + ); +} + +FHyperTwistTrainingCoachActionClosureSummary UHyperTwistContractLibrary::MakeSampleTrainingCoachActionClosureSummary() +{ + FHyperTwistTrainingRepositoryState RepositoryState = MakeSampleTrainingRepositoryState(); + const FHyperTwistTrainingCoachActionPlan RootPlan = MakeSampleTrainingCoachActionPlan(); + const FHyperTwistCoachBrief FollowUpBrief = MakeSampleTrainingCoachFollowUpBrief(); + const FHyperTwistTrainingCoachActionPlan FollowUpPlan = + UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan( + FollowUpBrief, + TEXT("coach_follow_up_training_session_02"), + TEXT("2026-04-28T12:20:00Z") + ); + if (FollowUpPlan.IsStructurallyValid()) + { + RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachActionPlan( + RepositoryState, + FollowUpPlan + ); + return UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionClosureSummary( + RepositoryState, + FollowUpPlan, + TEXT("2026-04-28T12:20:00Z") + ); + } + + return UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionClosureSummary( + RepositoryState, + RootPlan, + TEXT("2026-04-28T12:20:00Z") + ); +} + +FHyperTwistTrainingCoachClosureMemorySummary UHyperTwistContractLibrary::MakeSampleTrainingCoachClosureMemorySummary() +{ + const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult(); + if (!StepResult.IsStructurallyValid()) + { + return FHyperTwistTrainingCoachClosureMemorySummary(); + } + + return UHyperTwistTrainingRepositoryLibrary::DeriveCoachClosureMemorySummary( + MakeSampleTrainingRepositoryState(), + StepResult.UpdatedRunState.Session.UserId, + TEXT("2026-04-28T12:20:00Z") + ); +} + +FHyperTwistTrainingCoachRecommendationHistorySummary UHyperTwistContractLibrary::MakeSampleTrainingCoachRecommendationHistorySummary() +{ + const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult(); + if (!StepResult.IsStructurallyValid()) + { + return FHyperTwistTrainingCoachRecommendationHistorySummary(); + } + + FHyperTwistTrainingRepositoryState RepositoryState = MakeSampleTrainingRepositoryState(); + const FHyperTwistCoachBrief FollowUpBrief = MakeSampleTrainingCoachFollowUpBrief(); + const FHyperTwistTrainingCoachActionPlan FollowUpPlan = + UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan( + FollowUpBrief, + TEXT("coach_follow_up_training_session_02"), + TEXT("2026-04-28T12:20:00Z") + ); + if (FollowUpPlan.IsStructurallyValid()) + { + RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachActionPlan( + RepositoryState, + FollowUpPlan + ); + } + + return UHyperTwistTrainingRepositoryLibrary::DeriveCoachRecommendationHistorySummary( + RepositoryState, + StepResult.UpdatedRunState.Session.UserId, + TEXT("2026-04-28T12:20:00Z") + ); +} + +FHyperTwistTrainingCoachCarryForwardPolicySummary UHyperTwistContractLibrary::MakeSampleTrainingCoachCarryForwardPolicySummary() +{ + const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult(); + if (!StepResult.IsStructurallyValid()) + { + return FHyperTwistTrainingCoachCarryForwardPolicySummary(); + } + + FHyperTwistTrainingRepositoryState RepositoryState = MakeSampleTrainingRepositoryState(); + const FHyperTwistCoachBrief FollowUpBrief = MakeSampleTrainingCoachFollowUpBrief(); + const FHyperTwistTrainingCoachActionPlan FollowUpPlan = + UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan( + FollowUpBrief, + TEXT("coach_follow_up_training_session_02"), + TEXT("2026-04-28T12:20:00Z") + ); + if (FollowUpPlan.IsStructurallyValid()) + { + RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachActionPlan( + RepositoryState, + FollowUpPlan + ); + } + + return UHyperTwistTrainingRepositoryLibrary::DeriveCoachCarryForwardPolicySummary( + RepositoryState, + StepResult.UpdatedRunState.Session.UserId, + TEXT("2026-04-28T12:20:00Z") + ); +} + +FHyperTwistTrainingCoachArchivePolicySummary UHyperTwistContractLibrary::MakeSampleTrainingCoachArchivePolicySummary() +{ + const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult(); + if (!StepResult.IsStructurallyValid()) + { + return FHyperTwistTrainingCoachArchivePolicySummary(); + } + + FHyperTwistTrainingRepositoryState RepositoryState = MakeSampleTrainingRepositoryState(); + const FHyperTwistCoachBrief FollowUpBrief = MakeSampleTrainingCoachFollowUpBrief(); + const FHyperTwistTrainingCoachActionPlan FollowUpPlan = + UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan( + FollowUpBrief, + TEXT("coach_follow_up_training_session_02"), + TEXT("2026-04-28T12:20:00Z") + ); + if (FollowUpPlan.IsStructurallyValid()) + { + RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachActionPlan( + RepositoryState, + FollowUpPlan + ); + } + + return UHyperTwistTrainingRepositoryLibrary::DeriveCoachArchivePolicySummary( + RepositoryState, + StepResult.UpdatedRunState.Session.UserId, + TEXT("2026-04-28T12:20:00Z") + ); +} + FHyperTwistCoachBrief UHyperTwistContractLibrary::MakeSampleTrainingCoachFollowUpBrief() { return UHyperTwistTrainingCoachLibrary::DeriveCoachFollowUpBrief( diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index e20e1ce..3049001 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -60,6 +60,84 @@ TArray UHyperTwistTrainingCoachLibrary::DeriveCoachSigna { TArray Signals; + if (CoachMemorySnapshot.EscalatedCoachSequenceCount > 0 + || CoachMemorySnapshot.EscalationCaseIds.Num() > 0) + { + const float Score = 96.0f + + FMath::Min(static_cast(CoachMemorySnapshot.EscalatedCoachSequenceCount) * 4.0f, 8.0f) + + FMath::Min(static_cast(CoachMemorySnapshot.EscalationCaseIds.Num()), 6.0f); + FHyperTwistCoachSignal Signal = HyperTwistTrainingCoachLibraryInternal::MakeSignal( + TEXT("coach_escalation"), + EHyperTwistCoachFocusLane::Accuracy, + Score, + TEXT("Escalation is needed for unresolved coaching cases"), + FString::Printf( + TEXT("escalatedSequences=%d, escalationCases=%d, followUpReady=%d"), + CoachMemorySnapshot.EscalatedCoachSequenceCount, + CoachMemorySnapshot.EscalationCaseIds.Num(), + CoachMemorySnapshot.FollowUpReadyCoachSequenceCount), + EHyperTwistTrainingDeliveryMode::CoachReviewed, + EHyperTwistTrainingSelectionPolicy::Scripted); + Signal.SuggestedDeckId = CoachMemorySnapshot.FocusDeckId; + Signal.SuggestedMethodSegmentId = CoachMemorySnapshot.FocusMethodSegmentId; + Signal.SuggestedCaseIds = CoachMemorySnapshot.EscalationCaseIds.Num() > 0 + ? CoachMemorySnapshot.EscalationCaseIds + : CoachMemorySnapshot.PersistentCoachWeakCaseIds; + Signals.Add(Signal); + } + + if (CoachMemorySnapshot.bHasRepeatRecommendationPressure + && CoachMemorySnapshot.RecurringCoachCaseIds.Num() > 0) + { + const float Score = 78.0f + + FMath::Min(static_cast(CoachMemorySnapshot.RepeatRecommendationSequenceCount) * 3.0f, 9.0f) + + FMath::Min(static_cast(CoachMemorySnapshot.RecurringCoachCaseIds.Num()), 6.0f); + FHyperTwistCoachSignal Signal = HyperTwistTrainingCoachLibraryInternal::MakeSignal( + TEXT("repeat_recommendation"), + EHyperTwistCoachFocusLane::Stability, + Score, + TEXT("Recurring coach cases need consolidation"), + FString::Printf( + TEXT("repeatSequences=%d, recurringCases=%d, dominantMode=%d"), + CoachMemorySnapshot.RepeatRecommendationSequenceCount, + CoachMemorySnapshot.RecurringCoachCaseIds.Num(), + static_cast(CoachMemorySnapshot.DominantCoachSuggestedMode)), + CoachMemorySnapshot.DominantCoachSuggestedMode, + CoachMemorySnapshot.DominantCoachSelectionPolicy); + Signal.SuggestedDeckId = CoachMemorySnapshot.FocusDeckId; + Signal.SuggestedMethodSegmentId = CoachMemorySnapshot.FocusMethodSegmentId; + Signal.SuggestedCaseIds = CoachMemorySnapshot.PinnedCarryForwardCaseIds.Num() > 0 + ? CoachMemorySnapshot.PinnedCarryForwardCaseIds + : CoachMemorySnapshot.RecurringCoachCaseIds; + Signals.Add(Signal); + } + + if (CoachMemorySnapshot.bNeedsArchiveRetentionTuning + || CoachMemorySnapshot.ArchiveReopenRiskCaseIds.Num() > 0) + { + const float Score = 58.0f + + FMath::Min(CoachMemorySnapshot.ArchiveRetentionTuningScore * 0.35f, 22.0f) + + FMath::Min(static_cast(CoachMemorySnapshot.ArchiveReopenRiskCaseIds.Num()) * 5.0f, 15.0f); + FHyperTwistCoachSignal Signal = HyperTwistTrainingCoachLibraryInternal::MakeSignal( + TEXT("archive_retention"), + EHyperTwistCoachFocusLane::Stability, + Score, + TEXT("Archive retention needs tuning"), + FString::Printf( + TEXT("reopenRisk=%d, provisionalArchive=%d, stableArchive=%d"), + CoachMemorySnapshot.ArchiveReopenRiskCaseIds.Num(), + CoachMemorySnapshot.ProvisionalArchivedCoachCaseIds.Num(), + CoachMemorySnapshot.StableArchivedCoachCaseIds.Num()), + CoachMemorySnapshot.DominantCoachSuggestedMode, + CoachMemorySnapshot.DominantCoachSelectionPolicy); + Signal.SuggestedDeckId = CoachMemorySnapshot.FocusDeckId; + Signal.SuggestedMethodSegmentId = CoachMemorySnapshot.FocusMethodSegmentId; + Signal.SuggestedCaseIds = CoachMemorySnapshot.ArchiveReopenRiskCaseIds.Num() > 0 + ? CoachMemorySnapshot.ArchiveReopenRiskCaseIds + : CoachMemorySnapshot.ProvisionalArchivedCoachCaseIds; + Signals.Add(Signal); + } + if (CoachMemorySnapshot.bNeedsCoachIntervention || ReviewProgramSummary.bNeedsCoachIntervention || LearnerDeckStateSummary.OverdueCaseCount >= 3) @@ -330,7 +408,26 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::DeriveCoachBrief( if (Brief.FocusCaseIds.Num() == 0) { - if (Brief.bNeedsCoachReview && CoachMemorySnapshot.OverdueCaseIds.Num() > 0) + if (CoachMemorySnapshot.EscalationCaseIds.Num() > 0) + { + Brief.FocusCaseIds = CoachMemorySnapshot.EscalationCaseIds; + } + else if (CoachMemorySnapshot.bHasPinnedCarryForwardPressure + && CoachMemorySnapshot.PinnedCarryForwardCaseIds.Num() > 0) + { + Brief.FocusCaseIds = CoachMemorySnapshot.PinnedCarryForwardCaseIds; + } + else if (CoachMemorySnapshot.bHasRepeatRecommendationPressure + && CoachMemorySnapshot.RecurringCoachCaseIds.Num() > 0) + { + Brief.FocusCaseIds = CoachMemorySnapshot.RecurringCoachCaseIds; + } + else if (CoachMemorySnapshot.bNeedsArchiveRetentionTuning + && CoachMemorySnapshot.ArchiveReopenRiskCaseIds.Num() > 0) + { + Brief.FocusCaseIds = CoachMemorySnapshot.ArchiveReopenRiskCaseIds; + } + else if (Brief.bNeedsCoachReview && CoachMemorySnapshot.OverdueCaseIds.Num() > 0) { Brief.FocusCaseIds = CoachMemorySnapshot.OverdueCaseIds; } @@ -364,6 +461,9 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::DeriveCoachFollowUpBrief( : ActionPlan.ReferenceUtc; Brief.TrainingSessionId = OutcomeSummary.SourceSessionId; Brief.SourceReviewPlanId = CurrentBrief.SourceReviewPlanId; + Brief.CarryForwardPlanId = ActionPlan.PlanId; + Brief.RootPlanId = !ActionPlan.RootPlanId.IsEmpty() ? ActionPlan.RootPlanId : ActionPlan.PlanId; + Brief.FollowUpDepth = ActionPlan.FollowUpDepth + 1; Brief.FocusDeckId = !OutcomeSummary.FocusDeckId.IsEmpty() ? OutcomeSummary.FocusDeckId : ActionPlan.FocusDeckId; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp index c7ce656..2223d37 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp @@ -457,6 +457,20 @@ namespace HyperTwistTrainingRepositoryLibraryInternal { NormalizedPlan.Headline = NormalizedPlan.PrimaryActionLabel; } + if (NormalizedPlan.FollowUpDepth < 0) + { + NormalizedPlan.FollowUpDepth = 0; + } + if (NormalizedPlan.ParentPlanId.IsEmpty()) + { + NormalizedPlan.FollowUpDepth = 0; + } + if (NormalizedPlan.RootPlanId.IsEmpty()) + { + NormalizedPlan.RootPlanId = NormalizedPlan.ParentPlanId.IsEmpty() + ? NormalizedPlan.PlanId + : NormalizedPlan.ParentPlanId; + } return NormalizedPlan; } @@ -3808,6 +3822,269 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der Snapshot.FocusMethodSegmentId = Profile.FavoriteMethodSegmentId; } + const FHyperTwistTrainingCoachClosureMemorySummary ClosureMemory = DeriveCoachClosureMemorySummary( + RepositoryState, + UserId, + Snapshot.ReferenceUtc + ); + const FHyperTwistTrainingCoachRecommendationHistorySummary RecommendationHistory = + DeriveCoachRecommendationHistorySummary( + RepositoryState, + UserId, + Snapshot.ReferenceUtc + ); + const FHyperTwistTrainingCoachCarryForwardPolicySummary CarryForwardPolicy = + DeriveCoachCarryForwardPolicySummary( + RepositoryState, + UserId, + Snapshot.ReferenceUtc + ); + const FHyperTwistTrainingCoachArchivePolicySummary ArchivePolicy = + DeriveCoachArchivePolicySummary( + RepositoryState, + UserId, + Snapshot.ReferenceUtc + ); + if (ClosureMemory.IsStructurallyValid()) + { + Snapshot.ArchivedResolvedCaseIds = ClosureMemory.ArchivedResolvedCaseIds; + Snapshot.PersistentCoachWeakCaseIds = ClosureMemory.PersistentWeakCaseIds; + Snapshot.EscalationCaseIds = ClosureMemory.EscalationCaseIds; + Snapshot.ClosedCoachSequenceCount = ClosureMemory.ClosedSequenceCount; + Snapshot.EscalatedCoachSequenceCount = ClosureMemory.EscalatedSequenceCount; + Snapshot.FollowUpReadyCoachSequenceCount = ClosureMemory.FollowUpReadySequenceCount; + + if (!ClosureMemory.FocusDeckId.IsEmpty() + && (Snapshot.FocusDeckId.IsEmpty() + || ClosureMemory.bHasEscalationPressure + || ClosureMemory.bHasFollowUpPressure)) + { + Snapshot.FocusDeckId = ClosureMemory.FocusDeckId; + } + if (!ClosureMemory.FocusMethodSegmentId.IsEmpty() + && (Snapshot.FocusMethodSegmentId.IsEmpty() + || ClosureMemory.bHasEscalationPressure + || ClosureMemory.bHasFollowUpPressure)) + { + Snapshot.FocusMethodSegmentId = ClosureMemory.FocusMethodSegmentId; + } + + TSet ArchivedResolvedCaseSet; + for (const FString& CaseId : ClosureMemory.ArchivedResolvedCaseIds) + { + if (!CaseId.IsEmpty()) + { + ArchivedResolvedCaseSet.Add(CaseId); + } + } + + TArray PrioritizedWeakCaseIds; + const auto AppendUniqueCaseIds = [&PrioritizedWeakCaseIds, &ArchivedResolvedCaseSet](const TArray& SourceCaseIds) + { + for (const FString& CaseId : SourceCaseIds) + { + if (!CaseId.IsEmpty() + && !ArchivedResolvedCaseSet.Contains(CaseId) + && !PrioritizedWeakCaseIds.Contains(CaseId)) + { + PrioritizedWeakCaseIds.Add(CaseId); + } + } + }; + + AppendUniqueCaseIds(ClosureMemory.EscalationCaseIds); + AppendUniqueCaseIds(ClosureMemory.PersistentWeakCaseIds); + AppendUniqueCaseIds(Snapshot.WeakCaseIds); + Snapshot.WeakCaseIds = PrioritizedWeakCaseIds; + + Snapshot.bNeedsCoachIntervention = Snapshot.bNeedsCoachIntervention + || ClosureMemory.bHasEscalationPressure + || ClosureMemory.bHasFollowUpPressure; + } + if (RecommendationHistory.IsStructurallyValid()) + { + Snapshot.RecurringCoachCaseIds = RecommendationHistory.RecurringCaseIds; + Snapshot.RepeatRecommendationSequenceCount = RecommendationHistory.RepeatRecommendationSequenceCount; + Snapshot.DominantCoachSuggestedMode = RecommendationHistory.DominantSuggestedMode; + Snapshot.DominantCoachSelectionPolicy = RecommendationHistory.DominantSelectionPolicy; + Snapshot.bHasRepeatRecommendationPressure = RecommendationHistory.bHasRepeatRecommendationPressure; + + if (!RecommendationHistory.FocusDeckId.IsEmpty() + && (Snapshot.FocusDeckId.IsEmpty() + || RecommendationHistory.bShouldBiasCoachReviewed)) + { + Snapshot.FocusDeckId = RecommendationHistory.FocusDeckId; + } + if (!RecommendationHistory.FocusMethodSegmentId.IsEmpty() + && (Snapshot.FocusMethodSegmentId.IsEmpty() + || RecommendationHistory.bShouldBiasCoachReviewed)) + { + Snapshot.FocusMethodSegmentId = RecommendationHistory.FocusMethodSegmentId; + } + + TSet ArchivedResolvedCaseSet; + for (const FString& CaseId : Snapshot.ArchivedResolvedCaseIds) + { + if (!CaseId.IsEmpty()) + { + ArchivedResolvedCaseSet.Add(CaseId); + } + } + + TArray ExistingWeakCaseIds = Snapshot.WeakCaseIds; + TArray PrioritizedWeakCaseIds; + const auto AppendUniqueRecurringCases = [&PrioritizedWeakCaseIds, &ArchivedResolvedCaseSet](const TArray& SourceCaseIds) + { + for (const FString& CaseId : SourceCaseIds) + { + if (!CaseId.IsEmpty() + && !ArchivedResolvedCaseSet.Contains(CaseId) + && !PrioritizedWeakCaseIds.Contains(CaseId)) + { + PrioritizedWeakCaseIds.Add(CaseId); + } + } + }; + + AppendUniqueRecurringCases(RecommendationHistory.RecurringCaseIds); + AppendUniqueRecurringCases(ExistingWeakCaseIds); + Snapshot.WeakCaseIds = PrioritizedWeakCaseIds; + + Snapshot.bNeedsCoachIntervention = Snapshot.bNeedsCoachIntervention + || RecommendationHistory.bHasRepeatRecommendationPressure; + } + if (CarryForwardPolicy.IsStructurallyValid()) + { + Snapshot.SuppressedResolvedCoachCaseIds = CarryForwardPolicy.SuppressedResolvedCaseIds; + Snapshot.PinnedCarryForwardCaseIds = CarryForwardPolicy.PinnedCarryForwardCaseIds; + Snapshot.DominantCoachSuggestedMode = CarryForwardPolicy.DominantSuggestedMode; + Snapshot.DominantCoachSelectionPolicy = CarryForwardPolicy.DominantSelectionPolicy; + Snapshot.bHasPinnedCarryForwardPressure = CarryForwardPolicy.bPinRecurringCases + || CarryForwardPolicy.bNeedsEscalationHandOff; + + if (!CarryForwardPolicy.FocusDeckId.IsEmpty() + && (Snapshot.FocusDeckId.IsEmpty() + || CarryForwardPolicy.bNeedsEscalationHandOff + || CarryForwardPolicy.bBiasCoachReviewed)) + { + Snapshot.FocusDeckId = CarryForwardPolicy.FocusDeckId; + } + if (!CarryForwardPolicy.FocusMethodSegmentId.IsEmpty() + && (Snapshot.FocusMethodSegmentId.IsEmpty() + || CarryForwardPolicy.bNeedsEscalationHandOff + || CarryForwardPolicy.bBiasCoachReviewed)) + { + Snapshot.FocusMethodSegmentId = CarryForwardPolicy.FocusMethodSegmentId; + } + + TSet SuppressedResolvedCaseSet; + for (const FString& CaseId : CarryForwardPolicy.SuppressedResolvedCaseIds) + { + if (!CaseId.IsEmpty()) + { + SuppressedResolvedCaseSet.Add(CaseId); + } + } + + TArray PrioritizedWeakCaseIds; + const auto AppendPolicyCaseIds = [&PrioritizedWeakCaseIds, &SuppressedResolvedCaseSet](const TArray& SourceCaseIds) + { + for (const FString& CaseId : SourceCaseIds) + { + if (!CaseId.IsEmpty() + && !SuppressedResolvedCaseSet.Contains(CaseId) + && !PrioritizedWeakCaseIds.Contains(CaseId)) + { + PrioritizedWeakCaseIds.Add(CaseId); + } + } + }; + + AppendPolicyCaseIds(CarryForwardPolicy.EscalationCarryForwardCaseIds); + AppendPolicyCaseIds(CarryForwardPolicy.PinnedCarryForwardCaseIds); + AppendPolicyCaseIds(Snapshot.WeakCaseIds); + Snapshot.WeakCaseIds = PrioritizedWeakCaseIds; + + Snapshot.bNeedsCoachIntervention = Snapshot.bNeedsCoachIntervention + || CarryForwardPolicy.bHasActiveCarryForwardPressure + || CarryForwardPolicy.bNeedsEscalationHandOff; + } + if (ArchivePolicy.IsStructurallyValid()) + { + Snapshot.LongTermArchivedCoachCaseIds = ArchivePolicy.LongTermArchivedCaseIds; + Snapshot.ExpiredCarryForwardCaseIds = ArchivePolicy.ExpiredCarryForwardCaseIds; + Snapshot.StableArchivedCoachCaseIds = ArchivePolicy.StableLongTermArchivedCaseIds; + Snapshot.ProvisionalArchivedCoachCaseIds = ArchivePolicy.ProvisionalArchivedCaseIds; + Snapshot.ArchiveReopenRiskCaseIds = ArchivePolicy.ReopenRiskArchivedCaseIds; + Snapshot.ArchivedResolvedCaseIds = ArchivePolicy.LongTermArchivedCaseIds; + Snapshot.SuppressedResolvedCoachCaseIds = ArchivePolicy.LongTermArchivedCaseIds; + Snapshot.PinnedCarryForwardCaseIds = ArchivePolicy.RetainedCarryForwardCaseIds; + Snapshot.DominantCoachSuggestedMode = ArchivePolicy.DominantSuggestedMode; + Snapshot.DominantCoachSelectionPolicy = ArchivePolicy.DominantSelectionPolicy; + Snapshot.ArchiveStabilityScore = ArchivePolicy.ArchiveStabilityScore; + Snapshot.ArchiveRetentionTuningScore = ArchivePolicy.ArchiveRetentionTuningScore; + Snapshot.bHasStableArchiveMemory = ArchivePolicy.bHasStableArchiveMemory; + Snapshot.bNeedsArchiveRetentionTuning = ArchivePolicy.bNeedsArchiveRetentionTuning; + Snapshot.bHasPinnedCarryForwardPressure = ArchivePolicy.RetainedCarryForwardCaseCount > 0 + || ArchivePolicy.bShouldRetainEscalationCarryForward; + + if (!ArchivePolicy.FocusDeckId.IsEmpty() + && (Snapshot.FocusDeckId.IsEmpty() || ArchivePolicy.bHasActiveArchivePressure)) + { + Snapshot.FocusDeckId = ArchivePolicy.FocusDeckId; + } + if (!ArchivePolicy.FocusMethodSegmentId.IsEmpty() + && (Snapshot.FocusMethodSegmentId.IsEmpty() || ArchivePolicy.bHasActiveArchivePressure)) + { + Snapshot.FocusMethodSegmentId = ArchivePolicy.FocusMethodSegmentId; + } + + TSet LongTermArchivedCaseSet; + for (const FString& CaseId : ArchivePolicy.LongTermArchivedCaseIds) + { + if (!CaseId.IsEmpty()) + { + LongTermArchivedCaseSet.Add(CaseId); + } + } + + TArray FilteredRecurringCaseIds; + for (const FString& CaseId : Snapshot.RecurringCoachCaseIds) + { + if (!CaseId.IsEmpty() && !LongTermArchivedCaseSet.Contains(CaseId)) + { + FilteredRecurringCaseIds.AddUnique(CaseId); + } + } + Snapshot.RecurringCoachCaseIds = FilteredRecurringCaseIds; + Snapshot.bHasRepeatRecommendationPressure = Snapshot.bHasRepeatRecommendationPressure + && Snapshot.RecurringCoachCaseIds.Num() > 0; + + TArray PrioritizedWeakCaseIds; + const auto AppendArchivePolicyCaseIds = [&PrioritizedWeakCaseIds, &LongTermArchivedCaseSet]( + const TArray& SourceCaseIds) + { + for (const FString& CaseId : SourceCaseIds) + { + if (!CaseId.IsEmpty() + && !LongTermArchivedCaseSet.Contains(CaseId) + && !PrioritizedWeakCaseIds.Contains(CaseId)) + { + PrioritizedWeakCaseIds.Add(CaseId); + } + } + }; + + AppendArchivePolicyCaseIds(ArchivePolicy.EscalationRetainedCaseIds); + AppendArchivePolicyCaseIds(ArchivePolicy.RetainedCarryForwardCaseIds); + AppendArchivePolicyCaseIds(Snapshot.WeakCaseIds); + Snapshot.WeakCaseIds = PrioritizedWeakCaseIds; + + Snapshot.bNeedsCoachIntervention = Snapshot.bNeedsCoachIntervention + || ArchivePolicy.bHasActiveArchivePressure + || ArchivePolicy.bNeedsArchiveRetentionTuning; + } + if (Snapshot.OverdueCaseIds.Num() >= 3) { Snapshot.bNeedsCoachIntervention = true; @@ -4504,6 +4781,10 @@ FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRepositoryLibrary::BuildCo ); ActionPlan.SourceSessionId = CoachBrief.TrainingSessionId; ActionPlan.SourceReviewPlanId = CoachBrief.SourceReviewPlanId; + ActionPlan.ParentPlanId = CoachBrief.CarryForwardPlanId; + ActionPlan.RootPlanId = !CoachBrief.RootPlanId.IsEmpty() + ? CoachBrief.RootPlanId + : (!CoachBrief.CarryForwardPlanId.IsEmpty() ? CoachBrief.CarryForwardPlanId : PlanId); ActionPlan.FocusDeckId = CoachBrief.FocusDeckId; ActionPlan.FocusMethodSegmentId = CoachBrief.FocusMethodSegmentId; ActionPlan.FocusLaneLabel = StaticEnum()->GetNameStringByValue( @@ -4519,6 +4800,7 @@ FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRepositoryLibrary::BuildCo ActionPlan.SuggestedSelectionPolicy = CoachBrief.SuggestedSelectionPolicy; ActionPlan.FocusCaseIds = CoachBrief.FocusCaseIds; ActionPlan.PlanState = EHyperTwistTrainingCoachActionPlanState::Draft; + ActionPlan.FollowUpDepth = FMath::Max(0, CoachBrief.FollowUpDepth); ActionPlan.CreatedAtUtc = ActionPlan.ReferenceUtc; ActionPlan.bNeedsCoachReview = CoachBrief.bNeedsCoachReview; ActionPlan.bNeedsCadenceRecovery = CoachBrief.bNeedsCadenceRecovery; @@ -5116,6 +5398,1188 @@ FHyperTwistTrainingCoachActionPlanOutcomeSummary UHyperTwistTrainingRepositoryLi return Summary; } +FHyperTwistTrainingCoachActionSequenceSummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionSequenceSummary( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FHyperTwistTrainingCoachActionPlan& AnchorPlan, + const FString& ReferenceUtc +) +{ + FHyperTwistTrainingCoachActionSequenceSummary Summary; + const FHyperTwistTrainingCoachActionPlan NormalizedAnchorPlan = + HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(AnchorPlan); + if (!NormalizedAnchorPlan.IsStructurallyValid()) + { + return Summary; + } + + const FString RootPlanId = !NormalizedAnchorPlan.RootPlanId.IsEmpty() + ? NormalizedAnchorPlan.RootPlanId + : NormalizedAnchorPlan.PlanId; + Summary.UserId = NormalizedAnchorPlan.UserId; + Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc( + !ReferenceUtc.IsEmpty() ? ReferenceUtc : NormalizedAnchorPlan.ReferenceUtc + ); + Summary.RootPlanId = RootPlanId; + + const TArray UserPlans = ListCoachActionPlansForUser( + RepositoryState, + NormalizedAnchorPlan.UserId + ); + TArray SequencePlans; + for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans) + { + const FHyperTwistTrainingCoachActionPlan NormalizedPlan = + HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan); + const FString CandidateRootPlanId = !NormalizedPlan.RootPlanId.IsEmpty() + ? NormalizedPlan.RootPlanId + : NormalizedPlan.PlanId; + if (CandidateRootPlanId == RootPlanId + || NormalizedPlan.PlanId == RootPlanId + || NormalizedPlan.ParentPlanId == RootPlanId) + { + SequencePlans.Add(NormalizedPlan); + } + } + if (SequencePlans.Num() == 0) + { + SequencePlans.Add(NormalizedAnchorPlan); + } + + FString LatestSortUtc; + TSet UnresolvedFollowUpCases; + float CompletionRateAccumulator = 0.0f; + float OutcomeScoreAccumulator = 0.0f; + + for (const FHyperTwistTrainingCoachActionPlan& Plan : SequencePlans) + { + Summary.PlanCount += 1; + Summary.CarryForwardPlanCount += Plan.FollowUpDepth > 0 ? 1 : 0; + Summary.MaxFollowUpDepth = FMath::Max(Summary.MaxFollowUpDepth, Plan.FollowUpDepth); + + switch (Plan.PlanState) + { + case EHyperTwistTrainingCoachActionPlanState::Active: + Summary.ActivePlanCount += 1; + Summary.bHasOpenPlan = true; + break; + case EHyperTwistTrainingCoachActionPlanState::Completed: + Summary.CompletedPlanCount += 1; + break; + case EHyperTwistTrainingCoachActionPlanState::Aborted: + Summary.AbortedPlanCount += 1; + break; + default: + Summary.bHasOpenPlan = true; + break; + } + + const FHyperTwistTrainingCoachActionPlanOutcomeSummary OutcomeSummary = + DeriveCoachActionPlanOutcomeSummary(RepositoryState, Plan, Summary.ReferenceUtc); + Summary.TotalFocusCaseCount += OutcomeSummary.FocusCaseCount; + CompletionRateAccumulator += OutcomeSummary.CompletionRate; + OutcomeScoreAccumulator += OutcomeSummary.OutcomeScore; + Summary.bNeedsMoreFollowUp = Summary.bNeedsMoreFollowUp || OutcomeSummary.bNeedsFollowUpPlan; + for (const FString& CaseId : OutcomeSummary.FollowUpCaseIds) + { + if (!CaseId.IsEmpty()) + { + UnresolvedFollowUpCases.Add(CaseId); + } + } + + const FString SortUtc = !Plan.CompletedAtUtc.IsEmpty() + ? Plan.CompletedAtUtc + : (!Plan.StartedAtUtc.IsEmpty() ? Plan.StartedAtUtc : Plan.CreatedAtUtc); + if (Summary.LatestPlanId.IsEmpty() + || HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, LatestSortUtc) > 0 + || (HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, LatestSortUtc) == 0 + && Plan.FollowUpDepth >= Summary.MaxFollowUpDepth)) + { + Summary.LatestPlanId = Plan.PlanId; + Summary.FocusDeckId = Plan.FocusDeckId; + Summary.FocusMethodSegmentId = Plan.FocusMethodSegmentId; + Summary.LatestOutcomeScore = OutcomeSummary.OutcomeScore; + LatestSortUtc = SortUtc; + } + } + + Summary.UnresolvedFollowUpCaseCount = UnresolvedFollowUpCases.Num(); + if (Summary.PlanCount > 0) + { + Summary.AverageCompletionRate = CompletionRateAccumulator / static_cast(Summary.PlanCount); + Summary.AverageOutcomeScore = OutcomeScoreAccumulator / static_cast(Summary.PlanCount); + } + Summary.bNeedsMoreFollowUp = Summary.bNeedsMoreFollowUp + || Summary.UnresolvedFollowUpCaseCount > 0 + || Summary.bHasOpenPlan; + + return Summary; +} + +FHyperTwistTrainingCoachActionClosureSummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionClosureSummary( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FHyperTwistTrainingCoachActionPlan& AnchorPlan, + const FString& ReferenceUtc +) +{ + FHyperTwistTrainingCoachActionClosureSummary Summary; + const FHyperTwistTrainingCoachActionPlan NormalizedAnchorPlan = + HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(AnchorPlan); + if (!NormalizedAnchorPlan.IsStructurallyValid()) + { + return Summary; + } + + const FHyperTwistTrainingCoachActionSequenceSummary SequenceSummary = + DeriveCoachActionSequenceSummary(RepositoryState, NormalizedAnchorPlan, ReferenceUtc); + if (!SequenceSummary.IsStructurallyValid()) + { + return Summary; + } + + Summary.UserId = SequenceSummary.UserId; + Summary.ReferenceUtc = SequenceSummary.ReferenceUtc; + Summary.RootPlanId = SequenceSummary.RootPlanId; + Summary.LatestPlanId = SequenceSummary.LatestPlanId; + Summary.FocusDeckId = SequenceSummary.FocusDeckId; + Summary.FocusMethodSegmentId = SequenceSummary.FocusMethodSegmentId; + + TArray SequencePlans; + const TArray UserPlans = ListCoachActionPlansForUser( + RepositoryState, + NormalizedAnchorPlan.UserId + ); + for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans) + { + const FHyperTwistTrainingCoachActionPlan NormalizedPlan = + HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan); + const FString CandidateRootPlanId = !NormalizedPlan.RootPlanId.IsEmpty() + ? NormalizedPlan.RootPlanId + : NormalizedPlan.PlanId; + if (CandidateRootPlanId == SequenceSummary.RootPlanId + || NormalizedPlan.PlanId == SequenceSummary.RootPlanId + || NormalizedPlan.ParentPlanId == SequenceSummary.RootPlanId) + { + SequencePlans.Add(NormalizedPlan); + } + } + if (SequencePlans.Num() == 0) + { + SequencePlans.Add(NormalizedAnchorPlan); + } + + TSet AllFocusCaseIds; + TSet ResolvedCaseIds; + TSet UnresolvedCaseIds; + TMap CaseAttemptCounts; + TMap CaseNonSuccessCounts; + TMap CaseSuccessCounts; + bool bAnyRecognition = false; + + for (const FHyperTwistTrainingCoachActionPlan& Plan : SequencePlans) + { + const FHyperTwistTrainingCoachActionPlanOutcomeSummary OutcomeSummary = + DeriveCoachActionPlanOutcomeSummary(RepositoryState, Plan, SequenceSummary.ReferenceUtc); + TSet PlanFocusCaseIds; + for (const FString& FocusCaseId : Plan.FocusCaseIds) + { + if (!FocusCaseId.IsEmpty()) + { + PlanFocusCaseIds.Add(FocusCaseId); + AllFocusCaseIds.Add(FocusCaseId); + } + } + for (const FHyperTwistTrainingCoachActionPlanEntry& Entry : Plan.Entries) + { + if (!Entry.CaseId.IsEmpty()) + { + PlanFocusCaseIds.Add(Entry.CaseId); + AllFocusCaseIds.Add(Entry.CaseId); + } + } + + TSet PlanFollowUpCaseIds; + for (const FString& FollowUpCaseId : OutcomeSummary.FollowUpCaseIds) + { + if (!FollowUpCaseId.IsEmpty()) + { + PlanFollowUpCaseIds.Add(FollowUpCaseId); + UnresolvedCaseIds.Add(FollowUpCaseId); + } + } + + for (const FString& FocusCaseId : PlanFocusCaseIds) + { + if (!PlanFollowUpCaseIds.Contains(FocusCaseId)) + { + ResolvedCaseIds.Add(FocusCaseId); + } + } + + FHyperTwistTrainingRunRecord RunRecord; + if (!Plan.SourceSessionId.IsEmpty() + && TryGetRunRecord(RepositoryState, Plan.SourceSessionId, RunRecord)) + { + for (const FHyperTwistTrainingAttemptHistoryEntry& AttemptHistoryEntry : RunRecord.AttemptHistory) + { + if (!AllFocusCaseIds.Contains(AttemptHistoryEntry.CaseId)) + { + continue; + } + + Summary.TotalAttemptCount += 1; + CaseAttemptCounts.FindOrAdd(AttemptHistoryEntry.CaseId) += 1; + if (AttemptHistoryEntry.DeliveryMode == EHyperTwistTrainingDeliveryMode::RecognitionAssisted + || AttemptHistoryEntry.RecognitionTimeMs > 0) + { + bAnyRecognition = true; + } + + if (AttemptHistoryEntry.Result == EHyperTwistTrainingAttemptResult::Success) + { + CaseSuccessCounts.FindOrAdd(AttemptHistoryEntry.CaseId) += 1; + } + else + { + CaseNonSuccessCounts.FindOrAdd(AttemptHistoryEntry.CaseId) += 1; + } + } + } + } + + for (const FString& UnresolvedCaseId : UnresolvedCaseIds) + { + ResolvedCaseIds.Remove(UnresolvedCaseId); + } + + TSet PersistentWeakCaseIds; + for (const TPair& Pair : CaseNonSuccessCounts) + { + const int32 SuccessCount = CaseSuccessCounts.FindRef(Pair.Key); + if (Pair.Value >= 2 + || (UnresolvedCaseIds.Contains(Pair.Key) && Pair.Value >= 1) + || (SuccessCount == 0 && CaseAttemptCounts.FindRef(Pair.Key) >= 2)) + { + PersistentWeakCaseIds.Add(Pair.Key); + } + } + + Summary.ResolvedCaseIds = ResolvedCaseIds.Array(); + Summary.PersistentWeakCaseIds = PersistentWeakCaseIds.Array(); + Summary.UnresolvedCaseIds = UnresolvedCaseIds.Array(); + Summary.ResolvedCaseIds.Sort(); + Summary.PersistentWeakCaseIds.Sort(); + Summary.UnresolvedCaseIds.Sort(); + Summary.ResolvedCaseCount = Summary.ResolvedCaseIds.Num(); + Summary.PersistentWeakCaseCount = Summary.PersistentWeakCaseIds.Num(); + Summary.UnresolvedCaseCount = Summary.UnresolvedCaseIds.Num(); + Summary.RepeatedFailureCaseCount = Summary.PersistentWeakCaseCount; + + Summary.bSequenceClosed = + !SequenceSummary.bHasOpenPlan + && !SequenceSummary.bNeedsMoreFollowUp + && Summary.UnresolvedCaseCount == 0 + && SequenceSummary.CompletedPlanCount > 0 + && SequenceSummary.AbortedPlanCount == 0; + Summary.bNeedsEscalation = + SequenceSummary.AbortedPlanCount > 0 + || (SequenceSummary.MaxFollowUpDepth >= 2 && Summary.UnresolvedCaseCount > 0) + || Summary.PersistentWeakCaseCount >= 2 + || SequenceSummary.AverageOutcomeScore < 30.0f; + Summary.bShouldSpawnFollowUp = + !Summary.bSequenceClosed + && !SequenceSummary.bHasOpenPlan + && (Summary.UnresolvedCaseCount > 0 || Summary.PersistentWeakCaseCount > 0); + Summary.bShouldArchiveResolvedCases = + Summary.ResolvedCaseCount > 0 + && (Summary.bSequenceClosed || (!Summary.bShouldSpawnFollowUp && Summary.PersistentWeakCaseCount == 0)); + + if (Summary.bSequenceClosed) + { + Summary.ClosureLabel = TEXT("sequence-closed"); + Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::Timer; + } + else if (Summary.bNeedsEscalation) + { + Summary.ClosureLabel = TEXT("needs-escalation"); + Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::CoachReviewed; + } + else if (Summary.bShouldSpawnFollowUp) + { + Summary.ClosureLabel = TEXT("follow-up-ready"); + Summary.RecommendedNextMode = bAnyRecognition + ? EHyperTwistTrainingDeliveryMode::RecognitionAssisted + : EHyperTwistTrainingDeliveryMode::VirtualCube; + } + else if (SequenceSummary.bHasOpenPlan) + { + Summary.ClosureLabel = TEXT("open-active"); + Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::CoachReviewed; + } + else + { + Summary.ClosureLabel = TEXT("sequence-stable"); + Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::Timer; + } + + Summary.ClosureScore = FMath::Clamp( + SequenceSummary.AverageOutcomeScore + + (static_cast(Summary.ResolvedCaseCount) * 6.0f) + - (static_cast(Summary.PersistentWeakCaseCount) * 8.0f) + - (static_cast(Summary.UnresolvedCaseCount) * 10.0f) + - (Summary.bNeedsEscalation ? 20.0f : 0.0f), + 0.0f, + 100.0f + ); + + return Summary; +} + +FHyperTwistTrainingCoachClosureMemorySummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachClosureMemorySummary( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FString& UserId, + const FString& ReferenceUtc +) +{ + FHyperTwistTrainingCoachClosureMemorySummary Summary; + if (UserId.IsEmpty()) + { + return Summary; + } + + Summary.UserId = UserId; + Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc); + + const TArray UserPlans = ListCoachActionPlansForUser(RepositoryState, UserId); + TMap LatestPlanByRootId; + TMap LatestSortUtcByRootId; + + for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans) + { + const FHyperTwistTrainingCoachActionPlan NormalizedPlan = + HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan); + if (!NormalizedPlan.IsStructurallyValid()) + { + continue; + } + + const FString RootPlanId = !NormalizedPlan.RootPlanId.IsEmpty() + ? NormalizedPlan.RootPlanId + : NormalizedPlan.PlanId; + const FString SortUtc = !NormalizedPlan.CompletedAtUtc.IsEmpty() + ? NormalizedPlan.CompletedAtUtc + : (!NormalizedPlan.StartedAtUtc.IsEmpty() ? NormalizedPlan.StartedAtUtc : NormalizedPlan.CreatedAtUtc); + + FHyperTwistTrainingCoachActionPlan* ExistingPlan = LatestPlanByRootId.Find(RootPlanId); + FString* ExistingSortUtc = LatestSortUtcByRootId.Find(RootPlanId); + if (ExistingPlan == nullptr || ExistingSortUtc == nullptr) + { + LatestPlanByRootId.Add(RootPlanId, NormalizedPlan); + LatestSortUtcByRootId.Add(RootPlanId, SortUtc); + continue; + } + + const int32 CompareResult = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, *ExistingSortUtc); + if (CompareResult > 0 + || (CompareResult == 0 && NormalizedPlan.FollowUpDepth >= ExistingPlan->FollowUpDepth)) + { + *ExistingPlan = NormalizedPlan; + *ExistingSortUtc = SortUtc; + } + } + + float ClosureScoreAccumulator = 0.0f; + int32 FocusPressureScore = -1; + FString FocusSortUtc; + TSet ArchivedResolvedCaseIds; + TSet PersistentWeakCaseIds; + TSet EscalationCaseIds; + + for (const TPair& Pair : LatestPlanByRootId) + { + const FHyperTwistTrainingCoachActionClosureSummary ClosureSummary = + DeriveCoachActionClosureSummary(RepositoryState, Pair.Value, Summary.ReferenceUtc); + if (!ClosureSummary.IsStructurallyValid()) + { + continue; + } + + Summary.SequenceCount += 1; + Summary.ClosedSequenceCount += ClosureSummary.bSequenceClosed ? 1 : 0; + Summary.EscalatedSequenceCount += ClosureSummary.bNeedsEscalation ? 1 : 0; + Summary.FollowUpReadySequenceCount += ClosureSummary.bShouldSpawnFollowUp ? 1 : 0; + ClosureScoreAccumulator += ClosureSummary.ClosureScore; + + if (ClosureSummary.bShouldArchiveResolvedCases) + { + for (const FString& CaseId : ClosureSummary.ResolvedCaseIds) + { + if (!CaseId.IsEmpty()) + { + ArchivedResolvedCaseIds.Add(CaseId); + } + } + } + for (const FString& CaseId : ClosureSummary.PersistentWeakCaseIds) + { + if (!CaseId.IsEmpty()) + { + PersistentWeakCaseIds.Add(CaseId); + } + } + if (ClosureSummary.bNeedsEscalation) + { + for (const FString& CaseId : ClosureSummary.PersistentWeakCaseIds) + { + if (!CaseId.IsEmpty()) + { + EscalationCaseIds.Add(CaseId); + } + } + for (const FString& CaseId : ClosureSummary.UnresolvedCaseIds) + { + if (!CaseId.IsEmpty()) + { + EscalationCaseIds.Add(CaseId); + } + } + } + + const int32 CurrentPressureScore = + (ClosureSummary.bNeedsEscalation ? 300 : 0) + + (ClosureSummary.bShouldSpawnFollowUp ? 200 : 0) + + (ClosureSummary.PersistentWeakCaseCount * 10) + + ClosureSummary.UnresolvedCaseCount; + const FString SortUtc = !ClosureSummary.ReferenceUtc.IsEmpty() + ? ClosureSummary.ReferenceUtc + : Summary.ReferenceUtc; + const int32 TimeCompare = FocusSortUtc.IsEmpty() + ? 1 + : HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, FocusSortUtc); + if (CurrentPressureScore > FocusPressureScore + || (CurrentPressureScore == FocusPressureScore && TimeCompare > 0)) + { + FocusPressureScore = CurrentPressureScore; + FocusSortUtc = SortUtc; + Summary.FocusRootPlanId = ClosureSummary.RootPlanId; + Summary.FocusDeckId = ClosureSummary.FocusDeckId; + Summary.FocusMethodSegmentId = ClosureSummary.FocusMethodSegmentId; + Summary.FocusClosureLabel = ClosureSummary.ClosureLabel; + } + } + + if (Summary.SequenceCount == 0) + { + return FHyperTwistTrainingCoachClosureMemorySummary(); + } + + Summary.ArchivedResolvedCaseIds = ArchivedResolvedCaseIds.Array(); + Summary.PersistentWeakCaseIds = PersistentWeakCaseIds.Array(); + Summary.EscalationCaseIds = EscalationCaseIds.Array(); + Summary.ArchivedResolvedCaseIds.Sort(); + Summary.PersistentWeakCaseIds.Sort(); + Summary.EscalationCaseIds.Sort(); + Summary.ArchivedResolvedCaseCount = Summary.ArchivedResolvedCaseIds.Num(); + Summary.PersistentWeakCaseCount = Summary.PersistentWeakCaseIds.Num(); + Summary.AverageClosureScore = ClosureScoreAccumulator / static_cast(Summary.SequenceCount); + Summary.bHasEscalationPressure = Summary.EscalatedSequenceCount > 0 || Summary.EscalationCaseIds.Num() > 0; + Summary.bHasFollowUpPressure = Summary.FollowUpReadySequenceCount > 0; + + return Summary; +} + +FHyperTwistTrainingCoachRecommendationHistorySummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachRecommendationHistorySummary( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FString& UserId, + const FString& ReferenceUtc +) +{ + FHyperTwistTrainingCoachRecommendationHistorySummary Summary; + if (UserId.IsEmpty()) + { + return Summary; + } + + Summary.UserId = UserId; + Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc); + + const TArray UserPlans = ListCoachActionPlansForUser(RepositoryState, UserId); + TMap LatestPlanByRootId; + TMap LatestSortUtcByRootId; + TMap> CaseIdsByRootPlanId; + TMap ActionCountByLabel; + TMap ModeCountMap; + TMap PolicyCountMap; + TMap RecommendedCaseCounts; + float FollowUpDepthAccumulator = 0.0f; + FString LatestPlanSortUtc; + + for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans) + { + const FHyperTwistTrainingCoachActionPlan NormalizedPlan = + HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan); + if (!NormalizedPlan.IsStructurallyValid()) + { + continue; + } + + Summary.PlanCount += 1; + FollowUpDepthAccumulator += static_cast(NormalizedPlan.FollowUpDepth); + ModeCountMap.FindOrAdd(NormalizedPlan.SuggestedMode) += 1; + PolicyCountMap.FindOrAdd(NormalizedPlan.SuggestedSelectionPolicy) += 1; + if (!NormalizedPlan.PrimaryActionLabel.IsEmpty()) + { + ActionCountByLabel.FindOrAdd(NormalizedPlan.PrimaryActionLabel) += 1; + } + + const FString RootPlanId = !NormalizedPlan.RootPlanId.IsEmpty() + ? NormalizedPlan.RootPlanId + : NormalizedPlan.PlanId; + TSet& RootCaseIds = CaseIdsByRootPlanId.FindOrAdd(RootPlanId); + TSet UniquePlanCaseIds; + for (const FString& FocusCaseId : NormalizedPlan.FocusCaseIds) + { + if (!FocusCaseId.IsEmpty()) + { + UniquePlanCaseIds.Add(FocusCaseId); + } + } + for (const FHyperTwistTrainingCoachActionPlanEntry& Entry : NormalizedPlan.Entries) + { + if (!Entry.CaseId.IsEmpty()) + { + UniquePlanCaseIds.Add(Entry.CaseId); + } + } + for (const FString& CaseId : UniquePlanCaseIds) + { + RecommendedCaseCounts.FindOrAdd(CaseId) += 1; + RootCaseIds.Add(CaseId); + } + + const FString SortUtc = !NormalizedPlan.CompletedAtUtc.IsEmpty() + ? NormalizedPlan.CompletedAtUtc + : (!NormalizedPlan.StartedAtUtc.IsEmpty() ? NormalizedPlan.StartedAtUtc : NormalizedPlan.CreatedAtUtc); + if (Summary.LatestPlanId.IsEmpty() + || HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, LatestPlanSortUtc) > 0 + || (HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, LatestPlanSortUtc) == 0 + && NormalizedPlan.PlanId > Summary.LatestPlanId)) + { + Summary.LatestPlanId = NormalizedPlan.PlanId; + Summary.FocusDeckId = NormalizedPlan.FocusDeckId; + Summary.FocusMethodSegmentId = NormalizedPlan.FocusMethodSegmentId; + LatestPlanSortUtc = SortUtc; + } + + FHyperTwistTrainingCoachActionPlan* ExistingPlan = LatestPlanByRootId.Find(RootPlanId); + FString* ExistingSortUtc = LatestSortUtcByRootId.Find(RootPlanId); + if (ExistingPlan == nullptr || ExistingSortUtc == nullptr) + { + LatestPlanByRootId.Add(RootPlanId, NormalizedPlan); + LatestSortUtcByRootId.Add(RootPlanId, SortUtc); + } + else + { + const int32 CompareResult = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings( + SortUtc, + *ExistingSortUtc + ); + if (CompareResult > 0 + || (CompareResult == 0 && NormalizedPlan.FollowUpDepth >= ExistingPlan->FollowUpDepth)) + { + *ExistingPlan = NormalizedPlan; + *ExistingSortUtc = SortUtc; + } + } + } + + if (Summary.PlanCount == 0) + { + return FHyperTwistTrainingCoachRecommendationHistorySummary(); + } + + int32 BestModeCount = -1; + for (const TPair& Pair : ModeCountMap) + { + if (Pair.Value > BestModeCount + || (Pair.Value == BestModeCount + && static_cast(Pair.Key) < static_cast(Summary.DominantSuggestedMode))) + { + BestModeCount = Pair.Value; + Summary.DominantSuggestedMode = Pair.Key; + } + } + + int32 BestPolicyCount = -1; + for (const TPair& Pair : PolicyCountMap) + { + if (Pair.Value > BestPolicyCount + || (Pair.Value == BestPolicyCount + && static_cast(Pair.Key) < static_cast(Summary.DominantSelectionPolicy))) + { + BestPolicyCount = Pair.Value; + Summary.DominantSelectionPolicy = Pair.Key; + } + } + + int32 BestActionCount = -1; + for (const TPair& Pair : ActionCountByLabel) + { + if (Pair.Value > BestActionCount + || (Pair.Value == BestActionCount && Pair.Key < Summary.DominantPrimaryActionLabel)) + { + BestActionCount = Pair.Value; + Summary.DominantPrimaryActionLabel = Pair.Key; + } + } + + TSet RecurringCaseIds; + for (const TPair& Pair : RecommendedCaseCounts) + { + if (Pair.Value >= 2) + { + RecurringCaseIds.Add(Pair.Key); + } + } + + TSet ArchivedResolvedCaseIds; + TSet EscalationCaseIds; + float ClosureScoreAccumulator = 0.0f; + int32 FocusPressureScore = -1; + FString FocusPressureSortUtc; + + for (const TPair& Pair : LatestPlanByRootId) + { + const FHyperTwistTrainingCoachActionClosureSummary ClosureSummary = + DeriveCoachActionClosureSummary(RepositoryState, Pair.Value, Summary.ReferenceUtc); + if (!ClosureSummary.IsStructurallyValid()) + { + continue; + } + + Summary.SequenceCount += 1; + Summary.EscalatedSequenceCount += ClosureSummary.bNeedsEscalation ? 1 : 0; + Summary.FollowUpReadySequenceCount += ClosureSummary.bShouldSpawnFollowUp ? 1 : 0; + ClosureScoreAccumulator += ClosureSummary.ClosureScore; + + bool bHasRecurringCaseInSequence = false; + if (const TSet* RootCaseIds = CaseIdsByRootPlanId.Find(Pair.Key)) + { + for (const FString& CaseId : *RootCaseIds) + { + if (RecurringCaseIds.Contains(CaseId)) + { + bHasRecurringCaseInSequence = true; + break; + } + } + } + Summary.RepeatRecommendationSequenceCount += bHasRecurringCaseInSequence ? 1 : 0; + + if (ClosureSummary.bShouldArchiveResolvedCases) + { + for (const FString& CaseId : ClosureSummary.ResolvedCaseIds) + { + if (!CaseId.IsEmpty()) + { + ArchivedResolvedCaseIds.Add(CaseId); + } + } + } + if (ClosureSummary.bNeedsEscalation) + { + for (const FString& CaseId : ClosureSummary.PersistentWeakCaseIds) + { + if (!CaseId.IsEmpty()) + { + EscalationCaseIds.Add(CaseId); + } + } + for (const FString& CaseId : ClosureSummary.UnresolvedCaseIds) + { + if (!CaseId.IsEmpty()) + { + EscalationCaseIds.Add(CaseId); + } + } + } + + const int32 CurrentPressureScore = + (ClosureSummary.bNeedsEscalation ? 300 : 0) + + (ClosureSummary.bShouldSpawnFollowUp ? 200 : 0) + + (bHasRecurringCaseInSequence ? 100 : 0) + + (ClosureSummary.PersistentWeakCaseCount * 10) + + ClosureSummary.UnresolvedCaseCount; + const FString SortUtc = !ClosureSummary.ReferenceUtc.IsEmpty() + ? ClosureSummary.ReferenceUtc + : Summary.ReferenceUtc; + const int32 TimeCompare = FocusPressureSortUtc.IsEmpty() + ? 1 + : HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, FocusPressureSortUtc); + if (CurrentPressureScore > FocusPressureScore + || (CurrentPressureScore == FocusPressureScore && TimeCompare > 0)) + { + FocusPressureScore = CurrentPressureScore; + FocusPressureSortUtc = SortUtc; + Summary.FocusDeckId = ClosureSummary.FocusDeckId; + Summary.FocusMethodSegmentId = ClosureSummary.FocusMethodSegmentId; + } + } + + if (Summary.SequenceCount == 0) + { + return FHyperTwistTrainingCoachRecommendationHistorySummary(); + } + + Summary.RecurringCaseIds = RecurringCaseIds.Array(); + Summary.ArchivedResolvedCaseIds = ArchivedResolvedCaseIds.Array(); + Summary.EscalationCaseIds = EscalationCaseIds.Array(); + Summary.RecurringCaseIds.Sort(); + Summary.ArchivedResolvedCaseIds.Sort(); + Summary.EscalationCaseIds.Sort(); + Summary.RecurringCaseCount = Summary.RecurringCaseIds.Num(); + Summary.ArchivedResolvedCaseCount = Summary.ArchivedResolvedCaseIds.Num(); + Summary.EscalationCaseCount = Summary.EscalationCaseIds.Num(); + Summary.AverageClosureScore = ClosureScoreAccumulator / static_cast(Summary.SequenceCount); + Summary.AverageFollowUpDepth = FollowUpDepthAccumulator / static_cast(Summary.PlanCount); + Summary.bHasRepeatRecommendationPressure = + Summary.RepeatRecommendationSequenceCount > 0 + && (Summary.RecurringCaseCount > 0 + || Summary.FollowUpReadySequenceCount > 0 + || Summary.EscalatedSequenceCount > 0); + Summary.bShouldBiasCoachReviewed = + Summary.EscalatedSequenceCount > 0 + || Summary.RepeatRecommendationSequenceCount >= 2 + || (Summary.bHasRepeatRecommendationPressure + && Summary.DominantSuggestedMode == EHyperTwistTrainingDeliveryMode::CoachReviewed); + + return Summary; +} + +FHyperTwistTrainingCoachCarryForwardPolicySummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachCarryForwardPolicySummary( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FString& UserId, + const FString& ReferenceUtc +) +{ + FHyperTwistTrainingCoachCarryForwardPolicySummary Summary; + if (UserId.IsEmpty()) + { + return Summary; + } + + const FHyperTwistTrainingCoachClosureMemorySummary ClosureMemory = + DeriveCoachClosureMemorySummary(RepositoryState, UserId, ReferenceUtc); + const FHyperTwistTrainingCoachRecommendationHistorySummary RecommendationHistory = + DeriveCoachRecommendationHistorySummary(RepositoryState, UserId, ReferenceUtc); + if (!ClosureMemory.IsStructurallyValid() && !RecommendationHistory.IsStructurallyValid()) + { + return Summary; + } + + Summary.UserId = UserId; + Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc); + Summary.FocusDeckId = !RecommendationHistory.FocusDeckId.IsEmpty() + ? RecommendationHistory.FocusDeckId + : ClosureMemory.FocusDeckId; + Summary.FocusMethodSegmentId = !RecommendationHistory.FocusMethodSegmentId.IsEmpty() + ? RecommendationHistory.FocusMethodSegmentId + : ClosureMemory.FocusMethodSegmentId; + Summary.DominantSuggestedMode = RecommendationHistory.IsStructurallyValid() + ? RecommendationHistory.DominantSuggestedMode + : EHyperTwistTrainingDeliveryMode::Timer; + Summary.DominantSelectionPolicy = RecommendationHistory.IsStructurallyValid() + ? RecommendationHistory.DominantSelectionPolicy + : EHyperTwistTrainingSelectionPolicy::Weighted; + + TSet RecurringCarryForwardCaseIds; + TSet EscalationCarryForwardCaseIds; + TSet PinnedCarryForwardCaseIds; + TSet SuppressedResolvedCaseIds; + + for (const FString& CaseId : RecommendationHistory.RecurringCaseIds) + { + if (!CaseId.IsEmpty()) + { + RecurringCarryForwardCaseIds.Add(CaseId); + PinnedCarryForwardCaseIds.Add(CaseId); + } + } + for (const FString& CaseId : ClosureMemory.EscalationCaseIds) + { + if (!CaseId.IsEmpty()) + { + EscalationCarryForwardCaseIds.Add(CaseId); + PinnedCarryForwardCaseIds.Add(CaseId); + } + } + for (const FString& CaseId : RecommendationHistory.EscalationCaseIds) + { + if (!CaseId.IsEmpty()) + { + EscalationCarryForwardCaseIds.Add(CaseId); + PinnedCarryForwardCaseIds.Add(CaseId); + } + } + for (const FString& CaseId : ClosureMemory.PersistentWeakCaseIds) + { + if (!CaseId.IsEmpty()) + { + PinnedCarryForwardCaseIds.Add(CaseId); + } + } + for (const FString& CaseId : ClosureMemory.ArchivedResolvedCaseIds) + { + if (!CaseId.IsEmpty() && !PinnedCarryForwardCaseIds.Contains(CaseId)) + { + SuppressedResolvedCaseIds.Add(CaseId); + } + } + + Summary.RecurringCarryForwardCaseIds = RecurringCarryForwardCaseIds.Array(); + Summary.EscalationCarryForwardCaseIds = EscalationCarryForwardCaseIds.Array(); + Summary.PinnedCarryForwardCaseIds = PinnedCarryForwardCaseIds.Array(); + Summary.SuppressedResolvedCaseIds = SuppressedResolvedCaseIds.Array(); + Summary.RecurringCarryForwardCaseIds.Sort(); + Summary.EscalationCarryForwardCaseIds.Sort(); + Summary.PinnedCarryForwardCaseIds.Sort(); + Summary.SuppressedResolvedCaseIds.Sort(); + Summary.RecurringCarryForwardCaseCount = Summary.RecurringCarryForwardCaseIds.Num(); + Summary.EscalationCarryForwardCaseCount = Summary.EscalationCarryForwardCaseIds.Num(); + Summary.PinnedCarryForwardCaseCount = Summary.PinnedCarryForwardCaseIds.Num(); + Summary.SuppressedResolvedCaseCount = Summary.SuppressedResolvedCaseIds.Num(); + + Summary.bSuppressResolvedResurfacing = Summary.SuppressedResolvedCaseCount > 0; + Summary.bPinRecurringCases = Summary.PinnedCarryForwardCaseCount > 0; + Summary.bNeedsEscalationHandOff = + ClosureMemory.bHasEscalationPressure || Summary.EscalationCarryForwardCaseCount > 0; + Summary.bBiasCoachReviewed = + Summary.bNeedsEscalationHandOff + || RecommendationHistory.bShouldBiasCoachReviewed + || (Summary.PinnedCarryForwardCaseCount > 0 + && Summary.DominantSuggestedMode == EHyperTwistTrainingDeliveryMode::CoachReviewed); + Summary.bHasActiveCarryForwardPressure = + Summary.bNeedsEscalationHandOff + || Summary.bPinRecurringCases + || RecommendationHistory.bHasRepeatRecommendationPressure + || ClosureMemory.bHasFollowUpPressure; + + Summary.CarryForwardIntensityScore = FMath::Clamp( + (static_cast(Summary.EscalationCarryForwardCaseCount) * 25.0f) + + (static_cast(Summary.RecurringCarryForwardCaseCount) * 12.0f) + + (static_cast(RecommendationHistory.RepeatRecommendationSequenceCount) * 8.0f) + + (ClosureMemory.bHasFollowUpPressure ? 10.0f : 0.0f) + - (static_cast(Summary.SuppressedResolvedCaseCount) * 2.0f), + 0.0f, + 100.0f + ); + + return Summary; +} + +FHyperTwistTrainingCoachArchivePolicySummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachArchivePolicySummary( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FString& UserId, + const FString& ReferenceUtc +) +{ + FHyperTwistTrainingCoachArchivePolicySummary Summary; + if (UserId.IsEmpty()) + { + return Summary; + } + + const FHyperTwistTrainingCoachClosureMemorySummary ClosureMemory = + DeriveCoachClosureMemorySummary(RepositoryState, UserId, ReferenceUtc); + const FHyperTwistTrainingCoachRecommendationHistorySummary RecommendationHistory = + DeriveCoachRecommendationHistorySummary(RepositoryState, UserId, ReferenceUtc); + const FHyperTwistTrainingCoachCarryForwardPolicySummary CarryForwardPolicy = + DeriveCoachCarryForwardPolicySummary(RepositoryState, UserId, ReferenceUtc); + if (!ClosureMemory.IsStructurallyValid() + && !RecommendationHistory.IsStructurallyValid() + && !CarryForwardPolicy.IsStructurallyValid()) + { + return Summary; + } + + Summary.UserId = UserId; + Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc); + Summary.FocusDeckId = CarryForwardPolicy.IsStructurallyValid() + ? CarryForwardPolicy.FocusDeckId + : (!RecommendationHistory.FocusDeckId.IsEmpty() ? RecommendationHistory.FocusDeckId : ClosureMemory.FocusDeckId); + Summary.FocusMethodSegmentId = CarryForwardPolicy.IsStructurallyValid() + ? CarryForwardPolicy.FocusMethodSegmentId + : (!RecommendationHistory.FocusMethodSegmentId.IsEmpty() + ? RecommendationHistory.FocusMethodSegmentId + : ClosureMemory.FocusMethodSegmentId); + Summary.DominantSuggestedMode = CarryForwardPolicy.IsStructurallyValid() + ? CarryForwardPolicy.DominantSuggestedMode + : RecommendationHistory.DominantSuggestedMode; + Summary.DominantSelectionPolicy = CarryForwardPolicy.IsStructurallyValid() + ? CarryForwardPolicy.DominantSelectionPolicy + : RecommendationHistory.DominantSelectionPolicy; + + const TArray UserPlans = ListCoachActionPlansForUser(RepositoryState, UserId); + TMap LatestPlanByRootId; + TMap LatestSortUtcByRootId; + + for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans) + { + const FHyperTwistTrainingCoachActionPlan NormalizedPlan = + HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan); + if (!NormalizedPlan.IsStructurallyValid()) + { + continue; + } + + const FString RootPlanId = !NormalizedPlan.RootPlanId.IsEmpty() + ? NormalizedPlan.RootPlanId + : NormalizedPlan.PlanId; + const FString SortUtc = !NormalizedPlan.CompletedAtUtc.IsEmpty() + ? NormalizedPlan.CompletedAtUtc + : (!NormalizedPlan.StartedAtUtc.IsEmpty() ? NormalizedPlan.StartedAtUtc : NormalizedPlan.CreatedAtUtc); + + FHyperTwistTrainingCoachActionPlan* ExistingPlan = LatestPlanByRootId.Find(RootPlanId); + FString* ExistingSortUtc = LatestSortUtcByRootId.Find(RootPlanId); + if (ExistingPlan == nullptr || ExistingSortUtc == nullptr) + { + LatestPlanByRootId.Add(RootPlanId, NormalizedPlan); + LatestSortUtcByRootId.Add(RootPlanId, SortUtc); + continue; + } + + const int32 CompareResult = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, *ExistingSortUtc); + if (CompareResult > 0 + || (CompareResult == 0 && NormalizedPlan.FollowUpDepth >= ExistingPlan->FollowUpDepth)) + { + *ExistingPlan = NormalizedPlan; + *ExistingSortUtc = SortUtc; + } + } + + TMap ResolvedArchiveHitCounts; + TMap OpenCarryForwardHitCounts; + TMap EscalationHitCounts; + TMap StableArchiveHitCounts; + + for (const TPair& Pair : LatestPlanByRootId) + { + const FHyperTwistTrainingCoachActionClosureSummary ClosureSummary = + DeriveCoachActionClosureSummary(RepositoryState, Pair.Value, Summary.ReferenceUtc); + if (!ClosureSummary.IsStructurallyValid()) + { + continue; + } + + if (ClosureSummary.bShouldArchiveResolvedCases) + { + for (const FString& CaseId : ClosureSummary.ResolvedCaseIds) + { + if (CaseId.IsEmpty()) + { + continue; + } + + ResolvedArchiveHitCounts.FindOrAdd(CaseId) += 1; + if (ClosureSummary.bSequenceClosed + && !ClosureSummary.bNeedsEscalation + && !ClosureSummary.bShouldSpawnFollowUp) + { + StableArchiveHitCounts.FindOrAdd(CaseId) += 1; + } + } + } + + for (const FString& CaseId : ClosureSummary.PersistentWeakCaseIds) + { + if (!CaseId.IsEmpty()) + { + OpenCarryForwardHitCounts.FindOrAdd(CaseId) += 1; + if (ClosureSummary.bNeedsEscalation) + { + EscalationHitCounts.FindOrAdd(CaseId) += 1; + } + } + } + for (const FString& CaseId : ClosureSummary.UnresolvedCaseIds) + { + if (!CaseId.IsEmpty()) + { + OpenCarryForwardHitCounts.FindOrAdd(CaseId) += 1; + if (ClosureSummary.bNeedsEscalation) + { + EscalationHitCounts.FindOrAdd(CaseId) += 1; + } + } + } + } + + TSet RecurringCaseIds; + for (const FString& CaseId : RecommendationHistory.RecurringCaseIds) + { + if (!CaseId.IsEmpty()) + { + RecurringCaseIds.Add(CaseId); + } + } + + TSet LongTermArchivedCaseIds; + TSet ExpiredCarryForwardCaseIds; + TSet RetainedCarryForwardCaseIds; + TSet EscalationRetainedCaseIds; + TSet SuppressedResolvedCaseIds; + TSet StableLongTermArchivedCaseIds; + TSet ProvisionalArchivedCaseIds; + TSet ReopenRiskArchivedCaseIds; + + for (const FString& CaseId : CarryForwardPolicy.SuppressedResolvedCaseIds) + { + if (!CaseId.IsEmpty()) + { + LongTermArchivedCaseIds.Add(CaseId); + SuppressedResolvedCaseIds.Add(CaseId); + + const int32 OpenCarryForwardHits = OpenCarryForwardHitCounts.FindRef(CaseId); + const int32 StableArchiveHits = StableArchiveHitCounts.FindRef(CaseId); + const bool bRecurringCase = RecurringCaseIds.Contains(CaseId); + if (StableArchiveHits > 0 && OpenCarryForwardHits == 0 && !bRecurringCase) + { + StableLongTermArchivedCaseIds.Add(CaseId); + } + else + { + ProvisionalArchivedCaseIds.Add(CaseId); + if (bRecurringCase || OpenCarryForwardHits > 0) + { + ReopenRiskArchivedCaseIds.Add(CaseId); + } + } + } + } + + const bool bStableClosureMemory = + ClosureMemory.AverageClosureScore >= 70.0f + && !ClosureMemory.bHasEscalationPressure + && !ClosureMemory.bHasFollowUpPressure; + + for (const FString& CaseId : CarryForwardPolicy.PinnedCarryForwardCaseIds) + { + if (CaseId.IsEmpty()) + { + continue; + } + + const int32 ResolvedArchiveHits = ResolvedArchiveHitCounts.FindRef(CaseId); + const int32 OpenCarryForwardHits = OpenCarryForwardHitCounts.FindRef(CaseId); + const int32 EscalationHits = EscalationHitCounts.FindRef(CaseId); + const int32 StableArchiveHits = StableArchiveHitCounts.FindRef(CaseId); + const bool bRecurringCase = RecurringCaseIds.Contains(CaseId); + + const bool bCanExpireResolvedCarryForward = + EscalationHits == 0 + && ResolvedArchiveHits > 0 + && OpenCarryForwardHits <= ResolvedArchiveHits + && (StableArchiveHits > 0 + || bStableClosureMemory + || (ResolvedArchiveHits >= 2 && !bRecurringCase) + || (ResolvedArchiveHits > OpenCarryForwardHits + && RecommendationHistory.RepeatRecommendationSequenceCount <= 1)); + + if (bCanExpireResolvedCarryForward) + { + ExpiredCarryForwardCaseIds.Add(CaseId); + LongTermArchivedCaseIds.Add(CaseId); + if (StableArchiveHits > 0 && !bRecurringCase && OpenCarryForwardHits == 0) + { + StableLongTermArchivedCaseIds.Add(CaseId); + } + else + { + ProvisionalArchivedCaseIds.Add(CaseId); + if (bRecurringCase + || RecommendationHistory.bHasRepeatRecommendationPressure + || OpenCarryForwardHits > 0) + { + ReopenRiskArchivedCaseIds.Add(CaseId); + } + } + continue; + } + + RetainedCarryForwardCaseIds.Add(CaseId); + if (EscalationHits > 0) + { + EscalationRetainedCaseIds.Add(CaseId); + } + } + + Summary.LongTermArchivedCaseIds = LongTermArchivedCaseIds.Array(); + Summary.ExpiredCarryForwardCaseIds = ExpiredCarryForwardCaseIds.Array(); + Summary.RetainedCarryForwardCaseIds = RetainedCarryForwardCaseIds.Array(); + Summary.EscalationRetainedCaseIds = EscalationRetainedCaseIds.Array(); + Summary.SuppressedResolvedCaseIds = SuppressedResolvedCaseIds.Array(); + Summary.StableLongTermArchivedCaseIds = StableLongTermArchivedCaseIds.Array(); + Summary.ProvisionalArchivedCaseIds = ProvisionalArchivedCaseIds.Array(); + Summary.ReopenRiskArchivedCaseIds = ReopenRiskArchivedCaseIds.Array(); + Summary.LongTermArchivedCaseIds.Sort(); + Summary.ExpiredCarryForwardCaseIds.Sort(); + Summary.RetainedCarryForwardCaseIds.Sort(); + Summary.EscalationRetainedCaseIds.Sort(); + Summary.SuppressedResolvedCaseIds.Sort(); + Summary.StableLongTermArchivedCaseIds.Sort(); + Summary.ProvisionalArchivedCaseIds.Sort(); + Summary.ReopenRiskArchivedCaseIds.Sort(); + + Summary.LongTermArchivedCaseCount = Summary.LongTermArchivedCaseIds.Num(); + Summary.ExpiredCarryForwardCaseCount = Summary.ExpiredCarryForwardCaseIds.Num(); + Summary.RetainedCarryForwardCaseCount = Summary.RetainedCarryForwardCaseIds.Num(); + Summary.EscalationRetainedCaseCount = Summary.EscalationRetainedCaseIds.Num(); + Summary.SuppressedResolvedCaseCount = Summary.SuppressedResolvedCaseIds.Num(); + Summary.StableLongTermArchivedCaseCount = Summary.StableLongTermArchivedCaseIds.Num(); + Summary.ProvisionalArchivedCaseCount = Summary.ProvisionalArchivedCaseIds.Num(); + Summary.ReopenRiskArchivedCaseCount = Summary.ReopenRiskArchivedCaseIds.Num(); + + Summary.bShouldExpireResolvedCarryForward = Summary.ExpiredCarryForwardCaseCount > 0; + Summary.bShouldRetainEscalationCarryForward = Summary.EscalationRetainedCaseCount > 0; + Summary.ArchiveStabilityScore = FMath::Clamp( + ClosureMemory.AverageClosureScore + + (static_cast(Summary.LongTermArchivedCaseCount) * 5.0f) + + (static_cast(Summary.StableLongTermArchivedCaseCount) * 4.0f) + + (bStableClosureMemory ? 10.0f : 0.0f) + - (static_cast(Summary.ProvisionalArchivedCaseCount) * 3.0f) + - (static_cast(Summary.ReopenRiskArchivedCaseCount) * 8.0f) + - (static_cast(Summary.RetainedCarryForwardCaseCount) * 4.0f) + - (static_cast(Summary.EscalationRetainedCaseCount) * 10.0f) + - (RecommendationHistory.bHasRepeatRecommendationPressure ? 6.0f : 0.0f), + 0.0f, + 100.0f + ); + Summary.ArchiveRetentionTuningScore = FMath::Clamp( + (static_cast(Summary.ReopenRiskArchivedCaseCount) * 18.0f) + + (static_cast(Summary.ProvisionalArchivedCaseCount) * 9.0f) + + (RecommendationHistory.bHasRepeatRecommendationPressure ? 12.0f : 0.0f) + + (Summary.bShouldRetainEscalationCarryForward ? 14.0f : 0.0f) + - (static_cast(Summary.StableLongTermArchivedCaseCount) * 6.0f), + 0.0f, + 100.0f + ); + Summary.bHasStableArchiveMemory = + Summary.LongTermArchivedCaseCount > 0 + && Summary.ArchiveStabilityScore >= 55.0f + && Summary.EscalationRetainedCaseCount == 0; + Summary.bNeedsArchiveRetentionTuning = + Summary.ReopenRiskArchivedCaseCount > 0 + || Summary.ArchiveRetentionTuningScore >= 25.0f + || (Summary.ProvisionalArchivedCaseCount > Summary.StableLongTermArchivedCaseCount + && Summary.LongTermArchivedCaseCount > 0); + Summary.bHasActiveArchivePressure = + Summary.RetainedCarryForwardCaseCount > 0 + || Summary.EscalationRetainedCaseCount > 0 + || Summary.bNeedsArchiveRetentionTuning; + + return Summary; +} + FHyperTwistTrainingCaseRecommendation UHyperTwistTrainingRepositoryLibrary::MakeCaseRecommendation( const FHyperTwistTrainingCase& TrainingCase, const FHyperTwistTrainingCaseLearnerState& LearnerState, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp index 369decc..9ac0b93 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp @@ -276,6 +276,78 @@ FHyperTwistTrainingCoachActionPlanOutcomeSummary UHyperTwistTrainingRuntimeLibra return FHyperTwistTrainingCoachActionPlanOutcomeSummary(); } +FHyperTwistTrainingCoachActionSequenceSummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachActionSequenceSummary( + UObject* WorldContextObject +) +{ + if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) + { + return TrainingSubsystem->GetActiveCoachActionSequenceSummary(); + } + + return FHyperTwistTrainingCoachActionSequenceSummary(); +} + +FHyperTwistTrainingCoachActionClosureSummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachActionClosureSummary( + UObject* WorldContextObject +) +{ + if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) + { + return TrainingSubsystem->GetActiveCoachActionClosureSummary(); + } + + return FHyperTwistTrainingCoachActionClosureSummary(); +} + +FHyperTwistTrainingCoachClosureMemorySummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachClosureMemorySummary( + UObject* WorldContextObject +) +{ + if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) + { + return TrainingSubsystem->GetActiveCoachClosureMemorySummary(); + } + + return FHyperTwistTrainingCoachClosureMemorySummary(); +} + +FHyperTwistTrainingCoachRecommendationHistorySummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachRecommendationHistorySummary( + UObject* WorldContextObject +) +{ + if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) + { + return TrainingSubsystem->GetActiveCoachRecommendationHistorySummary(); + } + + return FHyperTwistTrainingCoachRecommendationHistorySummary(); +} + +FHyperTwistTrainingCoachCarryForwardPolicySummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachCarryForwardPolicySummary( + UObject* WorldContextObject +) +{ + if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) + { + return TrainingSubsystem->GetActiveCoachCarryForwardPolicySummary(); + } + + return FHyperTwistTrainingCoachCarryForwardPolicySummary(); +} + +FHyperTwistTrainingCoachArchivePolicySummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachArchivePolicySummary( + UObject* WorldContextObject +) +{ + if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) + { + return TrainingSubsystem->GetActiveCoachArchivePolicySummary(); + } + + return FHyperTwistTrainingCoachArchivePolicySummary(); +} + FHyperTwistCoachBrief UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachFollowUpBrief(UObject* WorldContextObject) { if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index 9bb0ba3..30b962b 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -123,6 +123,36 @@ FHyperTwistTrainingCoachActionPlanOutcomeSummary UHyperTwistTrainingSubsystem::G return ActiveCoachActionPlanOutcomeSummary; } +FHyperTwistTrainingCoachActionSequenceSummary UHyperTwistTrainingSubsystem::GetActiveCoachActionSequenceSummary() const +{ + return ActiveCoachActionSequenceSummary; +} + +FHyperTwistTrainingCoachActionClosureSummary UHyperTwistTrainingSubsystem::GetActiveCoachActionClosureSummary() const +{ + return ActiveCoachActionClosureSummary; +} + +FHyperTwistTrainingCoachClosureMemorySummary UHyperTwistTrainingSubsystem::GetActiveCoachClosureMemorySummary() const +{ + return ActiveCoachClosureMemorySummary; +} + +FHyperTwistTrainingCoachRecommendationHistorySummary UHyperTwistTrainingSubsystem::GetActiveCoachRecommendationHistorySummary() const +{ + return ActiveCoachRecommendationHistorySummary; +} + +FHyperTwistTrainingCoachCarryForwardPolicySummary UHyperTwistTrainingSubsystem::GetActiveCoachCarryForwardPolicySummary() const +{ + return ActiveCoachCarryForwardPolicySummary; +} + +FHyperTwistTrainingCoachArchivePolicySummary UHyperTwistTrainingSubsystem::GetActiveCoachArchivePolicySummary() const +{ + return ActiveCoachArchivePolicySummary; +} + FHyperTwistCoachBrief UHyperTwistTrainingSubsystem::GetActiveCoachFollowUpBrief() const { return ActiveCoachFollowUpBrief; @@ -1176,6 +1206,12 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() ActiveCoachActionPlan = FHyperTwistTrainingCoachActionPlan(); ActiveCoachActionPlanSummary = FHyperTwistTrainingCoachActionPlanSummary(); ActiveCoachActionPlanOutcomeSummary = FHyperTwistTrainingCoachActionPlanOutcomeSummary(); + ActiveCoachActionSequenceSummary = FHyperTwistTrainingCoachActionSequenceSummary(); + ActiveCoachActionClosureSummary = FHyperTwistTrainingCoachActionClosureSummary(); + ActiveCoachClosureMemorySummary = FHyperTwistTrainingCoachClosureMemorySummary(); + ActiveCoachRecommendationHistorySummary = FHyperTwistTrainingCoachRecommendationHistorySummary(); + ActiveCoachCarryForwardPolicySummary = FHyperTwistTrainingCoachCarryForwardPolicySummary(); + ActiveCoachArchivePolicySummary = FHyperTwistTrainingCoachArchivePolicySummary(); ActiveCoachFollowUpBrief = FHyperTwistCoachBrief(); ActiveLearnerMethodSegments.Reset(); ActiveLearnerPresets.Reset(); @@ -1237,6 +1273,26 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() ActiveRunState.Session.UserId, ActiveLearnerDeckStateSummary.ReferenceUtc ); + ActiveCoachClosureMemorySummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachClosureMemorySummary( + TrainingRepositoryState, + ActiveRunState.Session.UserId, + ActiveLearnerDeckStateSummary.ReferenceUtc + ); + ActiveCoachRecommendationHistorySummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachRecommendationHistorySummary( + TrainingRepositoryState, + ActiveRunState.Session.UserId, + ActiveLearnerDeckStateSummary.ReferenceUtc + ); + ActiveCoachCarryForwardPolicySummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachCarryForwardPolicySummary( + TrainingRepositoryState, + ActiveRunState.Session.UserId, + ActiveLearnerDeckStateSummary.ReferenceUtc + ); + ActiveCoachArchivePolicySummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachArchivePolicySummary( + TrainingRepositoryState, + ActiveRunState.Session.UserId, + ActiveLearnerDeckStateSummary.ReferenceUtc + ); ActiveReplayReviewAnalytics = UHyperTwistReplayReviewLibrary::DeriveReplayReviewAnalytics( ActiveRunState.ReplayPacket ); @@ -1284,6 +1340,20 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() ActiveLearnerDeckStateSummary.ReferenceUtc ) : FHyperTwistTrainingCoachActionPlanOutcomeSummary(); + ActiveCoachActionSequenceSummary = ActiveCoachActionPlan.IsStructurallyValid() + ? UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionSequenceSummary( + TrainingRepositoryState, + ActiveCoachActionPlan, + ActiveLearnerDeckStateSummary.ReferenceUtc + ) + : FHyperTwistTrainingCoachActionSequenceSummary(); + ActiveCoachActionClosureSummary = ActiveCoachActionPlan.IsStructurallyValid() + ? UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionClosureSummary( + TrainingRepositoryState, + ActiveCoachActionPlan, + ActiveLearnerDeckStateSummary.ReferenceUtc + ) + : FHyperTwistTrainingCoachActionClosureSummary(); ActiveCoachSignals = UHyperTwistTrainingCoachLibrary::DeriveCoachSignals( ActiveRunSummary, ActiveDeckScopedStats, @@ -1305,6 +1375,7 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() ActiveRecognitionReplaySummary ); ActiveCoachFollowUpBrief = ActiveCoachActionPlanOutcomeSummary.IsStructurallyValid() + && ActiveCoachActionClosureSummary.bShouldSpawnFollowUp ? UHyperTwistTrainingCoachLibrary::DeriveCoachFollowUpBrief( ActiveCoachBrief, ActiveCoachActionPlan, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistBootstrap/HyperTwistContractLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistBootstrap/HyperTwistContractLibrary.h index 6bd7680..96a5bc0 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistBootstrap/HyperTwistContractLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistBootstrap/HyperTwistContractLibrary.h @@ -185,6 +185,24 @@ public: UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") static FHyperTwistTrainingCoachActionPlanOutcomeSummary MakeSampleTrainingCoachActionPlanOutcomeSummary(); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachActionSequenceSummary MakeSampleTrainingCoachActionSequenceSummary(); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachActionClosureSummary MakeSampleTrainingCoachActionClosureSummary(); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachClosureMemorySummary MakeSampleTrainingCoachClosureMemorySummary(); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachRecommendationHistorySummary MakeSampleTrainingCoachRecommendationHistorySummary(); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachCarryForwardPolicySummary MakeSampleTrainingCoachCarryForwardPolicySummary(); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachArchivePolicySummary MakeSampleTrainingCoachArchivePolicySummary(); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") static FHyperTwistCoachBrief MakeSampleTrainingCoachFollowUpBrief(); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h index 7e0870a..5268212 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -87,6 +87,15 @@ struct FHyperTwistCoachBrief 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") EHyperTwistCoachPriority Priority = EHyperTwistCoachPriority::Informational; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h index 123e9f9..049b3e4 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h @@ -361,6 +361,48 @@ public: const FString& ReferenceUtc ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach") + static FHyperTwistTrainingCoachActionSequenceSummary DeriveCoachActionSequenceSummary( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FHyperTwistTrainingCoachActionPlan& AnchorPlan, + const FString& ReferenceUtc + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach") + static FHyperTwistTrainingCoachActionClosureSummary DeriveCoachActionClosureSummary( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FHyperTwistTrainingCoachActionPlan& AnchorPlan, + const FString& ReferenceUtc + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach") + static FHyperTwistTrainingCoachClosureMemorySummary DeriveCoachClosureMemorySummary( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FString& UserId, + const FString& ReferenceUtc + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach") + static FHyperTwistTrainingCoachRecommendationHistorySummary DeriveCoachRecommendationHistorySummary( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FString& UserId, + const FString& ReferenceUtc + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach") + static FHyperTwistTrainingCoachCarryForwardPolicySummary DeriveCoachCarryForwardPolicySummary( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FString& UserId, + const FString& ReferenceUtc + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach") + static FHyperTwistTrainingCoachArchivePolicySummary DeriveCoachArchivePolicySummary( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FString& UserId, + const FString& ReferenceUtc + ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository") static FHyperTwistTrainingCaseRecommendation MakeCaseRecommendation( const FHyperTwistTrainingCase& TrainingCase, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h index 500bf4f..7832060 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h @@ -90,6 +90,30 @@ public: UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject")) static FHyperTwistTrainingCoachActionPlanOutcomeSummary GetActiveTrainingCoachActionPlanOutcomeSummary(UObject* WorldContextObject); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject")) + static FHyperTwistTrainingCoachActionSequenceSummary GetActiveTrainingCoachActionSequenceSummary(UObject* WorldContextObject); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject")) + static FHyperTwistTrainingCoachActionClosureSummary GetActiveTrainingCoachActionClosureSummary(UObject* WorldContextObject); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject")) + static FHyperTwistTrainingCoachClosureMemorySummary GetActiveTrainingCoachClosureMemorySummary(UObject* WorldContextObject); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject")) + static FHyperTwistTrainingCoachRecommendationHistorySummary GetActiveTrainingCoachRecommendationHistorySummary( + UObject* WorldContextObject + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject")) + static FHyperTwistTrainingCoachCarryForwardPolicySummary GetActiveTrainingCoachCarryForwardPolicySummary( + UObject* WorldContextObject + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject")) + static FHyperTwistTrainingCoachArchivePolicySummary GetActiveTrainingCoachArchivePolicySummary( + UObject* WorldContextObject + ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject")) static FHyperTwistCoachBrief GetActiveTrainingCoachFollowUpBrief(UObject* WorldContextObject); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h index 746e759..036353f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h @@ -83,6 +83,24 @@ public: UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingCoachActionPlanOutcomeSummary GetActiveCoachActionPlanOutcomeSummary() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachActionSequenceSummary GetActiveCoachActionSequenceSummary() const; + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachActionClosureSummary GetActiveCoachActionClosureSummary() const; + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachClosureMemorySummary GetActiveCoachClosureMemorySummary() const; + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachRecommendationHistorySummary GetActiveCoachRecommendationHistorySummary() const; + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachCarryForwardPolicySummary GetActiveCoachCarryForwardPolicySummary() const; + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachArchivePolicySummary GetActiveCoachArchivePolicySummary() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") FHyperTwistCoachBrief GetActiveCoachFollowUpBrief() const; @@ -414,6 +432,24 @@ private: UPROPERTY() FHyperTwistTrainingCoachActionPlanOutcomeSummary ActiveCoachActionPlanOutcomeSummary; + UPROPERTY() + FHyperTwistTrainingCoachActionSequenceSummary ActiveCoachActionSequenceSummary; + + UPROPERTY() + FHyperTwistTrainingCoachActionClosureSummary ActiveCoachActionClosureSummary; + + UPROPERTY() + FHyperTwistTrainingCoachClosureMemorySummary ActiveCoachClosureMemorySummary; + + UPROPERTY() + FHyperTwistTrainingCoachRecommendationHistorySummary ActiveCoachRecommendationHistorySummary; + + UPROPERTY() + FHyperTwistTrainingCoachCarryForwardPolicySummary ActiveCoachCarryForwardPolicySummary; + + UPROPERTY() + FHyperTwistTrainingCoachArchivePolicySummary ActiveCoachArchivePolicySummary; + UPROPERTY() FHyperTwistCoachBrief ActiveCoachFollowUpBrief; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 43da0c1..c85eb7f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -1734,6 +1734,39 @@ struct FHyperTwistTrainingCoachMemorySnapshot UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") TArray PendingReviewPlanIds; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ArchivedResolvedCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray PersistentCoachWeakCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray EscalationCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray RecurringCoachCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray SuppressedResolvedCoachCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray PinnedCarryForwardCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray LongTermArchivedCoachCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ExpiredCarryForwardCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray StableArchivedCoachCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ProvisionalArchivedCoachCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ArchiveReopenRiskCaseIds; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString LastTrainingAtUtc; @@ -1743,6 +1776,31 @@ struct FHyperTwistTrainingCoachMemorySnapshot UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 ActiveTrainingDayStreak = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ClosedCoachSequenceCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 EscalatedCoachSequenceCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 FollowUpReadyCoachSequenceCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RepeatRecommendationSequenceCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingDeliveryMode DominantCoachSuggestedMode = EHyperTwistTrainingDeliveryMode::Timer; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingSelectionPolicy DominantCoachSelectionPolicy = + EHyperTwistTrainingSelectionPolicy::Weighted; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float ArchiveStabilityScore = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float ArchiveRetentionTuningScore = 0.0f; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") bool bNeedsCoachIntervention = false; @@ -1751,6 +1809,18 @@ struct FHyperTwistTrainingCoachMemorySnapshot UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") bool bNeedsCadenceRecovery = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasRepeatRecommendationPressure = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasPinnedCarryForwardPressure = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasStableArchiveMemory = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bNeedsArchiveRetentionTuning = false; }; USTRUCT(BlueprintType) @@ -2714,6 +2784,12 @@ struct FHyperTwistTrainingCoachActionPlan UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString SourceReviewPlanId; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ParentPlanId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RootPlanId; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString FocusDeckId; @@ -2750,6 +2826,9 @@ struct FHyperTwistTrainingCoachActionPlan UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") EHyperTwistTrainingCoachActionPlanState PlanState = EHyperTwistTrainingCoachActionPlanState::Draft; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 FollowUpDepth = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString CreatedAtUtc; @@ -2961,6 +3040,465 @@ struct FHyperTwistTrainingCoachActionPlanOutcomeSummary } }; +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachActionSequenceSummary +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UserId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ReferenceUtc; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RootPlanId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LatestPlanId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusDeckId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusMethodSegmentId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 PlanCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 CarryForwardPlanCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ActivePlanCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 CompletedPlanCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 AbortedPlanCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 MaxFollowUpDepth = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 TotalFocusCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 UnresolvedFollowUpCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float AverageCompletionRate = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float AverageOutcomeScore = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float LatestOutcomeScore = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasOpenPlan = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bNeedsMoreFollowUp = false; + + bool IsStructurallyValid() const + { + return !UserId.IsEmpty() && !RootPlanId.IsEmpty() && PlanCount > 0; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachActionClosureSummary +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UserId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ReferenceUtc; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RootPlanId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LatestPlanId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusDeckId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusMethodSegmentId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ClosureLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingDeliveryMode RecommendedNextMode = EHyperTwistTrainingDeliveryMode::Timer; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ResolvedCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray PersistentWeakCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray UnresolvedCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 TotalAttemptCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RepeatedFailureCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ResolvedCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 PersistentWeakCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 UnresolvedCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float ClosureScore = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSequenceClosed = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bShouldArchiveResolvedCases = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bShouldSpawnFollowUp = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bNeedsEscalation = false; + + bool IsStructurallyValid() const + { + return !UserId.IsEmpty() && !RootPlanId.IsEmpty(); + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachClosureMemorySummary +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UserId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ReferenceUtc; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusRootPlanId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusDeckId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusMethodSegmentId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusClosureLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 SequenceCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ClosedSequenceCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 EscalatedSequenceCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 FollowUpReadySequenceCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ArchivedResolvedCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 PersistentWeakCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float AverageClosureScore = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ArchivedResolvedCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray PersistentWeakCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray EscalationCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasEscalationPressure = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasFollowUpPressure = false; + + bool IsStructurallyValid() const + { + return !UserId.IsEmpty() && SequenceCount > 0; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachRecommendationHistorySummary +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UserId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ReferenceUtc; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LatestPlanId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusDeckId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusMethodSegmentId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DominantPrimaryActionLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingDeliveryMode DominantSuggestedMode = EHyperTwistTrainingDeliveryMode::Timer; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingSelectionPolicy DominantSelectionPolicy = + EHyperTwistTrainingSelectionPolicy::Weighted; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 SequenceCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 PlanCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RepeatRecommendationSequenceCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 EscalatedSequenceCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 FollowUpReadySequenceCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RecurringCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ArchivedResolvedCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 EscalationCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float AverageClosureScore = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float AverageFollowUpDepth = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray RecurringCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ArchivedResolvedCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray EscalationCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasRepeatRecommendationPressure = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bShouldBiasCoachReviewed = false; + + bool IsStructurallyValid() const + { + return !UserId.IsEmpty() && SequenceCount > 0; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachCarryForwardPolicySummary +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UserId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ReferenceUtc; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusDeckId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusMethodSegmentId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingDeliveryMode DominantSuggestedMode = EHyperTwistTrainingDeliveryMode::Timer; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingSelectionPolicy DominantSelectionPolicy = + EHyperTwistTrainingSelectionPolicy::Weighted; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray SuppressedResolvedCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray PinnedCarryForwardCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray RecurringCarryForwardCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray EscalationCarryForwardCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 SuppressedResolvedCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 PinnedCarryForwardCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RecurringCarryForwardCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 EscalationCarryForwardCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float CarryForwardIntensityScore = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSuppressResolvedResurfacing = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bPinRecurringCases = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bBiasCoachReviewed = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bNeedsEscalationHandOff = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasActiveCarryForwardPressure = false; + + bool IsStructurallyValid() const + { + return !UserId.IsEmpty(); + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachArchivePolicySummary +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UserId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ReferenceUtc; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusDeckId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusMethodSegmentId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingDeliveryMode DominantSuggestedMode = EHyperTwistTrainingDeliveryMode::Timer; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingSelectionPolicy DominantSelectionPolicy = + EHyperTwistTrainingSelectionPolicy::Weighted; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray LongTermArchivedCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ExpiredCarryForwardCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray RetainedCarryForwardCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray EscalationRetainedCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray SuppressedResolvedCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray StableLongTermArchivedCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ProvisionalArchivedCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ReopenRiskArchivedCaseIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 LongTermArchivedCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ExpiredCarryForwardCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RetainedCarryForwardCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 EscalationRetainedCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 SuppressedResolvedCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 StableLongTermArchivedCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ProvisionalArchivedCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ReopenRiskArchivedCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float ArchiveStabilityScore = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float ArchiveRetentionTuningScore = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bShouldExpireResolvedCarryForward = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bShouldRetainEscalationCarryForward = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasStableArchiveMemory = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasActiveArchivePressure = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bNeedsArchiveRetentionTuning = false; + + bool IsStructurallyValid() const + { + return !UserId.IsEmpty(); + } +}; + USTRUCT(BlueprintType) struct FHyperTwistTrainingRepositoryState {