Add coach panel projection and queued run bootstrap

This commit is contained in:
axiomlogicnexus 2026-04-27 04:21:28 +02:00
parent bbdee1398e
commit efe45636ec
9 changed files with 590 additions and 0 deletions

View file

@ -29,6 +29,7 @@ void UHyperTwistTrainingPanelWidget::RefreshTrainingState()
UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachSchedulePolicySummary(this);
CachedCoachQueueExecutionSummary =
UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachSessionQueueExecutionSummary(this);
CachedCoachPanelState = UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachPanelState(this);
CachedRepositoryRoundTripVerification =
UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingRepositoryRoundTripVerification(this);
}
@ -132,6 +133,17 @@ FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachFollowUpRu
return CachedRunState;
}
FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartNextQueuedCoachRun(const int32 MaxCases)
{
CachedRunState = UHyperTwistTrainingRuntimeLibrary::StartNextQueuedCoachRun(
this,
DefaultSessionId + TEXT("_queue"),
MaxCases > 0 ? MaxCases : DefaultCoachMaxCases
);
RefreshTrainingState();
return CachedRunState;
}
FHyperTwistTrainingRepositoryRoundTripVerification
UHyperTwistTrainingPanelWidget::VerifyTrainingRepositoryRoundTrip()
{

View file

@ -127,6 +127,18 @@ UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingRepositoryRoundTripVerificat
return FHyperTwistTrainingRepositoryRoundTripVerification();
}
FHyperTwistTrainingCoachPanelState UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachPanelState(
UObject* WorldContextObject
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveCoachPanelState();
}
return FHyperTwistTrainingCoachPanelState();
}
FHyperTwistTrainingDeckHistorySummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingDeckHistorySummary(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
@ -1017,6 +1029,20 @@ FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartCoachFollowU
return FHyperTwistTrainingRunState();
}
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartNextQueuedCoachRun(
UObject* WorldContextObject,
const FString& SessionId,
int32 MaxCases
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->StartNextQueuedCoachRun(SessionId, MaxCases);
}
return FHyperTwistTrainingRunState();
}
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartSampleClassicTrainingRun(
UObject* WorldContextObject,
const FString& UserId,

View file

@ -92,6 +92,7 @@ void AHyperTwistTrainingSessionActor::RefreshCachedTrainingState()
UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachSchedulePolicySummary(this);
CachedCoachQueueExecutionSummary =
UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachSessionQueueExecutionSummary(this);
CachedCoachPanelState = UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachPanelState(this);
CachedRepositoryRoundTripVerification =
UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingRepositoryRoundTripVerification(this);
}
@ -142,6 +143,19 @@ FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachFollowUpT
return CachedRunState;
}
FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartNextQueuedCoachTrainingRun(
const int32 MaxCases
)
{
CachedRunState = UHyperTwistTrainingRuntimeLibrary::StartNextQueuedCoachRun(
this,
AutoStartSessionId + TEXT("_queue"),
MaxCases > 0 ? MaxCases : DefaultCoachMaxCases
);
RefreshCachedTrainingState();
return CachedRunState;
}
FHyperTwistTrainingRepositoryRoundTripVerification
AHyperTwistTrainingSessionActor::VerifyRepositoryRoundTrip()
{

View file

@ -11,6 +11,192 @@
namespace HyperTwistTrainingSubsystemInternal
{
const TCHAR* ActiveCoachSessionQueueId = TEXT("coach_queue_active");
EHyperTwistCoachPriority CoachPriorityFromScore(const float Score)
{
if (Score >= 85.0f)
{
return EHyperTwistCoachPriority::Critical;
}
if (Score >= 65.0f)
{
return EHyperTwistCoachPriority::Elevated;
}
if (Score >= 40.0f)
{
return EHyperTwistCoachPriority::Advisory;
}
return EHyperTwistCoachPriority::Informational;
}
FString ResolveQueueActionLabel(const FString& SourceLabel)
{
if (SourceLabel == TEXT("follow-up-brief"))
{
return TEXT("Start coach follow-up run");
}
if (SourceLabel == TEXT("archive-policy"))
{
return TEXT("Start archive retention run");
}
if (SourceLabel == TEXT("primary-brief"))
{
return TEXT("Start coach recommended run");
}
return TEXT("Start queued coach run");
}
const FHyperTwistTrainingCoachSessionQueueStateEntry* FindNextRunnableCoachQueueEntry(
const FHyperTwistTrainingCoachSessionQueueState& QueueState
)
{
if (!QueueState.IsStructurallyValid())
{
return nullptr;
}
const FHyperTwistTrainingCoachSessionQueueStateEntry* BestEntry = nullptr;
for (const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate : QueueState.Entries)
{
const bool bIsRunnable = Candidate.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Ready
|| (Candidate.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Pending
&& Candidate.bCanStartImmediately);
if (!bIsRunnable)
{
continue;
}
if (BestEntry == nullptr || Candidate.SlotIndex < BestEntry->SlotIndex)
{
BestEntry = &Candidate;
}
}
return BestEntry;
}
FHyperTwistTrainingDeck BuildCoachQueueDeckFromEntry(
const FHyperTwistTrainingCoachSessionQueueStateEntry& QueueEntry,
const FHyperTwistTrainingRunState& ActiveRunState,
int32 MaxCases
)
{
const int32 DesiredCaseCount = MaxCases > 0 ? MaxCases : 3;
FHyperTwistTrainingDeck SourceDeck;
if (!QueueEntry.FocusDeckId.IsEmpty())
{
UHyperTwistTrainingCatalogLibrary::TryFindDeckInCatalog(
UHyperTwistTrainingCatalogLibrary::MakePhase3TrainingCatalog(),
QueueEntry.FocusDeckId,
SourceDeck
);
}
if (!SourceDeck.IsStructurallyValid()
&& ActiveRunState.ActiveDeck.IsStructurallyValid()
&& (QueueEntry.FocusDeckId.IsEmpty()
|| QueueEntry.FocusDeckId == ActiveRunState.ActiveDeck.DeckId))
{
SourceDeck = ActiveRunState.ActiveDeck;
}
if (!SourceDeck.IsStructurallyValid())
{
return FHyperTwistTrainingDeck();
}
TArray<FHyperTwistTrainingCase> SelectedCases;
if (QueueEntry.FocusCaseIds.Num() > 0)
{
for (const FString& FocusCaseId : QueueEntry.FocusCaseIds)
{
const FHyperTwistTrainingCase* MatchingCase = SourceDeck.Cases.FindByPredicate(
[&FocusCaseId](const FHyperTwistTrainingCase& Candidate)
{
return Candidate.CaseId == FocusCaseId;
}
);
if (MatchingCase != nullptr)
{
SelectedCases.Add(*MatchingCase);
if (SelectedCases.Num() >= DesiredCaseCount)
{
break;
}
}
}
}
if (SelectedCases.Num() == 0)
{
for (const FHyperTwistTrainingCase& Candidate : SourceDeck.Cases)
{
SelectedCases.Add(Candidate);
if (SelectedCases.Num() >= DesiredCaseCount)
{
break;
}
}
}
if (SelectedCases.Num() == 0)
{
return FHyperTwistTrainingDeck();
}
FHyperTwistTrainingDeck FocusDeck = SourceDeck;
FocusDeck.Cases = MoveTemp(SelectedCases);
FocusDeck.SelectionPolicy = QueueEntry.SuggestedSelectionPolicy;
if (QueueEntry.SuggestedMode != EHyperTwistTrainingDeliveryMode::Timer
&& !FocusDeck.DeliveryModes.Contains(QueueEntry.SuggestedMode))
{
FocusDeck.DeliveryModes.Insert(QueueEntry.SuggestedMode, 0);
}
if (!QueueEntry.Headline.IsEmpty())
{
FocusDeck.Title = FString::Printf(TEXT("%s - %s"), *SourceDeck.Title, *QueueEntry.Headline);
}
if (!FocusDeck.Tags.Contains(TEXT("coach-queue")))
{
FocusDeck.Tags.Add(TEXT("coach-queue"));
}
if (!QueueEntry.SourceLabel.IsEmpty())
{
FocusDeck.Tags.AddUnique(FString::Printf(TEXT("coach-queue:%s"), *QueueEntry.SourceLabel.ToLower()));
}
if (!QueueEntry.FocusMethodSegmentId.IsEmpty())
{
FocusDeck.SubsetId = QueueEntry.FocusMethodSegmentId;
}
return FocusDeck;
}
FHyperTwistCoachBrief BuildCoachBriefFromQueueEntry(
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
const FHyperTwistTrainingCoachSessionQueueStateEntry& QueueEntry,
const FString& ReferenceUtc
)
{
FHyperTwistCoachBrief Brief;
Brief.UserId = QueueState.UserId;
Brief.ReferenceUtc = ReferenceUtc;
Brief.PriorityScore = QueueEntry.PriorityScore;
Brief.Priority = CoachPriorityFromScore(QueueEntry.PriorityScore);
Brief.FocusLane = QueueEntry.SourceLabel == TEXT("archive-policy")
? EHyperTwistCoachFocusLane::Stability
: EHyperTwistCoachFocusLane::ReviewBacklog;
Brief.Headline = QueueEntry.Headline;
Brief.PrimaryActionLabel = ResolveQueueActionLabel(QueueEntry.SourceLabel);
Brief.SuggestedMode = QueueEntry.SuggestedMode;
Brief.SuggestedSelectionPolicy = QueueEntry.SuggestedSelectionPolicy;
Brief.FocusDeckId = QueueEntry.FocusDeckId;
Brief.FocusMethodSegmentId = QueueEntry.FocusMethodSegmentId;
Brief.FocusCaseIds = QueueEntry.FocusCaseIds;
Brief.bNeedsCoachReview = QueueEntry.SuggestedMode == EHyperTwistTrainingDeliveryMode::CoachReviewed;
Brief.bPreferRecognitionReplayReview =
QueueEntry.SuggestedMode == EHyperTwistTrainingDeliveryMode::RecognitionAssisted;
return Brief;
}
}
bool UHyperTwistTrainingSubsystem::HasActiveRun() const
@ -59,6 +245,131 @@ UHyperTwistTrainingSubsystem::GetActiveRepositoryRoundTripVerification() const
return ActiveRepositoryRoundTripVerification;
}
FHyperTwistTrainingCoachPanelState UHyperTwistTrainingSubsystem::GetActiveCoachPanelState() const
{
FHyperTwistTrainingCoachPanelState PanelState;
PanelState.UserId = !ActiveCoachBrief.UserId.IsEmpty()
? ActiveCoachBrief.UserId
: (!ActiveCoachSessionQueueSummary.UserId.IsEmpty()
? ActiveCoachSessionQueueSummary.UserId
: ActiveRunState.Session.UserId);
PanelState.bHasActiveRun = HasActiveRun();
PanelState.bHasCoachGuidance = !ActiveCoachBrief.UserId.IsEmpty() && !ActiveCoachBrief.Headline.IsEmpty();
PanelState.bHasFollowUpGuidance =
!ActiveCoachFollowUpBrief.UserId.IsEmpty() && !ActiveCoachFollowUpBrief.Headline.IsEmpty();
PanelState.SignalCount = ActiveCoachSignals.Num();
PanelState.bRepositoryVerificationPassed = ActiveRepositoryRoundTripVerification.bDidRoundTripPass;
PanelState.bRepositoryVerificationNeedsAttention = ActiveRepositoryRoundTripVerification.ResolvedPath.IsEmpty()
|| !ActiveRepositoryRoundTripVerification.bDidRoundTripPass;
const FHyperTwistTrainingCoachSessionQueueStateEntry* NextRunnableEntry =
HyperTwistTrainingSubsystemInternal::FindNextRunnableCoachQueueEntry(ActiveCoachSessionQueueState);
PanelState.bHasRunnableQueueEntry = NextRunnableEntry != nullptr;
PanelState.bHasQueueWork = ActiveCoachSessionQueueState.IsStructurallyValid()
? ActiveCoachSessionQueueState.TotalEntryCount > 0
: ActiveCoachSessionQueueSummary.TotalEntryCount > 0;
PanelState.bHasDeferredQueueWork = ActiveCoachSessionQueueState.IsStructurallyValid()
? ActiveCoachSessionQueueState.DeferredEntryCount > 0
: false;
PanelState.TotalQueueEntryCount = ActiveCoachSessionQueueState.IsStructurallyValid()
? ActiveCoachSessionQueueState.TotalEntryCount
: ActiveCoachSessionQueueSummary.TotalEntryCount;
PanelState.ReadyQueueEntryCount = ActiveCoachSessionQueueState.IsStructurallyValid()
? ActiveCoachSessionQueueState.ReadyEntryCount
: (ActiveCoachSessionQueueSummary.bHasImmediateQueue ? 1 : 0);
PanelState.DeferredQueueEntryCount = ActiveCoachSessionQueueState.IsStructurallyValid()
? ActiveCoachSessionQueueState.DeferredEntryCount
: 0;
PanelState.FollowUpQueueEntryCount = ActiveCoachSessionQueueState.IsStructurallyValid()
? ActiveCoachSessionQueueState.Entries.FilterByPredicate(
[](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
{
return Candidate.bRequiresFollowUp;
}
).Num()
: ActiveCoachSessionQueueSummary.FollowUpEntryCount;
const FHyperTwistTrainingDeck RecommendedDeck = BuildActiveCoachRecommendedDeck(3);
const FHyperTwistTrainingDeck FollowUpDeck = BuildActiveCoachFollowUpDeck(3);
PanelState.bCanStartCoachRecommendedRun = RecommendedDeck.IsStructurallyValid();
PanelState.bCanStartCoachFollowUpRun = FollowUpDeck.IsStructurallyValid();
PanelState.bCanStartQueuedRun = NextRunnableEntry != nullptr
&& ((NextRunnableEntry->SourceLabel == TEXT("primary-brief") && PanelState.bCanStartCoachRecommendedRun)
|| (NextRunnableEntry->SourceLabel == TEXT("follow-up-brief") && PanelState.bCanStartCoachFollowUpRun)
|| HyperTwistTrainingSubsystemInternal::BuildCoachQueueDeckFromEntry(
*NextRunnableEntry,
ActiveRunState,
3
).IsStructurallyValid());
if (NextRunnableEntry != nullptr)
{
PanelState.Headline = NextRunnableEntry->Headline;
PanelState.SecondaryLine = HyperTwistTrainingSubsystemInternal::ResolveQueueActionLabel(
NextRunnableEntry->SourceLabel
);
PanelState.PrimaryActionLabel = PanelState.SecondaryLine;
PanelState.NextQueueSourceLabel = NextRunnableEntry->SourceLabel;
PanelState.ActiveQueueEntryId = NextRunnableEntry->EntryId;
PanelState.FocusDeckId = NextRunnableEntry->FocusDeckId;
PanelState.FocusMethodSegmentId = NextRunnableEntry->FocusMethodSegmentId;
PanelState.FocusCaseCount = NextRunnableEntry->FocusCaseIds.Num();
PanelState.SuggestedMode = NextRunnableEntry->SuggestedMode;
PanelState.SuggestedSelectionPolicy = NextRunnableEntry->SuggestedSelectionPolicy;
}
else if (PanelState.bHasCoachGuidance)
{
PanelState.Headline = ActiveCoachBrief.Headline;
PanelState.SecondaryLine = ActiveCoachBrief.PrimaryActionLabel;
PanelState.PrimaryActionLabel = ActiveCoachBrief.PrimaryActionLabel;
PanelState.FocusDeckId = ActiveCoachBrief.FocusDeckId;
PanelState.FocusMethodSegmentId = ActiveCoachBrief.FocusMethodSegmentId;
PanelState.FocusCaseCount = ActiveCoachBrief.FocusCaseIds.Num();
PanelState.SuggestedMode = ActiveCoachBrief.SuggestedMode;
PanelState.SuggestedSelectionPolicy = ActiveCoachBrief.SuggestedSelectionPolicy;
}
else if (PanelState.bHasFollowUpGuidance)
{
PanelState.Headline = ActiveCoachFollowUpBrief.Headline;
PanelState.SecondaryLine = ActiveCoachFollowUpBrief.PrimaryActionLabel;
PanelState.PrimaryActionLabel = ActiveCoachFollowUpBrief.PrimaryActionLabel;
PanelState.FocusDeckId = ActiveCoachFollowUpBrief.FocusDeckId;
PanelState.FocusMethodSegmentId = ActiveCoachFollowUpBrief.FocusMethodSegmentId;
PanelState.FocusCaseCount = ActiveCoachFollowUpBrief.FocusCaseIds.Num();
PanelState.SuggestedMode = ActiveCoachFollowUpBrief.SuggestedMode;
PanelState.SuggestedSelectionPolicy = ActiveCoachFollowUpBrief.SuggestedSelectionPolicy;
}
else if (HasActiveRun())
{
PanelState.Headline = ActiveRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed
? TEXT("Training run is complete")
: TEXT("Training run is active");
PanelState.SecondaryLine = ActiveRunState.ActiveDeck.Title.IsEmpty()
? TEXT("Refresh coaching state to continue.")
: ActiveRunState.ActiveDeck.Title;
}
else
{
PanelState.Headline = TEXT("Training state is stable");
PanelState.SecondaryLine = TEXT("No immediate coach queue work is active.");
}
PanelState.QueueStatusLine = PanelState.TotalQueueEntryCount > 0
? FString::Printf(
TEXT("Queue: %d total, %d ready, %d deferred"),
PanelState.TotalQueueEntryCount,
PanelState.ReadyQueueEntryCount,
PanelState.DeferredQueueEntryCount
)
: TEXT("Queue: no queued coach work");
PanelState.VerificationStatusLine = ActiveRepositoryRoundTripVerification.ResolvedPath.IsEmpty()
? TEXT("Verification: not run yet")
: (ActiveRepositoryRoundTripVerification.bDidRoundTripPass
? TEXT("Verification: repository round-trip passed")
: TEXT("Verification: repository round-trip needs attention"));
return PanelState;
}
FHyperTwistTrainingDeckHistorySummary UHyperTwistTrainingSubsystem::GetActiveDeckHistorySummary() const
{
return ActiveDeckHistorySummary;
@ -1065,6 +1376,99 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachFollowUpRun(
return RunState;
}
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartNextQueuedCoachRun(
const FString& SessionId,
int32 MaxCases
)
{
const FHyperTwistTrainingCoachSessionQueueStateEntry* NextRunnableEntry =
HyperTwistTrainingSubsystemInternal::FindNextRunnableCoachQueueEntry(ActiveCoachSessionQueueState);
if (NextRunnableEntry == nullptr)
{
return FHyperTwistTrainingRunState();
}
if (NextRunnableEntry->SourceLabel == TEXT("primary-brief"))
{
return StartCoachRecommendedRun(SessionId, MaxCases);
}
if (NextRunnableEntry->SourceLabel == TEXT("follow-up-brief"))
{
return StartCoachFollowUpRun(SessionId, MaxCases);
}
const FHyperTwistTrainingDeck QueueDeck = HyperTwistTrainingSubsystemInternal::BuildCoachQueueDeckFromEntry(
*NextRunnableEntry,
ActiveRunState,
MaxCases
);
if (!QueueDeck.IsStructurallyValid())
{
return FHyperTwistTrainingRunState();
}
const FString UserId = !ActiveCoachSessionQueueState.UserId.IsEmpty()
? ActiveCoachSessionQueueState.UserId
: (ActiveRunState.Session.UserId.IsEmpty() ? TEXT("local-user") : ActiveRunState.Session.UserId);
const FHyperTwistTrainingCoachSessionQueueState QueueStateBeforeStart = ActiveCoachSessionQueueState;
const FHyperTwistCoachBrief QueueBrief = HyperTwistTrainingSubsystemInternal::BuildCoachBriefFromQueueEntry(
QueueStateBeforeStart,
*NextRunnableEntry,
QueueStateBeforeStart.ReferenceUtc
);
const FHyperTwistTrainingCoachActionPlan DraftActionPlan =
UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan(
QueueBrief,
FString::Printf(TEXT("coach_queue_%s_%s"), *NextRunnableEntry->EntryId, *SessionId),
FString()
);
FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck(
QueueDeck,
UserId,
SessionId,
NextRunnableEntry->SuggestedMode
);
if (!RunState.IsStructurallyValid())
{
return RunState;
}
if (DraftActionPlan.IsStructurallyValid())
{
ActiveCoachActionPlan = UHyperTwistTrainingRepositoryLibrary::ActivateCoachActionPlan(
DraftActionPlan,
RunState.Session.TrainingSessionId,
RunState.Session.StartedAtUtc
);
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachActionPlan(
TrainingRepositoryState,
ActiveCoachActionPlan
);
}
if (QueueStateBeforeStart.IsStructurallyValid())
{
const FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
UHyperTwistTrainingRepositoryLibrary::StartCoachSessionQueueEntryBySourceLabel(
QueueStateBeforeStart,
NextRunnableEntry->SourceLabel,
RunState.Session.TrainingSessionId,
RunState.Session.StartedAtUtc
);
if (UpdatedQueueState.IsStructurallyValid())
{
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState(
TrainingRepositoryState,
UpdatedQueueState
);
ActiveCoachSessionQueueState = UpdatedQueueState;
}
}
RefreshRepositoryViews();
return RunState;
}
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromDeck(
const FHyperTwistTrainingDeck& Deck,
const FString& UserId,

View file

@ -53,6 +53,9 @@ public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachSessionQueueExecutionSummary CachedCoachQueueExecutionSummary;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachPanelState CachedCoachPanelState;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingDeck CachedCoachRecommendedDeck;
@ -103,6 +106,9 @@ public:
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingRunState StartCoachFollowUpRun(int32 MaxCases);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingRunState StartNextQueuedCoachRun(int32 MaxCases);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
FHyperTwistTrainingRepositoryRoundTripVerification VerifyTrainingRepositoryRoundTrip();
};

View file

@ -50,6 +50,9 @@ public:
UObject* WorldContextObject
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingCoachPanelState GetActiveTrainingCoachPanelState(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingDeckHistorySummary GetActiveTrainingDeckHistorySummary(UObject* WorldContextObject);
@ -414,6 +417,13 @@ public:
int32 MaxCases
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRunState StartNextQueuedCoachRun(
UObject* WorldContextObject,
const FString& SessionId,
int32 MaxCases
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRunState StartSampleClassicTrainingRun(
UObject* WorldContextObject,

View file

@ -55,6 +55,9 @@ public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachSessionQueueExecutionSummary CachedCoachQueueExecutionSummary;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachPanelState CachedCoachPanelState;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingDeck CachedCoachRecommendedDeck;
@ -105,6 +108,9 @@ public:
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingRunState StartCoachFollowUpTrainingRun(int32 MaxCases);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingRunState StartNextQueuedCoachTrainingRun(int32 MaxCases);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
FHyperTwistTrainingRepositoryRoundTripVerification VerifyRepositoryRoundTrip();
};

View file

@ -41,6 +41,9 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingRepositoryRoundTripVerification GetActiveRepositoryRoundTripVerification() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachPanelState GetActiveCoachPanelState() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingDeckHistorySummary GetActiveDeckHistorySummary() const;
@ -333,6 +336,12 @@ public:
int32 MaxCases
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingRunState StartNextQueuedCoachRun(
const FString& SessionId,
int32 MaxCases
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
FHyperTwistTrainingRunState StartTrainingRunFromDeck(
const FHyperTwistTrainingDeck& Deck,

View file

@ -4182,3 +4182,106 @@ struct FHyperTwistTrainingRepositoryRoundTripVerification
return !ResolvedPath.IsEmpty() && bSaveSucceeded && bLoadSucceeded;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachPanelState
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString UserId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SecondaryLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString QueueStatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString VerificationStatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PrimaryActionLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString NextQueueSourceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ActiveQueueEntryId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString FocusDeckId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString FocusMethodSegmentId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDeliveryMode SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SignalCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 FocusCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 TotalQueueEntryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ReadyQueueEntryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 DeferredQueueEntryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 FollowUpQueueEntryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasActiveRun = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasCoachGuidance = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasFollowUpGuidance = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasQueueWork = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasDeferredQueueWork = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasRunnableQueueEntry = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCanStartCoachRecommendedRun = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCanStartCoachFollowUpRun = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCanStartQueuedRun = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bRepositoryVerificationPassed = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bRepositoryVerificationNeedsAttention = true;
bool IsStructurallyValid() const
{
return !Headline.IsEmpty()
|| bHasCoachGuidance
|| bHasFollowUpGuidance
|| bHasQueueWork
|| bHasRunnableQueueEntry
|| bHasActiveRun;
}
};