Add coach scheduling policy controls
This commit is contained in:
parent
5ec0257f1f
commit
1a309c862c
11 changed files with 564 additions and 26 deletions
|
|
@ -1323,11 +1323,29 @@ FHyperTwistCoachSessionQueueSummary UHyperTwistContractLibrary::MakeSampleTraini
|
|||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistContractLibrary::MakeSampleTrainingCoachSessionQueueState()
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::BuildCoachSessionQueueState(
|
||||
FHyperTwistTrainingCoachSessionQueueState QueueState = UHyperTwistTrainingRepositoryLibrary::BuildCoachSessionQueueState(
|
||||
MakeSampleTrainingCoachSessionQueueSummary(),
|
||||
TEXT("coach_queue_active"),
|
||||
TEXT("2026-04-28T12:12:00Z")
|
||||
);
|
||||
|
||||
QueueState = UHyperTwistTrainingRepositoryLibrary::DeferCoachSessionQueueEntry(
|
||||
QueueState,
|
||||
TEXT("coach_queue_follow_up"),
|
||||
TEXT("2026-04-28T18:00:00Z"),
|
||||
TEXT("follow-up-held-behind-primary"),
|
||||
false,
|
||||
TEXT("2026-04-28T12:13:00Z")
|
||||
);
|
||||
QueueState = UHyperTwistTrainingRepositoryLibrary::DeferCoachSessionQueueEntry(
|
||||
QueueState,
|
||||
TEXT("coach_queue_archive"),
|
||||
TEXT("2026-04-29T09:00:00Z"),
|
||||
TEXT("archive-retention-window"),
|
||||
true,
|
||||
TEXT("2026-04-28T12:14:00Z")
|
||||
);
|
||||
return QueueState;
|
||||
}
|
||||
|
||||
FHyperTwistCoachScheduleHorizonSummary UHyperTwistContractLibrary::MakeSampleTrainingCoachScheduleHorizonSummary()
|
||||
|
|
@ -1339,6 +1357,16 @@ FHyperTwistCoachScheduleHorizonSummary UHyperTwistContractLibrary::MakeSampleTra
|
|||
);
|
||||
}
|
||||
|
||||
FHyperTwistCoachSchedulePolicySummary UHyperTwistContractLibrary::MakeSampleTrainingCoachSchedulePolicySummary()
|
||||
{
|
||||
return UHyperTwistTrainingCoachLibrary::DeriveCoachSchedulePolicySummary(
|
||||
MakeSampleTrainingCoachSessionQueueState(),
|
||||
MakeSampleTrainingCoachScheduleHorizonSummary(),
|
||||
MakeSampleTrainingCoachArchivePolicySummary(),
|
||||
TEXT("2026-04-28T12:30:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistContractLibrary::IsPuzzleStateStructurallyValid(const FHyperTwistPuzzleState& State)
|
||||
{
|
||||
return State.IsStructurallyValid();
|
||||
|
|
|
|||
|
|
@ -746,7 +746,9 @@ FHyperTwistCoachScheduleHorizonSummary UHyperTwistTrainingCoachLibrary::DeriveCo
|
|||
const bool bIsImmediateEntry =
|
||||
Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Ready
|
||||
|| Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress;
|
||||
const bool bIsDeferredEntry = Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
|
||||
const bool bIsDeferredEntry =
|
||||
Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Deferred
|
||||
|| Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
|
||||
|
||||
Summary.ImmediateEntryCount += bIsImmediateEntry ? 1 : 0;
|
||||
Summary.DeferredEntryCount += bIsDeferredEntry ? 1 : 0;
|
||||
|
|
@ -808,3 +810,124 @@ FHyperTwistCoachScheduleHorizonSummary UHyperTwistTrainingCoachLibrary::DeriveCo
|
|||
|
||||
return Summary;
|
||||
}
|
||||
|
||||
FHyperTwistCoachSchedulePolicySummary UHyperTwistTrainingCoachLibrary::DeriveCoachSchedulePolicySummary(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FHyperTwistCoachScheduleHorizonSummary& HorizonSummary,
|
||||
const FHyperTwistTrainingCoachArchivePolicySummary& ArchivePolicySummary,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistCoachSchedulePolicySummary 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.NextImmediateEntryId = HorizonSummary.NextReadyEntryId;
|
||||
|
||||
TMap<FString, int32> DeferralReasonCounts;
|
||||
for (const FHyperTwistTrainingCoachSessionQueueStateEntry& Entry : QueueState.Entries)
|
||||
{
|
||||
if (!Entry.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (Entry.EntryState)
|
||||
{
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::Ready:
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::InProgress:
|
||||
Summary.ImmediateEntryCount += 1;
|
||||
break;
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::Pending:
|
||||
Summary.PendingEntryCount += 1;
|
||||
if (Summary.NextPendingEntryId.IsEmpty())
|
||||
{
|
||||
Summary.NextPendingEntryId = Entry.EntryId;
|
||||
}
|
||||
break;
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::Deferred:
|
||||
Summary.DeferredEntryCount += 1;
|
||||
if (Summary.NextDeferredEntryId.IsEmpty())
|
||||
{
|
||||
Summary.NextDeferredEntryId = Entry.EntryId;
|
||||
}
|
||||
if (Entry.bRequiresArchiveRetentionTuning)
|
||||
{
|
||||
Summary.ArchiveAwareDeferredEntryCount += 1;
|
||||
}
|
||||
if (Entry.bRequiresFollowUp)
|
||||
{
|
||||
Summary.BlockedFollowUpDeferredEntryCount += 1;
|
||||
}
|
||||
if (Summary.EarliestDeferredUntilUtc.IsEmpty()
|
||||
|| (!Entry.DeferredUntilUtc.IsEmpty() && Entry.DeferredUntilUtc < Summary.EarliestDeferredUntilUtc))
|
||||
{
|
||||
Summary.EarliestDeferredUntilUtc = Entry.DeferredUntilUtc;
|
||||
}
|
||||
if (!Entry.DeferralReasonLabel.IsEmpty())
|
||||
{
|
||||
DeferralReasonCounts.FindOrAdd(Entry.DeferralReasonLabel) += 1;
|
||||
}
|
||||
if (!Entry.DeferredUntilUtc.IsEmpty() && Entry.DeferredUntilUtc <= Summary.ReferenceUtc)
|
||||
{
|
||||
Summary.bHasExpiredDeferrals = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (Entry.bWasManuallyRescheduled)
|
||||
{
|
||||
Summary.ManualRescheduleEntryCount += 1;
|
||||
if (Summary.NextManualRescheduleEntryId.IsEmpty()
|
||||
&& (Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Deferred
|
||||
|| Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Pending))
|
||||
{
|
||||
Summary.NextManualRescheduleEntryId = Entry.EntryId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 DominantReasonCount = 0;
|
||||
for (const TPair<FString, int32>& Pair : DeferralReasonCounts)
|
||||
{
|
||||
if (Pair.Value > DominantReasonCount
|
||||
|| (Pair.Value == DominantReasonCount
|
||||
&& (Summary.DominantDeferralReasonLabel.IsEmpty()
|
||||
|| Pair.Key < Summary.DominantDeferralReasonLabel)))
|
||||
{
|
||||
DominantReasonCount = Pair.Value;
|
||||
Summary.DominantDeferralReasonLabel = Pair.Key;
|
||||
}
|
||||
}
|
||||
|
||||
Summary.bAllowsManualReschedule = QueueState.IsStructurallyValid()
|
||||
&& !HorizonSummary.bHasInProgressEntry;
|
||||
Summary.bHasArchiveAwareDeferrals = Summary.ArchiveAwareDeferredEntryCount > 0
|
||||
|| (ArchivePolicySummary.bNeedsArchiveRetentionTuning && Summary.DeferredEntryCount > 0);
|
||||
Summary.bHasBlockedFollowUpDeferrals = Summary.BlockedFollowUpDeferredEntryCount > 0;
|
||||
Summary.bNeedsManualRescheduleReview = Summary.bHasExpiredDeferrals
|
||||
|| Summary.ManualRescheduleEntryCount >= 2
|
||||
|| Summary.bHasBlockedFollowUpDeferrals;
|
||||
Summary.ReschedulePressureScore = FMath::Clamp(
|
||||
static_cast<float>(Summary.DeferredEntryCount) * 8.0f
|
||||
+ static_cast<float>(Summary.ManualRescheduleEntryCount) * 10.0f
|
||||
+ static_cast<float>(Summary.ArchiveAwareDeferredEntryCount) * 12.0f
|
||||
+ static_cast<float>(Summary.BlockedFollowUpDeferredEntryCount) * 12.0f
|
||||
+ (Summary.bHasExpiredDeferrals ? 15.0f : 0.0f),
|
||||
0.0f,
|
||||
100.0f
|
||||
);
|
||||
|
||||
return Summary;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -487,6 +487,18 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
|| EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Expired;
|
||||
}
|
||||
|
||||
bool IsUtcDueOrPast(const FString& CandidateUtc, const FString& ReferenceUtc)
|
||||
{
|
||||
FDateTime CandidateTime;
|
||||
FDateTime ReferenceTime;
|
||||
if (!TryParseUtc(CandidateUtc, CandidateTime) || !TryParseUtc(ReferenceUtc, ReferenceTime))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return CandidateTime <= ReferenceTime;
|
||||
}
|
||||
|
||||
FString MakeCoachSessionQueueEntryFingerprint(
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry& Entry
|
||||
)
|
||||
|
|
@ -531,9 +543,20 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
{
|
||||
NormalizedEntry.WorkFingerprint = MakeCoachSessionQueueEntryFingerprint(NormalizedEntry);
|
||||
}
|
||||
NormalizedEntry.RescheduleCount = FMath::Max(0, NormalizedEntry.RescheduleCount);
|
||||
NormalizedEntry.bPinned = NormalizedEntry.bPinned
|
||||
|| NormalizedEntry.bRequiresFollowUp
|
||||
|| NormalizedEntry.bRequiresArchiveRetentionTuning;
|
||||
if (NormalizedEntry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Deferred
|
||||
&& NormalizedEntry.DeferredUntilUtc.IsEmpty())
|
||||
{
|
||||
NormalizedEntry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
|
||||
}
|
||||
if (NormalizedEntry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Deferred
|
||||
&& IsUtcDueOrPast(NormalizedEntry.DeferredUntilUtc, NormalizedState.ReferenceUtc))
|
||||
{
|
||||
NormalizedEntry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
|
||||
}
|
||||
if (NormalizedEntry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Ready
|
||||
|| NormalizedEntry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress)
|
||||
{
|
||||
|
|
@ -541,7 +564,8 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
? NormalizedEntry.ScheduledAtUtc
|
||||
: NormalizedState.ReferenceUtc;
|
||||
}
|
||||
if (IsTerminalCoachQueueEntryState(NormalizedEntry.EntryState))
|
||||
if (NormalizedEntry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Deferred
|
||||
|| IsTerminalCoachQueueEntryState(NormalizedEntry.EntryState))
|
||||
{
|
||||
NormalizedEntry.bCanStartImmediately = false;
|
||||
}
|
||||
|
|
@ -580,6 +604,7 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
NormalizedState.ActiveEntryId.Reset();
|
||||
NormalizedState.ActiveTrainingSessionId.Reset();
|
||||
NormalizedState.PendingEntryCount = 0;
|
||||
NormalizedState.DeferredEntryCount = 0;
|
||||
NormalizedState.ReadyEntryCount = 0;
|
||||
NormalizedState.InProgressEntryCount = 0;
|
||||
NormalizedState.CompletedEntryCount = 0;
|
||||
|
|
@ -632,6 +657,9 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
case EHyperTwistTrainingCoachSessionQueueEntryState::Pending:
|
||||
NormalizedState.PendingEntryCount += 1;
|
||||
break;
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::Deferred:
|
||||
NormalizedState.DeferredEntryCount += 1;
|
||||
break;
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::Ready:
|
||||
NormalizedState.ReadyEntryCount += 1;
|
||||
break;
|
||||
|
|
@ -656,30 +684,9 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
{
|
||||
for (FHyperTwistTrainingCoachSessionQueueStateEntry& Entry : NormalizedEntries)
|
||||
{
|
||||
if (!IsTerminalCoachQueueEntryState(Entry.EntryState))
|
||||
if (Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Pending)
|
||||
{
|
||||
if (Entry.EntryState != EHyperTwistTrainingCoachSessionQueueEntryState::Pending)
|
||||
{
|
||||
switch (Entry.EntryState)
|
||||
{
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::Completed:
|
||||
NormalizedState.CompletedEntryCount -= 1;
|
||||
break;
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::Skipped:
|
||||
NormalizedState.SkippedEntryCount -= 1;
|
||||
break;
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::Expired:
|
||||
NormalizedState.ExpiredEntryCount -= 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NormalizedState.PendingEntryCount -= 1;
|
||||
}
|
||||
|
||||
NormalizedState.PendingEntryCount -= 1;
|
||||
Entry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Ready;
|
||||
Entry.ScheduledAtUtc = !Entry.ScheduledAtUtc.IsEmpty() ? Entry.ScheduledAtUtc : NormalizedState.ReferenceUtc;
|
||||
Entry.bCanStartImmediately = true;
|
||||
|
|
@ -5398,10 +5405,15 @@ FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::
|
|||
QueueEntry.EntryState = ExistingEntry->EntryState;
|
||||
QueueEntry.SourceTrainingSessionId = ExistingEntry->SourceTrainingSessionId;
|
||||
QueueEntry.ScheduledAtUtc = ExistingEntry->ScheduledAtUtc;
|
||||
QueueEntry.DeferredUntilUtc = ExistingEntry->DeferredUntilUtc;
|
||||
QueueEntry.DeferredAtUtc = ExistingEntry->DeferredAtUtc;
|
||||
QueueEntry.DeferralReasonLabel = ExistingEntry->DeferralReasonLabel;
|
||||
QueueEntry.StartedAtUtc = ExistingEntry->StartedAtUtc;
|
||||
QueueEntry.CompletedAtUtc = ExistingEntry->CompletedAtUtc;
|
||||
QueueEntry.RescheduleCount = ExistingEntry->RescheduleCount;
|
||||
QueueEntry.bPinned = QueueEntry.bPinned || ExistingEntry->bPinned;
|
||||
QueueEntry.bCanStartImmediately = ExistingEntry->bCanStartImmediately;
|
||||
QueueEntry.bWasManuallyRescheduled = ExistingEntry->bWasManuallyRescheduled;
|
||||
}
|
||||
|
||||
ResolvedQueueState.LastUpdatedAtUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
|
||||
|
|
@ -5431,6 +5443,7 @@ FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::
|
|||
{
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry& Entry = UpdatedQueueState.Entries[EntryIndex];
|
||||
if (Entry.SourceLabel == SourceLabel
|
||||
&& Entry.EntryState != EHyperTwistTrainingCoachSessionQueueEntryState::Deferred
|
||||
&& !HyperTwistTrainingRepositoryLibraryInternal::IsTerminalCoachQueueEntryState(Entry.EntryState))
|
||||
{
|
||||
ActivatedIndex = EntryIndex;
|
||||
|
|
@ -5460,6 +5473,7 @@ FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::
|
|||
ActiveEntry.ScheduledAtUtc = !ActiveEntry.ScheduledAtUtc.IsEmpty()
|
||||
? ActiveEntry.ScheduledAtUtc
|
||||
: ResolvedStartedAtUtc;
|
||||
ActiveEntry.DeferredUntilUtc.Reset();
|
||||
ActiveEntry.StartedAtUtc = ResolvedStartedAtUtc;
|
||||
ActiveEntry.bCanStartImmediately = false;
|
||||
UpdatedQueueState.LastUpdatedAtUtc = ResolvedStartedAtUtc;
|
||||
|
|
@ -5513,6 +5527,98 @@ FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::
|
|||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(UpdatedQueueState);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::DeferCoachSessionQueueEntry(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FString& EntryId,
|
||||
const FString& DeferredUntilUtc,
|
||||
const FString& DeferralReasonLabel,
|
||||
bool bManualReschedule,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
|
||||
if (!UpdatedQueueState.IsStructurallyValid() || EntryId.IsEmpty() || DeferredUntilUtc.IsEmpty())
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
const FString ResolvedReferenceUtc =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
|
||||
ReferenceUtc.IsEmpty() ? UpdatedQueueState.ReferenceUtc : ReferenceUtc
|
||||
);
|
||||
FHyperTwistTrainingCoachSessionQueueStateEntry* EntryToDefer =
|
||||
UpdatedQueueState.Entries.FindByPredicate(
|
||||
[&EntryId](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
|
||||
{
|
||||
return Candidate.EntryId == EntryId
|
||||
&& Candidate.EntryState != EHyperTwistTrainingCoachSessionQueueEntryState::InProgress
|
||||
&& !HyperTwistTrainingRepositoryLibraryInternal::IsTerminalCoachQueueEntryState(Candidate.EntryState);
|
||||
}
|
||||
);
|
||||
if (EntryToDefer == nullptr)
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
EntryToDefer->EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Deferred;
|
||||
EntryToDefer->DeferredUntilUtc = DeferredUntilUtc;
|
||||
EntryToDefer->DeferredAtUtc = ResolvedReferenceUtc;
|
||||
EntryToDefer->DeferralReasonLabel = !DeferralReasonLabel.IsEmpty()
|
||||
? DeferralReasonLabel
|
||||
: (bManualReschedule ? TEXT("manual-reschedule") : TEXT("deferred-window"));
|
||||
EntryToDefer->SourceTrainingSessionId.Reset();
|
||||
EntryToDefer->StartedAtUtc.Reset();
|
||||
EntryToDefer->CompletedAtUtc.Reset();
|
||||
EntryToDefer->bCanStartImmediately = false;
|
||||
EntryToDefer->RescheduleCount += 1;
|
||||
EntryToDefer->bWasManuallyRescheduled = EntryToDefer->bWasManuallyRescheduled || bManualReschedule;
|
||||
UpdatedQueueState.ActiveEntryId.Reset();
|
||||
UpdatedQueueState.ActiveTrainingSessionId.Reset();
|
||||
UpdatedQueueState.LastUpdatedAtUtc = ResolvedReferenceUtc;
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(UpdatedQueueState);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::PromoteCoachSessionQueueEntry(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FString& EntryId,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
|
||||
if (!UpdatedQueueState.IsStructurallyValid() || EntryId.IsEmpty())
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
const FString ResolvedReferenceUtc =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
|
||||
ReferenceUtc.IsEmpty() ? UpdatedQueueState.ReferenceUtc : ReferenceUtc
|
||||
);
|
||||
FHyperTwistTrainingCoachSessionQueueStateEntry* EntryToPromote =
|
||||
UpdatedQueueState.Entries.FindByPredicate(
|
||||
[&EntryId](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
|
||||
{
|
||||
return Candidate.EntryId == EntryId
|
||||
&& Candidate.EntryState != EHyperTwistTrainingCoachSessionQueueEntryState::InProgress
|
||||
&& !HyperTwistTrainingRepositoryLibraryInternal::IsTerminalCoachQueueEntryState(Candidate.EntryState);
|
||||
}
|
||||
);
|
||||
if (EntryToPromote == nullptr)
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
EntryToPromote->EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Ready;
|
||||
EntryToPromote->ScheduledAtUtc = ResolvedReferenceUtc;
|
||||
EntryToPromote->DeferredUntilUtc.Reset();
|
||||
EntryToPromote->bCanStartImmediately = true;
|
||||
EntryToPromote->SlotIndex = -1;
|
||||
UpdatedQueueState.LastUpdatedAtUtc = ResolvedReferenceUtc;
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(UpdatedQueueState);
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingCoachSessionQueueState> UHyperTwistTrainingRepositoryLibrary::ListCoachSessionQueueStatesForUser(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId
|
||||
|
|
|
|||
|
|
@ -393,6 +393,18 @@ FHyperTwistCoachScheduleHorizonSummary UHyperTwistTrainingRuntimeLibrary::GetAct
|
|||
return FHyperTwistCoachScheduleHorizonSummary();
|
||||
}
|
||||
|
||||
FHyperTwistCoachSchedulePolicySummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachSchedulePolicySummary(
|
||||
UObject* WorldContextObject
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveCoachSchedulePolicySummary();
|
||||
}
|
||||
|
||||
return FHyperTwistCoachSchedulePolicySummary();
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingMethodSegment> UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingLearnerMethodSegments(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
|
|
@ -697,6 +709,40 @@ bool UHyperTwistTrainingRuntimeLibrary::SaveCoachSessionQueueState(
|
|||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::DeferCoachSessionQueueEntry(
|
||||
UObject* WorldContextObject,
|
||||
const FString& EntryId,
|
||||
const FString& DeferredUntilUtc,
|
||||
const FString& DeferralReasonLabel,
|
||||
bool bManualReschedule
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->DeferCoachSessionQueueEntry(
|
||||
EntryId,
|
||||
DeferredUntilUtc,
|
||||
DeferralReasonLabel,
|
||||
bManualReschedule
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::PromoteCoachSessionQueueEntry(
|
||||
UObject* WorldContextObject,
|
||||
const FString& EntryId
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->PromoteCoachSessionQueueEntry(EntryId);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::RemoveMethodDrillBatch(
|
||||
UObject* WorldContextObject,
|
||||
const FString& UserId,
|
||||
|
|
|
|||
|
|
@ -178,6 +178,11 @@ FHyperTwistCoachScheduleHorizonSummary UHyperTwistTrainingSubsystem::GetActiveCo
|
|||
return ActiveCoachScheduleHorizonSummary;
|
||||
}
|
||||
|
||||
FHyperTwistCoachSchedulePolicySummary UHyperTwistTrainingSubsystem::GetActiveCoachSchedulePolicySummary() const
|
||||
{
|
||||
return ActiveCoachSchedulePolicySummary;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingMethodSegment> UHyperTwistTrainingSubsystem::GetActiveLearnerMethodSegments() const
|
||||
{
|
||||
return ActiveLearnerMethodSegments;
|
||||
|
|
@ -445,6 +450,64 @@ bool UHyperTwistTrainingSubsystem::SaveCoachSessionQueueState(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::DeferCoachSessionQueueEntry(
|
||||
const FString& EntryId,
|
||||
const FString& DeferredUntilUtc,
|
||||
const FString& DeferralReasonLabel,
|
||||
bool bManualReschedule
|
||||
)
|
||||
{
|
||||
if (!ActiveCoachSessionQueueState.IsStructurallyValid() || EntryId.IsEmpty() || DeferredUntilUtc.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ActiveCoachSessionQueueState = UHyperTwistTrainingRepositoryLibrary::DeferCoachSessionQueueEntry(
|
||||
ActiveCoachSessionQueueState,
|
||||
EntryId,
|
||||
DeferredUntilUtc,
|
||||
DeferralReasonLabel,
|
||||
bManualReschedule,
|
||||
FDateTime::UtcNow().ToIso8601()
|
||||
);
|
||||
if (!ActiveCoachSessionQueueState.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState(
|
||||
TrainingRepositoryState,
|
||||
ActiveCoachSessionQueueState
|
||||
);
|
||||
RefreshRepositoryViews();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::PromoteCoachSessionQueueEntry(const FString& EntryId)
|
||||
{
|
||||
if (!ActiveCoachSessionQueueState.IsStructurallyValid() || EntryId.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ActiveCoachSessionQueueState = UHyperTwistTrainingRepositoryLibrary::PromoteCoachSessionQueueEntry(
|
||||
ActiveCoachSessionQueueState,
|
||||
EntryId,
|
||||
FDateTime::UtcNow().ToIso8601()
|
||||
);
|
||||
if (!ActiveCoachSessionQueueState.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState(
|
||||
TrainingRepositoryState,
|
||||
ActiveCoachSessionQueueState
|
||||
);
|
||||
RefreshRepositoryViews();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::RemoveMethodDrillBatch(
|
||||
const FString& UserId,
|
||||
const FString& BatchId
|
||||
|
|
@ -1321,6 +1384,7 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
|
|||
ActiveCoachSessionQueueSummary = FHyperTwistCoachSessionQueueSummary();
|
||||
ActiveCoachSessionQueueState = FHyperTwistTrainingCoachSessionQueueState();
|
||||
ActiveCoachScheduleHorizonSummary = FHyperTwistCoachScheduleHorizonSummary();
|
||||
ActiveCoachSchedulePolicySummary = FHyperTwistCoachSchedulePolicySummary();
|
||||
ActiveLearnerMethodSegments.Reset();
|
||||
ActiveLearnerPresets.Reset();
|
||||
ActiveTrainingSessionTemplates.Reset();
|
||||
|
|
@ -1528,11 +1592,18 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
|
|||
ActiveCoachArchivePolicySummary,
|
||||
ActiveLearnerDeckStateSummary.ReferenceUtc
|
||||
);
|
||||
ActiveCoachSchedulePolicySummary = UHyperTwistTrainingCoachLibrary::DeriveCoachSchedulePolicySummary(
|
||||
ActiveCoachSessionQueueState,
|
||||
ActiveCoachScheduleHorizonSummary,
|
||||
ActiveCoachArchivePolicySummary,
|
||||
ActiveLearnerDeckStateSummary.ReferenceUtc
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveCoachSessionQueueState = FHyperTwistTrainingCoachSessionQueueState();
|
||||
ActiveCoachScheduleHorizonSummary = FHyperTwistCoachScheduleHorizonSummary();
|
||||
ActiveCoachSchedulePolicySummary = FHyperTwistCoachSchedulePolicySummary();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -215,6 +215,9 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistCoachScheduleHorizonSummary MakeSampleTrainingCoachScheduleHorizonSummary();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistCoachSchedulePolicySummary MakeSampleTrainingCoachSchedulePolicySummary();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Validation")
|
||||
static bool IsPuzzleStateStructurallyValid(const FHyperTwistPuzzleState& State);
|
||||
|
||||
|
|
|
|||
|
|
@ -311,6 +311,86 @@ struct FHyperTwistCoachScheduleHorizonSummary
|
|||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistCoachSchedulePolicySummary
|
||||
{
|
||||
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 NextImmediateEntryId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString NextPendingEntryId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString NextDeferredEntryId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString NextManualRescheduleEntryId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EarliestDeferredUntilUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DominantDeferralReasonLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ImmediateEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 PendingEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DeferredEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ManualRescheduleEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ArchiveAwareDeferredEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 BlockedFollowUpDeferredEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float ReschedulePressureScore = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bAllowsManualReschedule = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bHasExpiredDeferrals = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bNeedsManualRescheduleReview = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bHasArchiveAwareDeferrals = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bHasBlockedFollowUpDeferrals = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !UserId.IsEmpty() && !QueueId.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class UNREALHYPERTWIST_API UHyperTwistTrainingCoachLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
|
|
@ -362,4 +442,12 @@ public:
|
|||
const FHyperTwistTrainingCoachArchivePolicySummary& ArchivePolicySummary,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistCoachSchedulePolicySummary DeriveCoachSchedulePolicySummary(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FHyperTwistCoachScheduleHorizonSummary& HorizonSummary,
|
||||
const FHyperTwistTrainingCoachArchivePolicySummary& ArchivePolicySummary,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -377,6 +377,23 @@ public:
|
|||
bool bAbortEntry
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static FHyperTwistTrainingCoachSessionQueueState DeferCoachSessionQueueEntry(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FString& EntryId,
|
||||
const FString& DeferredUntilUtc,
|
||||
const FString& DeferralReasonLabel,
|
||||
bool bManualReschedule,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static FHyperTwistTrainingCoachSessionQueueState PromoteCoachSessionQueueEntry(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FString& EntryId,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static TArray<FHyperTwistTrainingCoachSessionQueueState> ListCoachSessionQueueStatesForUser(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
|
|
|
|||
|
|
@ -130,6 +130,11 @@ public:
|
|||
UObject* WorldContextObject
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistCoachSchedulePolicySummary GetActiveTrainingCoachSchedulePolicySummary(
|
||||
UObject* WorldContextObject
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static TArray<FHyperTwistTrainingMethodSegment> GetActiveTrainingLearnerMethodSegments(UObject* WorldContextObject);
|
||||
|
||||
|
|
@ -266,6 +271,21 @@ public:
|
|||
const FHyperTwistTrainingCoachSessionQueueState& QueueState
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool DeferCoachSessionQueueEntry(
|
||||
UObject* WorldContextObject,
|
||||
const FString& EntryId,
|
||||
const FString& DeferredUntilUtc,
|
||||
const FString& DeferralReasonLabel,
|
||||
bool bManualReschedule
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool PromoteCoachSessionQueueEntry(
|
||||
UObject* WorldContextObject,
|
||||
const FString& EntryId
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool RemoveMethodDrillBatch(
|
||||
UObject* WorldContextObject,
|
||||
|
|
|
|||
|
|
@ -113,6 +113,9 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistCoachScheduleHorizonSummary GetActiveCoachScheduleHorizonSummary() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistCoachSchedulePolicySummary GetActiveCoachSchedulePolicySummary() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
TArray<FHyperTwistTrainingMethodSegment> GetActiveLearnerMethodSegments() const;
|
||||
|
||||
|
|
@ -225,6 +228,17 @@ public:
|
|||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
|
||||
bool SaveCoachSessionQueueState(const FHyperTwistTrainingCoachSessionQueueState& QueueState);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
|
||||
bool DeferCoachSessionQueueEntry(
|
||||
const FString& EntryId,
|
||||
const FString& DeferredUntilUtc,
|
||||
const FString& DeferralReasonLabel,
|
||||
bool bManualReschedule
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
|
||||
bool PromoteCoachSessionQueueEntry(const FString& EntryId);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
|
||||
bool RemoveMethodDrillBatch(
|
||||
const FString& UserId,
|
||||
|
|
@ -481,6 +495,9 @@ private:
|
|||
UPROPERTY()
|
||||
FHyperTwistCoachScheduleHorizonSummary ActiveCoachScheduleHorizonSummary;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistCoachSchedulePolicySummary ActiveCoachSchedulePolicySummary;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FHyperTwistTrainingMethodSegment> ActiveLearnerMethodSegments;
|
||||
|
||||
|
|
|
|||
|
|
@ -2722,6 +2722,7 @@ UENUM(BlueprintType)
|
|||
enum class EHyperTwistTrainingCoachSessionQueueEntryState : uint8
|
||||
{
|
||||
Pending UMETA(DisplayName = "Pending"),
|
||||
Deferred UMETA(DisplayName = "Deferred"),
|
||||
Ready UMETA(DisplayName = "Ready"),
|
||||
InProgress UMETA(DisplayName = "In Progress"),
|
||||
Completed UMETA(DisplayName = "Completed"),
|
||||
|
|
@ -2921,12 +2922,24 @@ struct FHyperTwistTrainingCoachSessionQueueStateEntry
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ScheduledAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeferredUntilUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeferredAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeferralReasonLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString StartedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString CompletedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 RescheduleCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bRequiresFollowUp = false;
|
||||
|
||||
|
|
@ -2939,6 +2952,9 @@ struct FHyperTwistTrainingCoachSessionQueueStateEntry
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bCanStartImmediately = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bWasManuallyRescheduled = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !EntryId.IsEmpty() && !Headline.IsEmpty() && !WorkFingerprint.IsEmpty();
|
||||
|
|
@ -2989,6 +3005,9 @@ struct FHyperTwistTrainingCoachSessionQueueState
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 PendingEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DeferredEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ReadyEntryCount = 0;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue