Split HyperTwist coach workflow repository family
This commit is contained in:
parent
c6a02c9147
commit
c6a88aa9eb
5 changed files with 1027 additions and 887 deletions
|
|
@ -23081,893 +23081,6 @@ TArray<FHyperTwistTrainingSessionTemplate> UHyperTwistTrainingRepositoryLibrary:
|
|||
return EffectiveTemplates;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan(
|
||||
const FHyperTwistCoachBrief& CoachBrief,
|
||||
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
|
||||
const FString& PlanId,
|
||||
const FString& ReferenceUtc,
|
||||
const int32 RequestedLaunchCaseCount,
|
||||
const int32 PlannedLaunchCaseCount,
|
||||
const FString& LaunchBudgetReasonLine,
|
||||
const TArray<FString>& LaunchedCaseIds
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlan ActionPlan;
|
||||
if (CoachBrief.UserId.IsEmpty() || PlanId.IsEmpty())
|
||||
{
|
||||
return ActionPlan;
|
||||
}
|
||||
|
||||
ActionPlan.PlanId = PlanId;
|
||||
ActionPlan.UserId = CoachBrief.UserId;
|
||||
ActionPlan.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
|
||||
!ReferenceUtc.IsEmpty() ? ReferenceUtc : CoachBrief.ReferenceUtc
|
||||
);
|
||||
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<EHyperTwistCoachFocusLane>()->GetNameStringByValue(
|
||||
static_cast<int64>(CoachBrief.FocusLane)
|
||||
);
|
||||
ActionPlan.PriorityLabel = StaticEnum<EHyperTwistCoachPriority>()->GetNameStringByValue(
|
||||
static_cast<int64>(CoachBrief.Priority)
|
||||
);
|
||||
ActionPlan.PriorityScore = CoachBrief.PriorityScore;
|
||||
ActionPlan.Headline = CoachBrief.Headline;
|
||||
ActionPlan.PrimaryActionLabel = CoachBrief.PrimaryActionLabel;
|
||||
ActionPlan.SuggestedMode = CoachBrief.SuggestedMode;
|
||||
ActionPlan.SuggestedSelectionPolicy = CoachBrief.SuggestedSelectionPolicy;
|
||||
HyperTwistTrainingRepositoryLibraryInternal::ResolveCoachActionPlanPreferenceDefaults(
|
||||
CoachBrief,
|
||||
CoachMemorySnapshot,
|
||||
ActionPlan.SuggestedMode,
|
||||
ActionPlan.SuggestedSelectionPolicy
|
||||
);
|
||||
ActionPlan.PreferredGuidanceLane = CoachMemorySnapshot.PreferredGuidanceLane;
|
||||
ActionPlan.PreferredGuidanceSourceLabel = CoachMemorySnapshot.PreferredGuidanceSourceLabel;
|
||||
ActionPlan.PreferredCoachHandoffKind = CoachMemorySnapshot.PreferredCoachHandoffKind;
|
||||
ActionPlan.PreferredCoachHandoffSourceLabel = CoachMemorySnapshot.PreferredCoachHandoffSourceLabel;
|
||||
ActionPlan.RequestedLaunchCaseCount = RequestedLaunchCaseCount > 0
|
||||
? RequestedLaunchCaseCount
|
||||
: FMath::Max(1, CoachBrief.FocusCaseIds.Num());
|
||||
ActionPlan.PlannedLaunchCaseCount = PlannedLaunchCaseCount > 0
|
||||
? PlannedLaunchCaseCount
|
||||
: ActionPlan.RequestedLaunchCaseCount;
|
||||
ActionPlan.FocusCaseIds = HyperTwistTrainingRepositoryLibraryInternal::ResolveCoachActionPlanFocusCaseIds(
|
||||
CoachBrief,
|
||||
LaunchedCaseIds,
|
||||
ActionPlan.PlannedLaunchCaseCount
|
||||
);
|
||||
if (ActionPlan.FocusCaseIds.Num() > 0)
|
||||
{
|
||||
ActionPlan.PlannedLaunchCaseCount = ActionPlan.FocusCaseIds.Num();
|
||||
}
|
||||
for (const FString& OriginalFocusCaseId : HyperTwistTrainingRepositoryLibraryInternal::DeduplicateCaseIds(
|
||||
CoachBrief.FocusCaseIds))
|
||||
{
|
||||
if (!ActionPlan.FocusCaseIds.Contains(OriginalFocusCaseId))
|
||||
{
|
||||
ActionPlan.DeferredLaunchCaseIds.Add(OriginalFocusCaseId);
|
||||
}
|
||||
}
|
||||
ActionPlan.LaunchBudgetReasonLine = LaunchBudgetReasonLine;
|
||||
ActionPlan.bBroadenedLaunchBudget =
|
||||
ActionPlan.PlannedLaunchCaseCount > ActionPlan.RequestedLaunchCaseCount
|
||||
|| HyperTwistTrainingRepositoryLibraryInternal::ContainsLaunchBudgetBroadeningHint(
|
||||
ActionPlan.LaunchBudgetReasonLine);
|
||||
ActionPlan.bTightenedLaunchBudget =
|
||||
ActionPlan.PlannedLaunchCaseCount < ActionPlan.RequestedLaunchCaseCount
|
||||
|| HyperTwistTrainingRepositoryLibraryInternal::ContainsLaunchBudgetTighteningHint(
|
||||
ActionPlan.LaunchBudgetReasonLine);
|
||||
if (ActionPlan.FocusCaseIds.Num() == 0)
|
||||
{
|
||||
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;
|
||||
ActionPlan.bNeedsVarietyRotation = CoachBrief.bNeedsVarietyRotation;
|
||||
ActionPlan.bPreferRecognitionReplayReview = CoachBrief.bPreferRecognitionReplayReview;
|
||||
|
||||
for (int32 Index = 0; Index < ActionPlan.FocusCaseIds.Num(); ++Index)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlanEntry Entry;
|
||||
Entry.ActionId = FString::Printf(TEXT("%s_entry_%02d"), *PlanId, Index + 1);
|
||||
Entry.CaseId = ActionPlan.FocusCaseIds[Index];
|
||||
Entry.DeckId = CoachBrief.FocusDeckId;
|
||||
Entry.MethodSegmentId = CoachBrief.FocusMethodSegmentId;
|
||||
Entry.ActionLabel = CoachBrief.PrimaryActionLabel;
|
||||
Entry.Headline = CoachBrief.Headline;
|
||||
Entry.PriorityScore = CoachBrief.PriorityScore;
|
||||
Entry.SuggestedMode = ActionPlan.SuggestedMode;
|
||||
Entry.SuggestedSelectionPolicy = ActionPlan.SuggestedSelectionPolicy;
|
||||
Entry.bPreferRecognitionReplayReview = CoachBrief.bPreferRecognitionReplayReview;
|
||||
if (Entry.IsStructurallyValid())
|
||||
{
|
||||
ActionPlan.Entries.Add(Entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (ActionPlan.Entries.Num() == 0)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlanEntry Entry;
|
||||
Entry.ActionId = FString::Printf(TEXT("%s_entry_01"), *PlanId);
|
||||
Entry.CaseId = TEXT("coach-generic");
|
||||
Entry.DeckId = CoachBrief.FocusDeckId;
|
||||
Entry.MethodSegmentId = CoachBrief.FocusMethodSegmentId;
|
||||
Entry.ActionLabel = CoachBrief.PrimaryActionLabel.IsEmpty()
|
||||
? TEXT("coach-focus")
|
||||
: CoachBrief.PrimaryActionLabel;
|
||||
Entry.Headline = CoachBrief.Headline;
|
||||
Entry.PriorityScore = CoachBrief.PriorityScore;
|
||||
Entry.SuggestedMode = ActionPlan.SuggestedMode;
|
||||
Entry.SuggestedSelectionPolicy = ActionPlan.SuggestedSelectionPolicy;
|
||||
Entry.bPreferRecognitionReplayReview = CoachBrief.bPreferRecognitionReplayReview;
|
||||
ActionPlan.Entries.Add(Entry);
|
||||
}
|
||||
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRepositoryLibrary::ActivateCoachActionPlan(
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan,
|
||||
const FString& TrainingSessionId,
|
||||
const FString& StartedAtUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlan UpdatedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
if (!UpdatedPlan.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedPlan;
|
||||
}
|
||||
|
||||
UpdatedPlan.PlanState = EHyperTwistTrainingCoachActionPlanState::Active;
|
||||
UpdatedPlan.SourceSessionId = !TrainingSessionId.IsEmpty() ? TrainingSessionId : UpdatedPlan.SourceSessionId;
|
||||
UpdatedPlan.StartedAtUtc = !StartedAtUtc.IsEmpty()
|
||||
? StartedAtUtc
|
||||
: HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(UpdatedPlan.ReferenceUtc);
|
||||
return UpdatedPlan;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRepositoryLibrary::FinalizeCoachActionPlan(
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan,
|
||||
const FHyperTwistTrainingSession& Session,
|
||||
bool bAbortPlan
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlan UpdatedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
if (!UpdatedPlan.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedPlan;
|
||||
}
|
||||
|
||||
UpdatedPlan.SourceSessionId = !Session.TrainingSessionId.IsEmpty()
|
||||
? Session.TrainingSessionId
|
||||
: UpdatedPlan.SourceSessionId;
|
||||
UpdatedPlan.CompletedAtUtc = !Session.EndedAtUtc.IsEmpty()
|
||||
? Session.EndedAtUtc
|
||||
: HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(UpdatedPlan.ReferenceUtc);
|
||||
UpdatedPlan.PlanState = bAbortPlan || Session.SessionState == EHyperTwistTrainingSessionState::Aborted
|
||||
? EHyperTwistTrainingCoachActionPlanState::Aborted
|
||||
: EHyperTwistTrainingCoachActionPlanState::Completed;
|
||||
if (UpdatedPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Completed)
|
||||
{
|
||||
for (FHyperTwistTrainingCoachActionPlanEntry& Entry : UpdatedPlan.Entries)
|
||||
{
|
||||
Entry.bCompleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
return UpdatedPlan;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingCoachActionPlan> UHyperTwistTrainingRepositoryLibrary::ListCoachActionPlansForUser(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId
|
||||
)
|
||||
{
|
||||
TArray<FHyperTwistTrainingCoachActionPlan> ActionPlans;
|
||||
if (UserId.IsEmpty())
|
||||
{
|
||||
return ActionPlans;
|
||||
}
|
||||
|
||||
for (const FHyperTwistTrainingCoachActionPlan& ActionPlan : RepositoryState.StoredCoachActionPlans)
|
||||
{
|
||||
if (ActionPlan.UserId == UserId && ActionPlan.IsStructurallyValid())
|
||||
{
|
||||
ActionPlans.Add(ActionPlan);
|
||||
}
|
||||
}
|
||||
|
||||
ActionPlans.Sort([](const FHyperTwistTrainingCoachActionPlan& Left, const FHyperTwistTrainingCoachActionPlan& Right)
|
||||
{
|
||||
const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
|
||||
Left.CreatedAtUtc,
|
||||
Right.CreatedAtUtc
|
||||
);
|
||||
if (UtcCompare != 0)
|
||||
{
|
||||
return UtcCompare > 0;
|
||||
}
|
||||
|
||||
return Left.PlanId < Right.PlanId;
|
||||
});
|
||||
return ActionPlans;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRepositoryLibrary::TryGetCoachActionPlan(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& PlanId,
|
||||
FHyperTwistTrainingCoachActionPlan& OutActionPlan
|
||||
)
|
||||
{
|
||||
OutActionPlan = FHyperTwistTrainingCoachActionPlan();
|
||||
if (PlanId.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachActionPlan* MatchingPlan =
|
||||
RepositoryState.StoredCoachActionPlans.FindByPredicate(
|
||||
[&PlanId](const FHyperTwistTrainingCoachActionPlan& Candidate)
|
||||
{
|
||||
return Candidate.PlanId == PlanId && Candidate.IsStructurallyValid();
|
||||
}
|
||||
);
|
||||
if (MatchingPlan == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutActionPlan = *MatchingPlan;
|
||||
return true;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertCoachActionPlan(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
if (!NormalizedPlan.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedRepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan* ExistingPlan =
|
||||
UpdatedRepositoryState.StoredCoachActionPlans.FindByPredicate(
|
||||
[&NormalizedPlan](const FHyperTwistTrainingCoachActionPlan& Candidate)
|
||||
{
|
||||
return Candidate.UserId == NormalizedPlan.UserId
|
||||
&& Candidate.PlanId == NormalizedPlan.PlanId;
|
||||
}
|
||||
);
|
||||
if (ExistingPlan != nullptr)
|
||||
{
|
||||
*ExistingPlan = NormalizedPlan;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatedRepositoryState.StoredCoachActionPlans.Add(NormalizedPlan);
|
||||
}
|
||||
|
||||
return UpdatedRepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::BuildCoachSessionQueueState(
|
||||
const FHyperTwistCoachSessionQueueSummary& QueueSummary,
|
||||
const FString& QueueId,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueState QueueState;
|
||||
if (!QueueSummary.IsStructurallyValid())
|
||||
{
|
||||
return QueueState;
|
||||
}
|
||||
|
||||
QueueState.QueueId = QueueId.IsEmpty() ? TEXT("coach_queue_active") : QueueId;
|
||||
QueueState.UserId = QueueSummary.UserId;
|
||||
QueueState.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
|
||||
ReferenceUtc.IsEmpty() ? QueueSummary.ReferenceUtc : ReferenceUtc
|
||||
);
|
||||
QueueState.LastUpdatedAtUtc = QueueState.ReferenceUtc;
|
||||
QueueState.FocusDeckId = QueueSummary.FocusDeckId;
|
||||
QueueState.FocusMethodSegmentId = QueueSummary.FocusMethodSegmentId;
|
||||
QueueState.PrimaryQueueLabel = QueueSummary.PrimaryQueueLabel;
|
||||
QueueState.NextQueueLabel = QueueSummary.NextQueueLabel;
|
||||
QueueState.bHasImmediateQueue = QueueSummary.bHasImmediateQueue;
|
||||
QueueState.bNeedsArchiveAwareSequencing = QueueSummary.bNeedsArchiveAwareSequencing;
|
||||
|
||||
for (const FHyperTwistCoachSessionQueueEntry& SummaryEntry : QueueSummary.Entries)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueStateEntry QueueEntry;
|
||||
QueueEntry.EntryId = SummaryEntry.EntryId;
|
||||
QueueEntry.SourceLabel = SummaryEntry.SourceLabel;
|
||||
QueueEntry.SourceBriefTrainingSessionId = SummaryEntry.SourceBriefTrainingSessionId;
|
||||
QueueEntry.SourceReviewPlanId = SummaryEntry.SourceReviewPlanId;
|
||||
QueueEntry.CarryForwardPlanId = SummaryEntry.CarryForwardPlanId;
|
||||
QueueEntry.RootPlanId = SummaryEntry.RootPlanId;
|
||||
QueueEntry.FollowUpDepth = FMath::Max(0, SummaryEntry.FollowUpDepth);
|
||||
QueueEntry.SlotIndex = SummaryEntry.SlotIndex;
|
||||
QueueEntry.PriorityLabel = StaticEnum<EHyperTwistCoachPriority>()->GetNameStringByValue(
|
||||
static_cast<int64>(SummaryEntry.Priority)
|
||||
);
|
||||
QueueEntry.PriorityScore = SummaryEntry.PriorityScore;
|
||||
QueueEntry.FocusLaneLabel = StaticEnum<EHyperTwistCoachFocusLane>()->GetNameStringByValue(
|
||||
static_cast<int64>(SummaryEntry.FocusLane)
|
||||
);
|
||||
QueueEntry.Headline = SummaryEntry.Headline;
|
||||
QueueEntry.PrimaryActionLabel = SummaryEntry.PrimaryActionLabel;
|
||||
QueueEntry.SuggestedMode = SummaryEntry.SuggestedMode;
|
||||
QueueEntry.SuggestedSelectionPolicy = SummaryEntry.SuggestedSelectionPolicy;
|
||||
QueueEntry.FocusDeckId = SummaryEntry.FocusDeckId;
|
||||
QueueEntry.FocusMethodSegmentId = SummaryEntry.FocusMethodSegmentId;
|
||||
QueueEntry.FocusCaseIds = SummaryEntry.FocusCaseIds;
|
||||
QueueEntry.DeferredLaunchCaseCount = FMath::Max(0, SummaryEntry.DeferredLaunchCaseCount);
|
||||
QueueEntry.bNeedsDeferredLaunchRecovery = SummaryEntry.bNeedsDeferredLaunchRecovery;
|
||||
QueueEntry.bHasTightenedLaunchOverflow = SummaryEntry.bHasTightenedLaunchOverflow;
|
||||
QueueEntry.bRequiresFollowUp = SummaryEntry.bRequiresFollowUp;
|
||||
QueueEntry.bRequiresArchiveRetentionTuning = SummaryEntry.bRequiresArchiveRetentionTuning;
|
||||
QueueEntry.bNeedsCoachReview = SummaryEntry.bNeedsCoachReview;
|
||||
QueueEntry.bNeedsCadenceRecovery = SummaryEntry.bNeedsCadenceRecovery;
|
||||
QueueEntry.bNeedsVarietyRotation = SummaryEntry.bNeedsVarietyRotation;
|
||||
QueueEntry.bPreferRecognitionReplayReview = SummaryEntry.bPreferRecognitionReplayReview;
|
||||
QueueEntry.bPinned = QueueEntry.bRequiresFollowUp || QueueEntry.bRequiresArchiveRetentionTuning;
|
||||
QueueEntry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
|
||||
QueueEntry.WorkFingerprint =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::MakeCoachSessionQueueEntryFingerprint(QueueEntry);
|
||||
QueueState.Entries.Add(QueueEntry);
|
||||
}
|
||||
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::ResolveCoachSessionQueueState(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& ExistingQueueState,
|
||||
const FHyperTwistCoachSessionQueueSummary& QueueSummary,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueState ResolvedQueueState = BuildCoachSessionQueueState(
|
||||
QueueSummary,
|
||||
ExistingQueueState.QueueId,
|
||||
ReferenceUtc
|
||||
);
|
||||
if (!ResolvedQueueState.IsStructurallyValid() || !ExistingQueueState.IsStructurallyValid())
|
||||
{
|
||||
return ResolvedQueueState;
|
||||
}
|
||||
|
||||
for (FHyperTwistTrainingCoachSessionQueueStateEntry& QueueEntry : ResolvedQueueState.Entries)
|
||||
{
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry* ExistingEntry =
|
||||
ExistingQueueState.Entries.FindByPredicate(
|
||||
[&QueueEntry](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
|
||||
{
|
||||
return Candidate.EntryId == QueueEntry.EntryId
|
||||
&& Candidate.WorkFingerprint == QueueEntry.WorkFingerprint
|
||||
&& Candidate.IsStructurallyValid();
|
||||
}
|
||||
);
|
||||
if (ExistingEntry == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
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(
|
||||
ReferenceUtc.IsEmpty() ? ResolvedQueueState.ReferenceUtc : ReferenceUtc
|
||||
);
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(ResolvedQueueState);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::StartCoachSessionQueueEntryBySourceLabel(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FString& SourceLabel,
|
||||
const FString& TrainingSessionId,
|
||||
const FString& StartedAtUtc,
|
||||
const FString& StartActionSourceLabel
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
|
||||
if (!UpdatedQueueState.IsStructurallyValid() || SourceLabel.IsEmpty())
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
const FString ResolvedStartedAtUtc =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(StartedAtUtc);
|
||||
int32 ActivatedIndex = INDEX_NONE;
|
||||
for (int32 EntryIndex = 0; EntryIndex < UpdatedQueueState.Entries.Num(); ++EntryIndex)
|
||||
{
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry& Entry = UpdatedQueueState.Entries[EntryIndex];
|
||||
if (Entry.SourceLabel == SourceLabel
|
||||
&& Entry.EntryState != EHyperTwistTrainingCoachSessionQueueEntryState::Deferred
|
||||
&& !HyperTwistTrainingRepositoryLibraryInternal::IsTerminalCoachQueueEntryState(Entry.EntryState))
|
||||
{
|
||||
ActivatedIndex = EntryIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ActivatedIndex == INDEX_NONE)
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
for (FHyperTwistTrainingCoachSessionQueueStateEntry& Entry : UpdatedQueueState.Entries)
|
||||
{
|
||||
if (Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress)
|
||||
{
|
||||
Entry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
|
||||
Entry.SourceTrainingSessionId.Reset();
|
||||
Entry.StartActionSourceLabel.Reset();
|
||||
Entry.StartedAtUtc.Reset();
|
||||
Entry.bCanStartImmediately = false;
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueStateEntry& ActiveEntry =
|
||||
UpdatedQueueState.Entries[ActivatedIndex];
|
||||
ActiveEntry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::InProgress;
|
||||
ActiveEntry.SourceTrainingSessionId = TrainingSessionId;
|
||||
ActiveEntry.StartActionSourceLabel = !StartActionSourceLabel.IsEmpty()
|
||||
? StartActionSourceLabel
|
||||
: SourceLabel;
|
||||
ActiveEntry.ScheduledAtUtc = !ActiveEntry.ScheduledAtUtc.IsEmpty()
|
||||
? ActiveEntry.ScheduledAtUtc
|
||||
: ResolvedStartedAtUtc;
|
||||
ActiveEntry.DeferredUntilUtc.Reset();
|
||||
ActiveEntry.StartedAtUtc = ResolvedStartedAtUtc;
|
||||
ActiveEntry.bCanStartImmediately = false;
|
||||
UpdatedQueueState.LastUpdatedAtUtc = ResolvedStartedAtUtc;
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(UpdatedQueueState);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::StartCoachSessionQueueEntryById(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FString& EntryId,
|
||||
const FString& TrainingSessionId,
|
||||
const FString& StartedAtUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
|
||||
if (!UpdatedQueueState.IsStructurallyValid() || EntryId.IsEmpty())
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
const FString ResolvedStartedAtUtc =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(StartedAtUtc);
|
||||
const int32 ActivatedIndex = UpdatedQueueState.Entries.IndexOfByPredicate(
|
||||
[&EntryId](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
|
||||
{
|
||||
return Candidate.EntryId == EntryId
|
||||
&& Candidate.EntryState != EHyperTwistTrainingCoachSessionQueueEntryState::Deferred
|
||||
&& !HyperTwistTrainingRepositoryLibraryInternal::IsTerminalCoachQueueEntryState(Candidate.EntryState);
|
||||
}
|
||||
);
|
||||
if (ActivatedIndex == INDEX_NONE)
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
for (FHyperTwistTrainingCoachSessionQueueStateEntry& Entry : UpdatedQueueState.Entries)
|
||||
{
|
||||
if (Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress)
|
||||
{
|
||||
Entry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
|
||||
Entry.SourceTrainingSessionId.Reset();
|
||||
Entry.StartedAtUtc.Reset();
|
||||
Entry.bCanStartImmediately = false;
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueStateEntry& ActiveEntry =
|
||||
UpdatedQueueState.Entries[ActivatedIndex];
|
||||
ActiveEntry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::InProgress;
|
||||
ActiveEntry.SourceTrainingSessionId = TrainingSessionId;
|
||||
ActiveEntry.ScheduledAtUtc = !ActiveEntry.ScheduledAtUtc.IsEmpty()
|
||||
? ActiveEntry.ScheduledAtUtc
|
||||
: ResolvedStartedAtUtc;
|
||||
ActiveEntry.DeferredUntilUtc.Reset();
|
||||
ActiveEntry.StartedAtUtc = ResolvedStartedAtUtc;
|
||||
ActiveEntry.bCanStartImmediately = false;
|
||||
UpdatedQueueState.LastUpdatedAtUtc = ResolvedStartedAtUtc;
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(UpdatedQueueState);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::FinalizeCoachSessionQueueEntry(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FString& TrainingSessionId,
|
||||
const FString& CompletedAtUtc,
|
||||
bool bAbortEntry
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
|
||||
if (!UpdatedQueueState.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
const FString ResolvedCompletedAtUtc =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
|
||||
CompletedAtUtc.IsEmpty() ? UpdatedQueueState.ReferenceUtc : CompletedAtUtc
|
||||
);
|
||||
FHyperTwistTrainingCoachSessionQueueStateEntry* ActiveEntry =
|
||||
UpdatedQueueState.Entries.FindByPredicate(
|
||||
[&TrainingSessionId, &UpdatedQueueState](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
|
||||
{
|
||||
if (!TrainingSessionId.IsEmpty() && Candidate.SourceTrainingSessionId == TrainingSessionId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Candidate.EntryId == UpdatedQueueState.ActiveEntryId
|
||||
&& Candidate.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress;
|
||||
}
|
||||
);
|
||||
if (ActiveEntry == nullptr)
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
ActiveEntry->EntryState = bAbortEntry
|
||||
? EHyperTwistTrainingCoachSessionQueueEntryState::Skipped
|
||||
: EHyperTwistTrainingCoachSessionQueueEntryState::Completed;
|
||||
ActiveEntry->CompletedAtUtc = ResolvedCompletedAtUtc;
|
||||
ActiveEntry->bCanStartImmediately = false;
|
||||
UpdatedQueueState.ActiveEntryId.Reset();
|
||||
UpdatedQueueState.ActiveTrainingSessionId.Reset();
|
||||
UpdatedQueueState.LastUpdatedAtUtc = ResolvedCompletedAtUtc;
|
||||
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& StartActionSourceLabel,
|
||||
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;
|
||||
if (!StartActionSourceLabel.IsEmpty())
|
||||
{
|
||||
EntryToPromote->StartActionSourceLabel = StartActionSourceLabel;
|
||||
}
|
||||
UpdatedQueueState.LastUpdatedAtUtc = ResolvedReferenceUtc;
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(UpdatedQueueState);
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingCoachSessionQueueState> UHyperTwistTrainingRepositoryLibrary::ListCoachSessionQueueStatesForUser(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId
|
||||
)
|
||||
{
|
||||
TArray<FHyperTwistTrainingCoachSessionQueueState> QueueStates;
|
||||
if (UserId.IsEmpty())
|
||||
{
|
||||
return QueueStates;
|
||||
}
|
||||
|
||||
for (const FHyperTwistTrainingCoachSessionQueueState& QueueState : RepositoryState.StoredCoachSessionQueues)
|
||||
{
|
||||
if (QueueState.UserId == UserId && QueueState.IsStructurallyValid())
|
||||
{
|
||||
QueueStates.Add(QueueState);
|
||||
}
|
||||
}
|
||||
|
||||
QueueStates.Sort([](
|
||||
const FHyperTwistTrainingCoachSessionQueueState& Left,
|
||||
const FHyperTwistTrainingCoachSessionQueueState& Right
|
||||
)
|
||||
{
|
||||
const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
|
||||
Left.LastUpdatedAtUtc,
|
||||
Right.LastUpdatedAtUtc
|
||||
);
|
||||
if (UtcCompare != 0)
|
||||
{
|
||||
return UtcCompare > 0;
|
||||
}
|
||||
|
||||
return Left.QueueId < Right.QueueId;
|
||||
});
|
||||
return QueueStates;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingCoachSessionQueueHistoryEntry> UHyperTwistTrainingRepositoryLibrary::ListCoachSessionQueueHistoryEntries(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& QueueId
|
||||
)
|
||||
{
|
||||
TArray<FHyperTwistTrainingCoachSessionQueueHistoryEntry> HistoryEntries;
|
||||
if (UserId.IsEmpty() || QueueId.IsEmpty())
|
||||
{
|
||||
return HistoryEntries;
|
||||
}
|
||||
|
||||
for (const FHyperTwistTrainingCoachSessionQueueHistoryEntry& HistoryEntry : RepositoryState.StoredCoachSessionQueueHistory)
|
||||
{
|
||||
if (HistoryEntry.UserId == UserId
|
||||
&& HistoryEntry.QueueId == QueueId
|
||||
&& HistoryEntry.IsStructurallyValid())
|
||||
{
|
||||
HistoryEntries.Add(HistoryEntry);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryEntries.Sort([](
|
||||
const FHyperTwistTrainingCoachSessionQueueHistoryEntry& Left,
|
||||
const FHyperTwistTrainingCoachSessionQueueHistoryEntry& Right
|
||||
)
|
||||
{
|
||||
const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
|
||||
Left.EventAtUtc,
|
||||
Right.EventAtUtc
|
||||
);
|
||||
if (UtcCompare != 0)
|
||||
{
|
||||
return UtcCompare < 0;
|
||||
}
|
||||
|
||||
return Left.HistoryEntryId < Right.HistoryEntryId;
|
||||
});
|
||||
return HistoryEntries;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRepositoryLibrary::TryGetCoachSessionQueueState(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& QueueId,
|
||||
FHyperTwistTrainingCoachSessionQueueState& OutQueueState
|
||||
)
|
||||
{
|
||||
OutQueueState = FHyperTwistTrainingCoachSessionQueueState();
|
||||
if (UserId.IsEmpty() || QueueId.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachSessionQueueState* MatchingQueueState =
|
||||
RepositoryState.StoredCoachSessionQueues.FindByPredicate(
|
||||
[&UserId, &QueueId](const FHyperTwistTrainingCoachSessionQueueState& Candidate)
|
||||
{
|
||||
return Candidate.UserId == UserId
|
||||
&& Candidate.QueueId == QueueId
|
||||
&& Candidate.IsStructurallyValid();
|
||||
}
|
||||
);
|
||||
if (MatchingQueueState == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutQueueState = *MatchingQueueState;
|
||||
return true;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
|
||||
const FHyperTwistTrainingCoachSessionQueueState NormalizedQueueState =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
|
||||
if (!NormalizedQueueState.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedRepositoryState;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachSessionQueueState* ExistingQueueState =
|
||||
UpdatedRepositoryState.StoredCoachSessionQueues.FindByPredicate(
|
||||
[&NormalizedQueueState](const FHyperTwistTrainingCoachSessionQueueState& Candidate)
|
||||
{
|
||||
return Candidate.UserId == NormalizedQueueState.UserId
|
||||
&& Candidate.QueueId == NormalizedQueueState.QueueId;
|
||||
}
|
||||
);
|
||||
|
||||
for (const FHyperTwistTrainingCoachSessionQueueStateEntry& CurrentEntry : NormalizedQueueState.Entries)
|
||||
{
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry* PreviousEntry = ExistingQueueState != nullptr
|
||||
? ExistingQueueState->Entries.FindByPredicate(
|
||||
[&CurrentEntry](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
|
||||
{
|
||||
return Candidate.EntryId == CurrentEntry.EntryId;
|
||||
}
|
||||
)
|
||||
: nullptr;
|
||||
if (PreviousEntry != nullptr && PreviousEntry->WorkFingerprint != CurrentEntry.WorkFingerprint)
|
||||
{
|
||||
PreviousEntry = nullptr;
|
||||
}
|
||||
if (PreviousEntry != nullptr
|
||||
&& !HyperTwistTrainingRepositoryLibraryInternal::DidCoachSessionQueueEntryChange(
|
||||
*PreviousEntry,
|
||||
CurrentEntry
|
||||
))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachSessionQueueHistoryEntry HistoryEntry =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::BuildCoachSessionQueueHistoryEntry(
|
||||
NormalizedQueueState,
|
||||
CurrentEntry,
|
||||
PreviousEntry
|
||||
);
|
||||
if (!HistoryEntry.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool bAlreadyExists =
|
||||
UpdatedRepositoryState.StoredCoachSessionQueueHistory.ContainsByPredicate(
|
||||
[&HistoryEntry](const FHyperTwistTrainingCoachSessionQueueHistoryEntry& Candidate)
|
||||
{
|
||||
return Candidate.HistoryEntryId == HistoryEntry.HistoryEntryId;
|
||||
}
|
||||
);
|
||||
if (!bAlreadyExists)
|
||||
{
|
||||
UpdatedRepositoryState.StoredCoachSessionQueueHistory.Add(HistoryEntry);
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState* MutableExistingQueueState =
|
||||
UpdatedRepositoryState.StoredCoachSessionQueues.FindByPredicate(
|
||||
[&NormalizedQueueState](const FHyperTwistTrainingCoachSessionQueueState& Candidate)
|
||||
{
|
||||
return Candidate.UserId == NormalizedQueueState.UserId
|
||||
&& Candidate.QueueId == NormalizedQueueState.QueueId;
|
||||
}
|
||||
);
|
||||
if (MutableExistingQueueState != nullptr)
|
||||
{
|
||||
*MutableExistingQueueState = NormalizedQueueState;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatedRepositoryState.StoredCoachSessionQueues.Add(NormalizedQueueState);
|
||||
}
|
||||
|
||||
return UpdatedRepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RemoveCoachSessionQueueState(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& QueueId
|
||||
)
|
||||
{
|
||||
if (UserId.IsEmpty() || QueueId.IsEmpty())
|
||||
{
|
||||
return RepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
|
||||
UpdatedRepositoryState.StoredCoachSessionQueues.RemoveAll(
|
||||
[&UserId, &QueueId](const FHyperTwistTrainingCoachSessionQueueState& Candidate)
|
||||
{
|
||||
return Candidate.UserId == UserId && Candidate.QueueId == QueueId;
|
||||
}
|
||||
);
|
||||
return UpdatedRepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueExecutionSummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachSessionQueueExecutionSummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,892 @@
|
|||
// Copyright HyperTwist, Inc. All Rights Reserved.
|
||||
|
||||
#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"
|
||||
|
||||
#include "HyperTwistTrainingRepositoryLibraryInternal.h"
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan(
|
||||
const FHyperTwistCoachBrief& CoachBrief,
|
||||
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
|
||||
const FString& PlanId,
|
||||
const FString& ReferenceUtc,
|
||||
const int32 RequestedLaunchCaseCount,
|
||||
const int32 PlannedLaunchCaseCount,
|
||||
const FString& LaunchBudgetReasonLine,
|
||||
const TArray<FString>& LaunchedCaseIds
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlan ActionPlan;
|
||||
if (CoachBrief.UserId.IsEmpty() || PlanId.IsEmpty())
|
||||
{
|
||||
return ActionPlan;
|
||||
}
|
||||
|
||||
ActionPlan.PlanId = PlanId;
|
||||
ActionPlan.UserId = CoachBrief.UserId;
|
||||
ActionPlan.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
|
||||
!ReferenceUtc.IsEmpty() ? ReferenceUtc : CoachBrief.ReferenceUtc
|
||||
);
|
||||
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<EHyperTwistCoachFocusLane>()->GetNameStringByValue(
|
||||
static_cast<int64>(CoachBrief.FocusLane)
|
||||
);
|
||||
ActionPlan.PriorityLabel = StaticEnum<EHyperTwistCoachPriority>()->GetNameStringByValue(
|
||||
static_cast<int64>(CoachBrief.Priority)
|
||||
);
|
||||
ActionPlan.PriorityScore = CoachBrief.PriorityScore;
|
||||
ActionPlan.Headline = CoachBrief.Headline;
|
||||
ActionPlan.PrimaryActionLabel = CoachBrief.PrimaryActionLabel;
|
||||
ActionPlan.SuggestedMode = CoachBrief.SuggestedMode;
|
||||
ActionPlan.SuggestedSelectionPolicy = CoachBrief.SuggestedSelectionPolicy;
|
||||
HyperTwistTrainingRepositoryLibraryInternal::ResolveCoachActionPlanPreferenceDefaults(
|
||||
CoachBrief,
|
||||
CoachMemorySnapshot,
|
||||
ActionPlan.SuggestedMode,
|
||||
ActionPlan.SuggestedSelectionPolicy
|
||||
);
|
||||
ActionPlan.PreferredGuidanceLane = CoachMemorySnapshot.PreferredGuidanceLane;
|
||||
ActionPlan.PreferredGuidanceSourceLabel = CoachMemorySnapshot.PreferredGuidanceSourceLabel;
|
||||
ActionPlan.PreferredCoachHandoffKind = CoachMemorySnapshot.PreferredCoachHandoffKind;
|
||||
ActionPlan.PreferredCoachHandoffSourceLabel = CoachMemorySnapshot.PreferredCoachHandoffSourceLabel;
|
||||
ActionPlan.RequestedLaunchCaseCount = RequestedLaunchCaseCount > 0
|
||||
? RequestedLaunchCaseCount
|
||||
: FMath::Max(1, CoachBrief.FocusCaseIds.Num());
|
||||
ActionPlan.PlannedLaunchCaseCount = PlannedLaunchCaseCount > 0
|
||||
? PlannedLaunchCaseCount
|
||||
: ActionPlan.RequestedLaunchCaseCount;
|
||||
ActionPlan.FocusCaseIds = HyperTwistTrainingRepositoryLibraryInternal::ResolveCoachActionPlanFocusCaseIds(
|
||||
CoachBrief,
|
||||
LaunchedCaseIds,
|
||||
ActionPlan.PlannedLaunchCaseCount
|
||||
);
|
||||
if (ActionPlan.FocusCaseIds.Num() > 0)
|
||||
{
|
||||
ActionPlan.PlannedLaunchCaseCount = ActionPlan.FocusCaseIds.Num();
|
||||
}
|
||||
for (const FString& OriginalFocusCaseId : HyperTwistTrainingRepositoryLibraryInternal::DeduplicateCaseIds(
|
||||
CoachBrief.FocusCaseIds))
|
||||
{
|
||||
if (!ActionPlan.FocusCaseIds.Contains(OriginalFocusCaseId))
|
||||
{
|
||||
ActionPlan.DeferredLaunchCaseIds.Add(OriginalFocusCaseId);
|
||||
}
|
||||
}
|
||||
ActionPlan.LaunchBudgetReasonLine = LaunchBudgetReasonLine;
|
||||
ActionPlan.bBroadenedLaunchBudget =
|
||||
ActionPlan.PlannedLaunchCaseCount > ActionPlan.RequestedLaunchCaseCount
|
||||
|| HyperTwistTrainingRepositoryLibraryInternal::ContainsLaunchBudgetBroadeningHint(
|
||||
ActionPlan.LaunchBudgetReasonLine);
|
||||
ActionPlan.bTightenedLaunchBudget =
|
||||
ActionPlan.PlannedLaunchCaseCount < ActionPlan.RequestedLaunchCaseCount
|
||||
|| HyperTwistTrainingRepositoryLibraryInternal::ContainsLaunchBudgetTighteningHint(
|
||||
ActionPlan.LaunchBudgetReasonLine);
|
||||
if (ActionPlan.FocusCaseIds.Num() == 0)
|
||||
{
|
||||
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;
|
||||
ActionPlan.bNeedsVarietyRotation = CoachBrief.bNeedsVarietyRotation;
|
||||
ActionPlan.bPreferRecognitionReplayReview = CoachBrief.bPreferRecognitionReplayReview;
|
||||
|
||||
for (int32 Index = 0; Index < ActionPlan.FocusCaseIds.Num(); ++Index)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlanEntry Entry;
|
||||
Entry.ActionId = FString::Printf(TEXT("%s_entry_%02d"), *PlanId, Index + 1);
|
||||
Entry.CaseId = ActionPlan.FocusCaseIds[Index];
|
||||
Entry.DeckId = CoachBrief.FocusDeckId;
|
||||
Entry.MethodSegmentId = CoachBrief.FocusMethodSegmentId;
|
||||
Entry.ActionLabel = CoachBrief.PrimaryActionLabel;
|
||||
Entry.Headline = CoachBrief.Headline;
|
||||
Entry.PriorityScore = CoachBrief.PriorityScore;
|
||||
Entry.SuggestedMode = ActionPlan.SuggestedMode;
|
||||
Entry.SuggestedSelectionPolicy = ActionPlan.SuggestedSelectionPolicy;
|
||||
Entry.bPreferRecognitionReplayReview = CoachBrief.bPreferRecognitionReplayReview;
|
||||
if (Entry.IsStructurallyValid())
|
||||
{
|
||||
ActionPlan.Entries.Add(Entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (ActionPlan.Entries.Num() == 0)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlanEntry Entry;
|
||||
Entry.ActionId = FString::Printf(TEXT("%s_entry_01"), *PlanId);
|
||||
Entry.CaseId = TEXT("coach-generic");
|
||||
Entry.DeckId = CoachBrief.FocusDeckId;
|
||||
Entry.MethodSegmentId = CoachBrief.FocusMethodSegmentId;
|
||||
Entry.ActionLabel = CoachBrief.PrimaryActionLabel.IsEmpty()
|
||||
? TEXT("coach-focus")
|
||||
: CoachBrief.PrimaryActionLabel;
|
||||
Entry.Headline = CoachBrief.Headline;
|
||||
Entry.PriorityScore = CoachBrief.PriorityScore;
|
||||
Entry.SuggestedMode = ActionPlan.SuggestedMode;
|
||||
Entry.SuggestedSelectionPolicy = ActionPlan.SuggestedSelectionPolicy;
|
||||
Entry.bPreferRecognitionReplayReview = CoachBrief.bPreferRecognitionReplayReview;
|
||||
ActionPlan.Entries.Add(Entry);
|
||||
}
|
||||
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRepositoryLibrary::ActivateCoachActionPlan(
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan,
|
||||
const FString& TrainingSessionId,
|
||||
const FString& StartedAtUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlan UpdatedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
if (!UpdatedPlan.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedPlan;
|
||||
}
|
||||
|
||||
UpdatedPlan.PlanState = EHyperTwistTrainingCoachActionPlanState::Active;
|
||||
UpdatedPlan.SourceSessionId = !TrainingSessionId.IsEmpty() ? TrainingSessionId : UpdatedPlan.SourceSessionId;
|
||||
UpdatedPlan.StartedAtUtc = !StartedAtUtc.IsEmpty()
|
||||
? StartedAtUtc
|
||||
: HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(UpdatedPlan.ReferenceUtc);
|
||||
return UpdatedPlan;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRepositoryLibrary::FinalizeCoachActionPlan(
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan,
|
||||
const FHyperTwistTrainingSession& Session,
|
||||
bool bAbortPlan
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlan UpdatedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
if (!UpdatedPlan.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedPlan;
|
||||
}
|
||||
|
||||
UpdatedPlan.SourceSessionId = !Session.TrainingSessionId.IsEmpty()
|
||||
? Session.TrainingSessionId
|
||||
: UpdatedPlan.SourceSessionId;
|
||||
UpdatedPlan.CompletedAtUtc = !Session.EndedAtUtc.IsEmpty()
|
||||
? Session.EndedAtUtc
|
||||
: HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(UpdatedPlan.ReferenceUtc);
|
||||
UpdatedPlan.PlanState = bAbortPlan || Session.SessionState == EHyperTwistTrainingSessionState::Aborted
|
||||
? EHyperTwistTrainingCoachActionPlanState::Aborted
|
||||
: EHyperTwistTrainingCoachActionPlanState::Completed;
|
||||
if (UpdatedPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Completed)
|
||||
{
|
||||
for (FHyperTwistTrainingCoachActionPlanEntry& Entry : UpdatedPlan.Entries)
|
||||
{
|
||||
Entry.bCompleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
return UpdatedPlan;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingCoachActionPlan> UHyperTwistTrainingRepositoryLibrary::ListCoachActionPlansForUser(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId
|
||||
)
|
||||
{
|
||||
TArray<FHyperTwistTrainingCoachActionPlan> ActionPlans;
|
||||
if (UserId.IsEmpty())
|
||||
{
|
||||
return ActionPlans;
|
||||
}
|
||||
|
||||
for (const FHyperTwistTrainingCoachActionPlan& ActionPlan : RepositoryState.StoredCoachActionPlans)
|
||||
{
|
||||
if (ActionPlan.UserId == UserId && ActionPlan.IsStructurallyValid())
|
||||
{
|
||||
ActionPlans.Add(ActionPlan);
|
||||
}
|
||||
}
|
||||
|
||||
ActionPlans.Sort([](const FHyperTwistTrainingCoachActionPlan& Left, const FHyperTwistTrainingCoachActionPlan& Right)
|
||||
{
|
||||
const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
|
||||
Left.CreatedAtUtc,
|
||||
Right.CreatedAtUtc
|
||||
);
|
||||
if (UtcCompare != 0)
|
||||
{
|
||||
return UtcCompare > 0;
|
||||
}
|
||||
|
||||
return Left.PlanId < Right.PlanId;
|
||||
});
|
||||
return ActionPlans;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRepositoryLibrary::TryGetCoachActionPlan(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& PlanId,
|
||||
FHyperTwistTrainingCoachActionPlan& OutActionPlan
|
||||
)
|
||||
{
|
||||
OutActionPlan = FHyperTwistTrainingCoachActionPlan();
|
||||
if (PlanId.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachActionPlan* MatchingPlan =
|
||||
RepositoryState.StoredCoachActionPlans.FindByPredicate(
|
||||
[&PlanId](const FHyperTwistTrainingCoachActionPlan& Candidate)
|
||||
{
|
||||
return Candidate.PlanId == PlanId && Candidate.IsStructurallyValid();
|
||||
}
|
||||
);
|
||||
if (MatchingPlan == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutActionPlan = *MatchingPlan;
|
||||
return true;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertCoachActionPlan(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
if (!NormalizedPlan.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedRepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan* ExistingPlan =
|
||||
UpdatedRepositoryState.StoredCoachActionPlans.FindByPredicate(
|
||||
[&NormalizedPlan](const FHyperTwistTrainingCoachActionPlan& Candidate)
|
||||
{
|
||||
return Candidate.UserId == NormalizedPlan.UserId
|
||||
&& Candidate.PlanId == NormalizedPlan.PlanId;
|
||||
}
|
||||
);
|
||||
if (ExistingPlan != nullptr)
|
||||
{
|
||||
*ExistingPlan = NormalizedPlan;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatedRepositoryState.StoredCoachActionPlans.Add(NormalizedPlan);
|
||||
}
|
||||
|
||||
return UpdatedRepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::BuildCoachSessionQueueState(
|
||||
const FHyperTwistCoachSessionQueueSummary& QueueSummary,
|
||||
const FString& QueueId,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueState QueueState;
|
||||
if (!QueueSummary.IsStructurallyValid())
|
||||
{
|
||||
return QueueState;
|
||||
}
|
||||
|
||||
QueueState.QueueId = QueueId.IsEmpty() ? TEXT("coach_queue_active") : QueueId;
|
||||
QueueState.UserId = QueueSummary.UserId;
|
||||
QueueState.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
|
||||
ReferenceUtc.IsEmpty() ? QueueSummary.ReferenceUtc : ReferenceUtc
|
||||
);
|
||||
QueueState.LastUpdatedAtUtc = QueueState.ReferenceUtc;
|
||||
QueueState.FocusDeckId = QueueSummary.FocusDeckId;
|
||||
QueueState.FocusMethodSegmentId = QueueSummary.FocusMethodSegmentId;
|
||||
QueueState.PrimaryQueueLabel = QueueSummary.PrimaryQueueLabel;
|
||||
QueueState.NextQueueLabel = QueueSummary.NextQueueLabel;
|
||||
QueueState.bHasImmediateQueue = QueueSummary.bHasImmediateQueue;
|
||||
QueueState.bNeedsArchiveAwareSequencing = QueueSummary.bNeedsArchiveAwareSequencing;
|
||||
|
||||
for (const FHyperTwistCoachSessionQueueEntry& SummaryEntry : QueueSummary.Entries)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueStateEntry QueueEntry;
|
||||
QueueEntry.EntryId = SummaryEntry.EntryId;
|
||||
QueueEntry.SourceLabel = SummaryEntry.SourceLabel;
|
||||
QueueEntry.SourceBriefTrainingSessionId = SummaryEntry.SourceBriefTrainingSessionId;
|
||||
QueueEntry.SourceReviewPlanId = SummaryEntry.SourceReviewPlanId;
|
||||
QueueEntry.CarryForwardPlanId = SummaryEntry.CarryForwardPlanId;
|
||||
QueueEntry.RootPlanId = SummaryEntry.RootPlanId;
|
||||
QueueEntry.FollowUpDepth = FMath::Max(0, SummaryEntry.FollowUpDepth);
|
||||
QueueEntry.SlotIndex = SummaryEntry.SlotIndex;
|
||||
QueueEntry.PriorityLabel = StaticEnum<EHyperTwistCoachPriority>()->GetNameStringByValue(
|
||||
static_cast<int64>(SummaryEntry.Priority)
|
||||
);
|
||||
QueueEntry.PriorityScore = SummaryEntry.PriorityScore;
|
||||
QueueEntry.FocusLaneLabel = StaticEnum<EHyperTwistCoachFocusLane>()->GetNameStringByValue(
|
||||
static_cast<int64>(SummaryEntry.FocusLane)
|
||||
);
|
||||
QueueEntry.Headline = SummaryEntry.Headline;
|
||||
QueueEntry.PrimaryActionLabel = SummaryEntry.PrimaryActionLabel;
|
||||
QueueEntry.SuggestedMode = SummaryEntry.SuggestedMode;
|
||||
QueueEntry.SuggestedSelectionPolicy = SummaryEntry.SuggestedSelectionPolicy;
|
||||
QueueEntry.FocusDeckId = SummaryEntry.FocusDeckId;
|
||||
QueueEntry.FocusMethodSegmentId = SummaryEntry.FocusMethodSegmentId;
|
||||
QueueEntry.FocusCaseIds = SummaryEntry.FocusCaseIds;
|
||||
QueueEntry.DeferredLaunchCaseCount = FMath::Max(0, SummaryEntry.DeferredLaunchCaseCount);
|
||||
QueueEntry.bNeedsDeferredLaunchRecovery = SummaryEntry.bNeedsDeferredLaunchRecovery;
|
||||
QueueEntry.bHasTightenedLaunchOverflow = SummaryEntry.bHasTightenedLaunchOverflow;
|
||||
QueueEntry.bRequiresFollowUp = SummaryEntry.bRequiresFollowUp;
|
||||
QueueEntry.bRequiresArchiveRetentionTuning = SummaryEntry.bRequiresArchiveRetentionTuning;
|
||||
QueueEntry.bNeedsCoachReview = SummaryEntry.bNeedsCoachReview;
|
||||
QueueEntry.bNeedsCadenceRecovery = SummaryEntry.bNeedsCadenceRecovery;
|
||||
QueueEntry.bNeedsVarietyRotation = SummaryEntry.bNeedsVarietyRotation;
|
||||
QueueEntry.bPreferRecognitionReplayReview = SummaryEntry.bPreferRecognitionReplayReview;
|
||||
QueueEntry.bPinned = QueueEntry.bRequiresFollowUp || QueueEntry.bRequiresArchiveRetentionTuning;
|
||||
QueueEntry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
|
||||
QueueEntry.WorkFingerprint =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::MakeCoachSessionQueueEntryFingerprint(QueueEntry);
|
||||
QueueState.Entries.Add(QueueEntry);
|
||||
}
|
||||
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::ResolveCoachSessionQueueState(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& ExistingQueueState,
|
||||
const FHyperTwistCoachSessionQueueSummary& QueueSummary,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueState ResolvedQueueState = BuildCoachSessionQueueState(
|
||||
QueueSummary,
|
||||
ExistingQueueState.QueueId,
|
||||
ReferenceUtc
|
||||
);
|
||||
if (!ResolvedQueueState.IsStructurallyValid() || !ExistingQueueState.IsStructurallyValid())
|
||||
{
|
||||
return ResolvedQueueState;
|
||||
}
|
||||
|
||||
for (FHyperTwistTrainingCoachSessionQueueStateEntry& QueueEntry : ResolvedQueueState.Entries)
|
||||
{
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry* ExistingEntry =
|
||||
ExistingQueueState.Entries.FindByPredicate(
|
||||
[&QueueEntry](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
|
||||
{
|
||||
return Candidate.EntryId == QueueEntry.EntryId
|
||||
&& Candidate.WorkFingerprint == QueueEntry.WorkFingerprint
|
||||
&& Candidate.IsStructurallyValid();
|
||||
}
|
||||
);
|
||||
if (ExistingEntry == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
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(
|
||||
ReferenceUtc.IsEmpty() ? ResolvedQueueState.ReferenceUtc : ReferenceUtc
|
||||
);
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(ResolvedQueueState);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::StartCoachSessionQueueEntryBySourceLabel(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FString& SourceLabel,
|
||||
const FString& TrainingSessionId,
|
||||
const FString& StartedAtUtc,
|
||||
const FString& StartActionSourceLabel
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
|
||||
if (!UpdatedQueueState.IsStructurallyValid() || SourceLabel.IsEmpty())
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
const FString ResolvedStartedAtUtc =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(StartedAtUtc);
|
||||
int32 ActivatedIndex = INDEX_NONE;
|
||||
for (int32 EntryIndex = 0; EntryIndex < UpdatedQueueState.Entries.Num(); ++EntryIndex)
|
||||
{
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry& Entry = UpdatedQueueState.Entries[EntryIndex];
|
||||
if (Entry.SourceLabel == SourceLabel
|
||||
&& Entry.EntryState != EHyperTwistTrainingCoachSessionQueueEntryState::Deferred
|
||||
&& !HyperTwistTrainingRepositoryLibraryInternal::IsTerminalCoachQueueEntryState(Entry.EntryState))
|
||||
{
|
||||
ActivatedIndex = EntryIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ActivatedIndex == INDEX_NONE)
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
for (FHyperTwistTrainingCoachSessionQueueStateEntry& Entry : UpdatedQueueState.Entries)
|
||||
{
|
||||
if (Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress)
|
||||
{
|
||||
Entry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
|
||||
Entry.SourceTrainingSessionId.Reset();
|
||||
Entry.StartActionSourceLabel.Reset();
|
||||
Entry.StartedAtUtc.Reset();
|
||||
Entry.bCanStartImmediately = false;
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueStateEntry& ActiveEntry =
|
||||
UpdatedQueueState.Entries[ActivatedIndex];
|
||||
ActiveEntry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::InProgress;
|
||||
ActiveEntry.SourceTrainingSessionId = TrainingSessionId;
|
||||
ActiveEntry.StartActionSourceLabel = !StartActionSourceLabel.IsEmpty()
|
||||
? StartActionSourceLabel
|
||||
: SourceLabel;
|
||||
ActiveEntry.ScheduledAtUtc = !ActiveEntry.ScheduledAtUtc.IsEmpty()
|
||||
? ActiveEntry.ScheduledAtUtc
|
||||
: ResolvedStartedAtUtc;
|
||||
ActiveEntry.DeferredUntilUtc.Reset();
|
||||
ActiveEntry.StartedAtUtc = ResolvedStartedAtUtc;
|
||||
ActiveEntry.bCanStartImmediately = false;
|
||||
UpdatedQueueState.LastUpdatedAtUtc = ResolvedStartedAtUtc;
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(UpdatedQueueState);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::StartCoachSessionQueueEntryById(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FString& EntryId,
|
||||
const FString& TrainingSessionId,
|
||||
const FString& StartedAtUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
|
||||
if (!UpdatedQueueState.IsStructurallyValid() || EntryId.IsEmpty())
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
const FString ResolvedStartedAtUtc =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(StartedAtUtc);
|
||||
const int32 ActivatedIndex = UpdatedQueueState.Entries.IndexOfByPredicate(
|
||||
[&EntryId](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
|
||||
{
|
||||
return Candidate.EntryId == EntryId
|
||||
&& Candidate.EntryState != EHyperTwistTrainingCoachSessionQueueEntryState::Deferred
|
||||
&& !HyperTwistTrainingRepositoryLibraryInternal::IsTerminalCoachQueueEntryState(Candidate.EntryState);
|
||||
}
|
||||
);
|
||||
if (ActivatedIndex == INDEX_NONE)
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
for (FHyperTwistTrainingCoachSessionQueueStateEntry& Entry : UpdatedQueueState.Entries)
|
||||
{
|
||||
if (Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress)
|
||||
{
|
||||
Entry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
|
||||
Entry.SourceTrainingSessionId.Reset();
|
||||
Entry.StartedAtUtc.Reset();
|
||||
Entry.bCanStartImmediately = false;
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueStateEntry& ActiveEntry =
|
||||
UpdatedQueueState.Entries[ActivatedIndex];
|
||||
ActiveEntry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::InProgress;
|
||||
ActiveEntry.SourceTrainingSessionId = TrainingSessionId;
|
||||
ActiveEntry.ScheduledAtUtc = !ActiveEntry.ScheduledAtUtc.IsEmpty()
|
||||
? ActiveEntry.ScheduledAtUtc
|
||||
: ResolvedStartedAtUtc;
|
||||
ActiveEntry.DeferredUntilUtc.Reset();
|
||||
ActiveEntry.StartedAtUtc = ResolvedStartedAtUtc;
|
||||
ActiveEntry.bCanStartImmediately = false;
|
||||
UpdatedQueueState.LastUpdatedAtUtc = ResolvedStartedAtUtc;
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(UpdatedQueueState);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::FinalizeCoachSessionQueueEntry(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FString& TrainingSessionId,
|
||||
const FString& CompletedAtUtc,
|
||||
bool bAbortEntry
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
|
||||
if (!UpdatedQueueState.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
const FString ResolvedCompletedAtUtc =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
|
||||
CompletedAtUtc.IsEmpty() ? UpdatedQueueState.ReferenceUtc : CompletedAtUtc
|
||||
);
|
||||
FHyperTwistTrainingCoachSessionQueueStateEntry* ActiveEntry =
|
||||
UpdatedQueueState.Entries.FindByPredicate(
|
||||
[&TrainingSessionId, &UpdatedQueueState](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
|
||||
{
|
||||
if (!TrainingSessionId.IsEmpty() && Candidate.SourceTrainingSessionId == TrainingSessionId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Candidate.EntryId == UpdatedQueueState.ActiveEntryId
|
||||
&& Candidate.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress;
|
||||
}
|
||||
);
|
||||
if (ActiveEntry == nullptr)
|
||||
{
|
||||
return UpdatedQueueState;
|
||||
}
|
||||
|
||||
ActiveEntry->EntryState = bAbortEntry
|
||||
? EHyperTwistTrainingCoachSessionQueueEntryState::Skipped
|
||||
: EHyperTwistTrainingCoachSessionQueueEntryState::Completed;
|
||||
ActiveEntry->CompletedAtUtc = ResolvedCompletedAtUtc;
|
||||
ActiveEntry->bCanStartImmediately = false;
|
||||
UpdatedQueueState.ActiveEntryId.Reset();
|
||||
UpdatedQueueState.ActiveTrainingSessionId.Reset();
|
||||
UpdatedQueueState.LastUpdatedAtUtc = ResolvedCompletedAtUtc;
|
||||
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& StartActionSourceLabel,
|
||||
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;
|
||||
if (!StartActionSourceLabel.IsEmpty())
|
||||
{
|
||||
EntryToPromote->StartActionSourceLabel = StartActionSourceLabel;
|
||||
}
|
||||
UpdatedQueueState.LastUpdatedAtUtc = ResolvedReferenceUtc;
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(UpdatedQueueState);
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingCoachSessionQueueState> UHyperTwistTrainingRepositoryLibrary::ListCoachSessionQueueStatesForUser(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId
|
||||
)
|
||||
{
|
||||
TArray<FHyperTwistTrainingCoachSessionQueueState> QueueStates;
|
||||
if (UserId.IsEmpty())
|
||||
{
|
||||
return QueueStates;
|
||||
}
|
||||
|
||||
for (const FHyperTwistTrainingCoachSessionQueueState& QueueState : RepositoryState.StoredCoachSessionQueues)
|
||||
{
|
||||
if (QueueState.UserId == UserId && QueueState.IsStructurallyValid())
|
||||
{
|
||||
QueueStates.Add(QueueState);
|
||||
}
|
||||
}
|
||||
|
||||
QueueStates.Sort([](
|
||||
const FHyperTwistTrainingCoachSessionQueueState& Left,
|
||||
const FHyperTwistTrainingCoachSessionQueueState& Right
|
||||
)
|
||||
{
|
||||
const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
|
||||
Left.LastUpdatedAtUtc,
|
||||
Right.LastUpdatedAtUtc
|
||||
);
|
||||
if (UtcCompare != 0)
|
||||
{
|
||||
return UtcCompare > 0;
|
||||
}
|
||||
|
||||
return Left.QueueId < Right.QueueId;
|
||||
});
|
||||
return QueueStates;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingCoachSessionQueueHistoryEntry> UHyperTwistTrainingRepositoryLibrary::ListCoachSessionQueueHistoryEntries(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& QueueId
|
||||
)
|
||||
{
|
||||
TArray<FHyperTwistTrainingCoachSessionQueueHistoryEntry> HistoryEntries;
|
||||
if (UserId.IsEmpty() || QueueId.IsEmpty())
|
||||
{
|
||||
return HistoryEntries;
|
||||
}
|
||||
|
||||
for (const FHyperTwistTrainingCoachSessionQueueHistoryEntry& HistoryEntry : RepositoryState.StoredCoachSessionQueueHistory)
|
||||
{
|
||||
if (HistoryEntry.UserId == UserId
|
||||
&& HistoryEntry.QueueId == QueueId
|
||||
&& HistoryEntry.IsStructurallyValid())
|
||||
{
|
||||
HistoryEntries.Add(HistoryEntry);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryEntries.Sort([](
|
||||
const FHyperTwistTrainingCoachSessionQueueHistoryEntry& Left,
|
||||
const FHyperTwistTrainingCoachSessionQueueHistoryEntry& Right
|
||||
)
|
||||
{
|
||||
const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
|
||||
Left.EventAtUtc,
|
||||
Right.EventAtUtc
|
||||
);
|
||||
if (UtcCompare != 0)
|
||||
{
|
||||
return UtcCompare < 0;
|
||||
}
|
||||
|
||||
return Left.HistoryEntryId < Right.HistoryEntryId;
|
||||
});
|
||||
return HistoryEntries;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRepositoryLibrary::TryGetCoachSessionQueueState(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& QueueId,
|
||||
FHyperTwistTrainingCoachSessionQueueState& OutQueueState
|
||||
)
|
||||
{
|
||||
OutQueueState = FHyperTwistTrainingCoachSessionQueueState();
|
||||
if (UserId.IsEmpty() || QueueId.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachSessionQueueState* MatchingQueueState =
|
||||
RepositoryState.StoredCoachSessionQueues.FindByPredicate(
|
||||
[&UserId, &QueueId](const FHyperTwistTrainingCoachSessionQueueState& Candidate)
|
||||
{
|
||||
return Candidate.UserId == UserId
|
||||
&& Candidate.QueueId == QueueId
|
||||
&& Candidate.IsStructurallyValid();
|
||||
}
|
||||
);
|
||||
if (MatchingQueueState == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutQueueState = *MatchingQueueState;
|
||||
return true;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
|
||||
const FHyperTwistTrainingCoachSessionQueueState NormalizedQueueState =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
|
||||
if (!NormalizedQueueState.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedRepositoryState;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachSessionQueueState* ExistingQueueState =
|
||||
UpdatedRepositoryState.StoredCoachSessionQueues.FindByPredicate(
|
||||
[&NormalizedQueueState](const FHyperTwistTrainingCoachSessionQueueState& Candidate)
|
||||
{
|
||||
return Candidate.UserId == NormalizedQueueState.UserId
|
||||
&& Candidate.QueueId == NormalizedQueueState.QueueId;
|
||||
}
|
||||
);
|
||||
|
||||
for (const FHyperTwistTrainingCoachSessionQueueStateEntry& CurrentEntry : NormalizedQueueState.Entries)
|
||||
{
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry* PreviousEntry = ExistingQueueState != nullptr
|
||||
? ExistingQueueState->Entries.FindByPredicate(
|
||||
[&CurrentEntry](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
|
||||
{
|
||||
return Candidate.EntryId == CurrentEntry.EntryId;
|
||||
}
|
||||
)
|
||||
: nullptr;
|
||||
if (PreviousEntry != nullptr && PreviousEntry->WorkFingerprint != CurrentEntry.WorkFingerprint)
|
||||
{
|
||||
PreviousEntry = nullptr;
|
||||
}
|
||||
if (PreviousEntry != nullptr
|
||||
&& !HyperTwistTrainingRepositoryLibraryInternal::DidCoachSessionQueueEntryChange(
|
||||
*PreviousEntry,
|
||||
CurrentEntry
|
||||
))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachSessionQueueHistoryEntry HistoryEntry =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::BuildCoachSessionQueueHistoryEntry(
|
||||
NormalizedQueueState,
|
||||
CurrentEntry,
|
||||
PreviousEntry
|
||||
);
|
||||
if (!HistoryEntry.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool bAlreadyExists =
|
||||
UpdatedRepositoryState.StoredCoachSessionQueueHistory.ContainsByPredicate(
|
||||
[&HistoryEntry](const FHyperTwistTrainingCoachSessionQueueHistoryEntry& Candidate)
|
||||
{
|
||||
return Candidate.HistoryEntryId == HistoryEntry.HistoryEntryId;
|
||||
}
|
||||
);
|
||||
if (!bAlreadyExists)
|
||||
{
|
||||
UpdatedRepositoryState.StoredCoachSessionQueueHistory.Add(HistoryEntry);
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState* MutableExistingQueueState =
|
||||
UpdatedRepositoryState.StoredCoachSessionQueues.FindByPredicate(
|
||||
[&NormalizedQueueState](const FHyperTwistTrainingCoachSessionQueueState& Candidate)
|
||||
{
|
||||
return Candidate.UserId == NormalizedQueueState.UserId
|
||||
&& Candidate.QueueId == NormalizedQueueState.QueueId;
|
||||
}
|
||||
);
|
||||
if (MutableExistingQueueState != nullptr)
|
||||
{
|
||||
*MutableExistingQueueState = NormalizedQueueState;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatedRepositoryState.StoredCoachSessionQueues.Add(NormalizedQueueState);
|
||||
}
|
||||
|
||||
return UpdatedRepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RemoveCoachSessionQueueState(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& QueueId
|
||||
)
|
||||
{
|
||||
if (UserId.IsEmpty() || QueueId.IsEmpty())
|
||||
{
|
||||
return RepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
|
||||
UpdatedRepositoryState.StoredCoachSessionQueues.RemoveAll(
|
||||
[&UserId, &QueueId](const FHyperTwistTrainingCoachSessionQueueState& Candidate)
|
||||
{
|
||||
return Candidate.UserId == UserId && Candidate.QueueId == QueueId;
|
||||
}
|
||||
);
|
||||
return UpdatedRepositoryState;
|
||||
}
|
||||
|
|
@ -23,4 +23,48 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
const FHyperTwistTrainingReviewPolicy& ReviewPolicy,
|
||||
int32 RequestedMaxCases
|
||||
);
|
||||
|
||||
bool ContainsLaunchBudgetBroadeningHint(const FString& ReasonLine);
|
||||
|
||||
bool ContainsLaunchBudgetTighteningHint(const FString& ReasonLine);
|
||||
|
||||
TArray<FString> DeduplicateCaseIds(const TArray<FString>& CaseIds);
|
||||
|
||||
void ResolveCoachActionPlanPreferenceDefaults(
|
||||
const FHyperTwistCoachBrief& CoachBrief,
|
||||
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
|
||||
EHyperTwistTrainingDeliveryMode& InOutMode,
|
||||
EHyperTwistTrainingSelectionPolicy& InOutSelectionPolicy
|
||||
);
|
||||
|
||||
TArray<FString> ResolveCoachActionPlanFocusCaseIds(
|
||||
const FHyperTwistCoachBrief& CoachBrief,
|
||||
const TArray<FString>& LaunchedCaseIds,
|
||||
int32 PlannedLaunchCaseCount
|
||||
);
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan NormalizeCoachActionPlan(
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan
|
||||
);
|
||||
|
||||
bool IsTerminalCoachQueueEntryState(EHyperTwistTrainingCoachSessionQueueEntryState EntryState);
|
||||
|
||||
FString MakeCoachSessionQueueEntryFingerprint(
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry& Entry
|
||||
);
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState NormalizeCoachSessionQueueState(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState
|
||||
);
|
||||
|
||||
bool DidCoachSessionQueueEntryChange(
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry& PreviousEntry,
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry& CurrentEntry
|
||||
);
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueHistoryEntry BuildCoachSessionQueueHistoryEntry(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry& CurrentEntry,
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry* PreviousEntry
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1633,3 +1633,49 @@ Latest native repository review-family split follow-up later on `2026-06-29`:
|
|||
repository library
|
||||
- the best next bounded seam remains the coach action-plan and
|
||||
coach-session-queue cluster rather than any fresh product-family widening
|
||||
|
||||
Latest native repository coach-workflow split follow-up later on `2026-06-29`:
|
||||
|
||||
- the next same-family native continuation kept the refactor lane disciplined
|
||||
and narrowed the remaining training-repository hotspot without widening
|
||||
simulator or website scope:
|
||||
- moved the coach action-plan lifecycle plus coach session-queue state,
|
||||
mutation, history, and persistence family out of
|
||||
`HyperTwistTrainingRepositoryLibrary.cpp`
|
||||
- kept the broader `DeriveCoachSessionQueueExecutionSummary` method in the
|
||||
main repository unit on purpose because it still depends on a larger helper
|
||||
and reporting surface than this bounded extraction needed
|
||||
- added the extracted implementation unit at
|
||||
`UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibraryCoachWorkflow.cpp`
|
||||
- widened the existing private internal declaration seam at
|
||||
`UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibraryInternal.h`
|
||||
so the extracted workflow family can share only the bounded internal
|
||||
helpers it actually needs
|
||||
- reduced the main repository file to `31,089` lines while the extracted
|
||||
coach-workflow unit now holds `892` lines
|
||||
- the HyperTwist-owned structural loop stayed green on that exact native
|
||||
packet:
|
||||
- `scripts/run-hypertwist-sentrux-source-only.sh`
|
||||
- `Quality: 6233`
|
||||
- all `7` rules pass
|
||||
- `scripts/run-hypertwist-sentrux-gate.sh`
|
||||
- comparison result:
|
||||
- `Quality: 6234 -> 6233`
|
||||
- `Coupling: 0.15 -> 0.15`
|
||||
- `Cycles: 0 -> 0`
|
||||
- `God files: 1 -> 1`
|
||||
- `No degradation detected`
|
||||
- the canonical Windows Unreal proof for that native packet then passed
|
||||
through the maintained reverse-SSH lane:
|
||||
- reran `scripts/run-hypertwist-remote-unreal-build.sh --worktree-root 'C:\htpp'`
|
||||
- `Target is up to date`
|
||||
- `Result: Succeeded`
|
||||
- UnrealBuildTool `Total execution time: 5.10 seconds`
|
||||
- current truthful reading after this native follow-up:
|
||||
- the coach-workflow extraction is now build-proven on the canonical Windows
|
||||
lane and safe to land
|
||||
- vanilla refactoring still is not fully complete as a code-shape task
|
||||
because the training repository library still contains the remaining
|
||||
same-family hotspot
|
||||
- the next worthwhile continuation remains another bounded training-repository
|
||||
seam rather than reopening product topology or renderer widening
|
||||
|
|
|
|||
|
|
@ -2456,3 +2456,48 @@ Latest same-family responsive public-route expansion follow-up on `2026-06-29`:
|
|||
repository library still contains the remaining same-family hotspot
|
||||
- the best next bounded native seam remains the coach action-plan and
|
||||
coach-session-queue cluster instead of any fresh product-family widening
|
||||
|
||||
## Latest native repository coach-workflow split follow-up later on `2026-06-29`
|
||||
|
||||
- the next same-family native continuation then narrowed the remaining
|
||||
training-repository hotspot again without widening simulator or website
|
||||
scope:
|
||||
- the coach action-plan lifecycle plus coach session-queue state, mutation,
|
||||
history, and persistence family was moved out of
|
||||
`HyperTwistTrainingRepositoryLibrary.cpp`
|
||||
- the broader `DeriveCoachSessionQueueExecutionSummary` method was kept in
|
||||
the main repository unit on purpose because it still depends on a larger
|
||||
helper and reporting surface than this bounded extraction needed
|
||||
- the extracted implementation family now lives at
|
||||
`UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibraryCoachWorkflow.cpp`
|
||||
- the existing private helper declaration seam in
|
||||
`UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibraryInternal.h`
|
||||
was widened so the workflow split can reuse only the bounded internal
|
||||
helpers it actually needs
|
||||
- the main repository file now stands at `31,089` lines and the extracted
|
||||
coach-workflow unit stands at `892` lines
|
||||
- the HyperTwist-owned structural loop stayed healthy on that exact native
|
||||
state:
|
||||
- `scripts/run-hypertwist-sentrux-source-only.sh`
|
||||
- `Quality: 6233`
|
||||
- all `7` rules pass
|
||||
- `scripts/run-hypertwist-sentrux-gate.sh`
|
||||
- comparison result:
|
||||
- `Quality: 6234 -> 6233`
|
||||
- `Coupling: 0.15 -> 0.15`
|
||||
- `Cycles: 0 -> 0`
|
||||
- `God files: 1 -> 1`
|
||||
- `No degradation detected`
|
||||
- the canonical Windows Unreal proof for that native split then passed through
|
||||
the maintained reverse-SSH lane:
|
||||
- `scripts/run-hypertwist-remote-unreal-build.sh --worktree-root 'C:\htpp'`
|
||||
- `Target is up to date`
|
||||
- `Result: Succeeded`
|
||||
- UnrealBuildTool `Total execution time: 5.10 seconds`
|
||||
- current truthful reading after this native follow-up:
|
||||
- the coach-workflow extraction is now build-proven on the canonical Windows
|
||||
lane
|
||||
- vanilla refactoring still is not fully complete because the training
|
||||
repository library still contains the remaining same-family hotspot
|
||||
- the next worthwhile continuation remains another bounded training-repository
|
||||
seam instead of fresh product-topology widening
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue