From 5ec0257f1fec9975c9d216a93c5dcbdb4bba74cf Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Sun, 26 Apr 2026 04:09:24 +0200 Subject: [PATCH] Add coach schedule horizon summaries --- .../HyperTwistContractLibrary.cpp | 9 ++ .../HyperTwistTrainingCoachLibrary.cpp | 94 +++++++++++++++++++ .../HyperTwistTrainingRuntimeLibrary.cpp | 12 +++ .../HyperTwistTrainingSubsystem.cpp | 12 +++ .../HyperTwistContractLibrary.h | 3 + .../HyperTwistTrainingCoachLibrary.h | 81 ++++++++++++++++ .../HyperTwistTrainingRuntimeLibrary.h | 5 + .../HyperTwistTrainingSubsystem.h | 6 ++ 8 files changed, 222 insertions(+) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp index 6aff754..308b2d4 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp @@ -1330,6 +1330,15 @@ FHyperTwistTrainingCoachSessionQueueState UHyperTwistContractLibrary::MakeSample ); } +FHyperTwistCoachScheduleHorizonSummary UHyperTwistContractLibrary::MakeSampleTrainingCoachScheduleHorizonSummary() +{ + return UHyperTwistTrainingCoachLibrary::DeriveCoachScheduleHorizonSummary( + MakeSampleTrainingCoachSessionQueueState(), + MakeSampleTrainingCoachArchivePolicySummary(), + TEXT("2026-04-28T12:12:00Z") + ); +} + bool UHyperTwistContractLibrary::IsPuzzleStateStructurallyValid(const FHyperTwistPuzzleState& State) { return State.IsStructurallyValid(); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index 6149898..48fa5b6 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -714,3 +714,97 @@ FHyperTwistCoachSessionQueueSummary UHyperTwistTrainingCoachLibrary::DeriveCoach return Summary; } + +FHyperTwistCoachScheduleHorizonSummary UHyperTwistTrainingCoachLibrary::DeriveCoachScheduleHorizonSummary( + const FHyperTwistTrainingCoachSessionQueueState& QueueState, + const FHyperTwistTrainingCoachArchivePolicySummary& ArchivePolicySummary, + const FString& ReferenceUtc +) +{ + FHyperTwistCoachScheduleHorizonSummary Summary; + if (!QueueState.IsStructurallyValid()) + { + return Summary; + } + + Summary.UserId = QueueState.UserId; + Summary.ReferenceUtc = !ReferenceUtc.IsEmpty() + ? ReferenceUtc + : (!QueueState.LastUpdatedAtUtc.IsEmpty() ? QueueState.LastUpdatedAtUtc : QueueState.ReferenceUtc); + Summary.QueueId = QueueState.QueueId; + Summary.FocusDeckId = QueueState.FocusDeckId; + Summary.FocusMethodSegmentId = QueueState.FocusMethodSegmentId; + Summary.ActiveEntryId = QueueState.ActiveEntryId; + + for (const FHyperTwistTrainingCoachSessionQueueStateEntry& Entry : QueueState.Entries) + { + if (!Entry.IsStructurallyValid()) + { + continue; + } + + const bool bIsImmediateEntry = + Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Ready + || Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress; + const bool bIsDeferredEntry = Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Pending; + + Summary.ImmediateEntryCount += bIsImmediateEntry ? 1 : 0; + Summary.DeferredEntryCount += bIsDeferredEntry ? 1 : 0; + Summary.ArchiveAwareEntryCount += Entry.bRequiresArchiveRetentionTuning ? 1 : 0; + Summary.FollowUpEntryCount += Entry.bRequiresFollowUp ? 1 : 0; + Summary.PinnedEntryCount += Entry.bPinned ? 1 : 0; + + if (Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress) + { + Summary.bHasInProgressEntry = true; + } + + if (bIsImmediateEntry && Summary.NextReadyEntryId.IsEmpty()) + { + Summary.NextReadyEntryId = Entry.EntryId; + } + if (bIsDeferredEntry && Summary.NextDeferredEntryId.IsEmpty()) + { + Summary.NextDeferredEntryId = Entry.EntryId; + } + if (Entry.bRequiresArchiveRetentionTuning && Summary.NextArchiveEntryId.IsEmpty()) + { + Summary.NextArchiveEntryId = Entry.EntryId; + } + if (Entry.bRequiresFollowUp && Summary.NextFollowUpEntryId.IsEmpty()) + { + Summary.NextFollowUpEntryId = Entry.EntryId; + } + } + + Summary.bHasImmediateWindow = Summary.ImmediateEntryCount > 0; + Summary.bHasDeferredWindow = Summary.DeferredEntryCount > 0; + Summary.bNeedsArchiveWindow = QueueState.bNeedsArchiveAwareSequencing + && (Summary.ArchiveAwareEntryCount > 0 || ArchivePolicySummary.bNeedsArchiveRetentionTuning); + Summary.bHasBlockedFollowUp = Summary.FollowUpEntryCount > 0 + && Summary.NextFollowUpEntryId != Summary.NextReadyEntryId + && (Summary.bHasImmediateWindow || Summary.bNeedsArchiveWindow); + + if (Summary.bHasInProgressEntry || Summary.bHasImmediateWindow) + { + Summary.CurrentHorizonLabel = TEXT("immediate"); + } + else if (Summary.bNeedsArchiveWindow) + { + Summary.CurrentHorizonLabel = TEXT("archive-aware"); + } + else if (!Summary.NextFollowUpEntryId.IsEmpty()) + { + Summary.CurrentHorizonLabel = TEXT("follow-up"); + } + else if (!Summary.NextDeferredEntryId.IsEmpty()) + { + Summary.CurrentHorizonLabel = TEXT("deferred"); + } + else + { + Summary.CurrentHorizonLabel = TEXT("idle"); + } + + return Summary; +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp index 923d749..b66c4a7 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp @@ -381,6 +381,18 @@ FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRuntimeLibrary::Get return FHyperTwistTrainingCoachSessionQueueState(); } +FHyperTwistCoachScheduleHorizonSummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachScheduleHorizonSummary( + UObject* WorldContextObject +) +{ + if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) + { + return TrainingSubsystem->GetActiveCoachScheduleHorizonSummary(); + } + + return FHyperTwistCoachScheduleHorizonSummary(); +} + TArray UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingLearnerMethodSegments(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 71012d3..4a4c464 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -173,6 +173,11 @@ FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingSubsystem::GetActiv return ActiveCoachSessionQueueState; } +FHyperTwistCoachScheduleHorizonSummary UHyperTwistTrainingSubsystem::GetActiveCoachScheduleHorizonSummary() const +{ + return ActiveCoachScheduleHorizonSummary; +} + TArray UHyperTwistTrainingSubsystem::GetActiveLearnerMethodSegments() const { return ActiveLearnerMethodSegments; @@ -1315,6 +1320,7 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() ActiveCoachFollowUpBrief = FHyperTwistCoachBrief(); ActiveCoachSessionQueueSummary = FHyperTwistCoachSessionQueueSummary(); ActiveCoachSessionQueueState = FHyperTwistTrainingCoachSessionQueueState(); + ActiveCoachScheduleHorizonSummary = FHyperTwistCoachScheduleHorizonSummary(); ActiveLearnerMethodSegments.Reset(); ActiveLearnerPresets.Reset(); ActiveTrainingSessionTemplates.Reset(); @@ -1517,10 +1523,16 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() ActiveCoachSessionQueueState ); } + ActiveCoachScheduleHorizonSummary = UHyperTwistTrainingCoachLibrary::DeriveCoachScheduleHorizonSummary( + ActiveCoachSessionQueueState, + ActiveCoachArchivePolicySummary, + ActiveLearnerDeckStateSummary.ReferenceUtc + ); } else { ActiveCoachSessionQueueState = FHyperTwistTrainingCoachSessionQueueState(); + ActiveCoachScheduleHorizonSummary = FHyperTwistCoachScheduleHorizonSummary(); } } diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistBootstrap/HyperTwistContractLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistBootstrap/HyperTwistContractLibrary.h index ebe7d76..3af4e9e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistBootstrap/HyperTwistContractLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistBootstrap/HyperTwistContractLibrary.h @@ -212,6 +212,9 @@ public: UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") static FHyperTwistTrainingCoachSessionQueueState MakeSampleTrainingCoachSessionQueueState(); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistCoachScheduleHorizonSummary MakeSampleTrainingCoachScheduleHorizonSummary(); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Validation") static bool IsPuzzleStateStructurallyValid(const FHyperTwistPuzzleState& State); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h index 184ecbb..5a05233 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -237,6 +237,80 @@ struct FHyperTwistCoachSessionQueueSummary } }; +USTRUCT(BlueprintType) +struct FHyperTwistCoachScheduleHorizonSummary +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UserId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ReferenceUtc; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString QueueId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusDeckId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusMethodSegmentId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActiveEntryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString NextReadyEntryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString NextDeferredEntryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString NextArchiveEntryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString NextFollowUpEntryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentHorizonLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ImmediateEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 DeferredEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ArchiveAwareEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 FollowUpEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 PinnedEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasImmediateWindow = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasDeferredWindow = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bNeedsArchiveWindow = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasBlockedFollowUp = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasInProgressEntry = false; + + bool IsStructurallyValid() const + { + return !UserId.IsEmpty() && !QueueId.IsEmpty(); + } +}; + UCLASS() class UNREALHYPERTWIST_API UHyperTwistTrainingCoachLibrary : public UBlueprintFunctionLibrary { @@ -281,4 +355,11 @@ public: const FHyperTwistTrainingCoachArchivePolicySummary& ArchivePolicySummary, const FHyperTwistTrainingReviewProgramSummary& ReviewProgramSummary ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistCoachScheduleHorizonSummary DeriveCoachScheduleHorizonSummary( + const FHyperTwistTrainingCoachSessionQueueState& QueueState, + const FHyperTwistTrainingCoachArchivePolicySummary& ArchivePolicySummary, + const FString& ReferenceUtc + ); }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h index 914c62d..d70111d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h @@ -125,6 +125,11 @@ public: UObject* WorldContextObject ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject")) + static FHyperTwistCoachScheduleHorizonSummary GetActiveTrainingCoachScheduleHorizonSummary( + UObject* WorldContextObject + ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject")) static TArray GetActiveTrainingLearnerMethodSegments(UObject* WorldContextObject); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h index 91255ec..52d316e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h @@ -110,6 +110,9 @@ public: UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingCoachSessionQueueState GetActiveCoachSessionQueueState() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistCoachScheduleHorizonSummary GetActiveCoachScheduleHorizonSummary() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training") TArray GetActiveLearnerMethodSegments() const; @@ -475,6 +478,9 @@ private: UPROPERTY() FHyperTwistTrainingCoachSessionQueueState ActiveCoachSessionQueueState; + UPROPERTY() + FHyperTwistCoachScheduleHorizonSummary ActiveCoachScheduleHorizonSummary; + UPROPERTY() TArray ActiveLearnerMethodSegments;