Persist coach session queue state
This commit is contained in:
parent
8d740cb997
commit
8df9776fcf
11 changed files with 1368 additions and 1 deletions
|
|
@ -628,6 +628,16 @@ FHyperTwistTrainingRepositoryState UHyperTwistContractLibrary::MakeSampleTrainin
|
|||
);
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachSessionQueueState CoachSessionQueueState =
|
||||
MakeSampleTrainingCoachSessionQueueState();
|
||||
if (CoachSessionQueueState.IsStructurallyValid())
|
||||
{
|
||||
RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState(
|
||||
RepositoryState,
|
||||
CoachSessionQueueState
|
||||
);
|
||||
}
|
||||
|
||||
return RepositoryState;
|
||||
}
|
||||
|
||||
|
|
@ -686,6 +696,15 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistContractLibrary::MakeSam
|
|||
}
|
||||
RepositoryState.StoredCoachActionPlans.Add(FHyperTwistTrainingCoachActionPlan());
|
||||
|
||||
const FHyperTwistTrainingCoachSessionQueueState CoachSessionQueueState =
|
||||
MakeSampleTrainingCoachSessionQueueState();
|
||||
if (CoachSessionQueueState.IsStructurallyValid())
|
||||
{
|
||||
RepositoryState.StoredCoachSessionQueues.Add(CoachSessionQueueState);
|
||||
RepositoryState.StoredCoachSessionQueues.Add(CoachSessionQueueState);
|
||||
}
|
||||
RepositoryState.StoredCoachSessionQueues.Add(FHyperTwistTrainingCoachSessionQueueState());
|
||||
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveRepositoryIntegrityReport(RepositoryState);
|
||||
}
|
||||
|
||||
|
|
@ -1292,6 +1311,25 @@ FHyperTwistCoachBrief UHyperTwistContractLibrary::MakeSampleTrainingCoachFollowU
|
|||
);
|
||||
}
|
||||
|
||||
FHyperTwistCoachSessionQueueSummary UHyperTwistContractLibrary::MakeSampleTrainingCoachSessionQueueSummary()
|
||||
{
|
||||
return UHyperTwistTrainingCoachLibrary::DeriveCoachSessionQueueSummary(
|
||||
MakeSampleTrainingCoachBrief(),
|
||||
MakeSampleTrainingCoachFollowUpBrief(),
|
||||
MakeSampleTrainingCoachArchivePolicySummary(),
|
||||
MakeSampleTrainingReviewProgramSummary()
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistContractLibrary::MakeSampleTrainingCoachSessionQueueState()
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::BuildCoachSessionQueueState(
|
||||
MakeSampleTrainingCoachSessionQueueSummary(),
|
||||
TEXT("coach_queue_active"),
|
||||
TEXT("2026-04-28T12:12:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistContractLibrary::IsPuzzleStateStructurallyValid(const FHyperTwistPuzzleState& State)
|
||||
{
|
||||
return State.IsStructurallyValid();
|
||||
|
|
|
|||
|
|
@ -569,3 +569,148 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::DeriveCoachFollowUpBrief(
|
|||
|
||||
return Brief;
|
||||
}
|
||||
|
||||
FHyperTwistCoachSessionQueueSummary UHyperTwistTrainingCoachLibrary::DeriveCoachSessionQueueSummary(
|
||||
const FHyperTwistCoachBrief& CurrentBrief,
|
||||
const FHyperTwistCoachBrief& FollowUpBrief,
|
||||
const FHyperTwistTrainingCoachArchivePolicySummary& ArchivePolicySummary,
|
||||
const FHyperTwistTrainingReviewProgramSummary& ReviewProgramSummary)
|
||||
{
|
||||
FHyperTwistCoachSessionQueueSummary Summary;
|
||||
Summary.UserId = !CurrentBrief.UserId.IsEmpty()
|
||||
? CurrentBrief.UserId
|
||||
: FollowUpBrief.UserId;
|
||||
if (Summary.UserId.IsEmpty())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
Summary.ReferenceUtc = !FollowUpBrief.ReferenceUtc.IsEmpty()
|
||||
? FollowUpBrief.ReferenceUtc
|
||||
: (!CurrentBrief.ReferenceUtc.IsEmpty() ? CurrentBrief.ReferenceUtc : ArchivePolicySummary.ReferenceUtc);
|
||||
Summary.FocusDeckId = !FollowUpBrief.FocusDeckId.IsEmpty()
|
||||
? FollowUpBrief.FocusDeckId
|
||||
: (!CurrentBrief.FocusDeckId.IsEmpty() ? CurrentBrief.FocusDeckId : ArchivePolicySummary.FocusDeckId);
|
||||
Summary.FocusMethodSegmentId = !FollowUpBrief.FocusMethodSegmentId.IsEmpty()
|
||||
? FollowUpBrief.FocusMethodSegmentId
|
||||
: (!CurrentBrief.FocusMethodSegmentId.IsEmpty()
|
||||
? CurrentBrief.FocusMethodSegmentId
|
||||
: ArchivePolicySummary.FocusMethodSegmentId);
|
||||
|
||||
auto AddBriefEntry = [&Summary](
|
||||
const FString& EntryId,
|
||||
const FString& SourceLabel,
|
||||
const FHyperTwistCoachBrief& Brief,
|
||||
const bool bRequiresFollowUp,
|
||||
const bool bRequiresArchiveRetentionTuning)
|
||||
{
|
||||
if (Brief.UserId.IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FHyperTwistCoachSessionQueueEntry Entry;
|
||||
Entry.EntryId = EntryId;
|
||||
Entry.SourceLabel = SourceLabel;
|
||||
Entry.Priority = Brief.Priority;
|
||||
Entry.PriorityScore = Brief.PriorityScore;
|
||||
Entry.Headline = Brief.Headline;
|
||||
Entry.SuggestedMode = Brief.SuggestedMode;
|
||||
Entry.SuggestedSelectionPolicy = Brief.SuggestedSelectionPolicy;
|
||||
Entry.FocusDeckId = Brief.FocusDeckId;
|
||||
Entry.FocusMethodSegmentId = Brief.FocusMethodSegmentId;
|
||||
Entry.FocusCaseIds = Brief.FocusCaseIds;
|
||||
Entry.bRequiresFollowUp = bRequiresFollowUp;
|
||||
Entry.bRequiresArchiveRetentionTuning = bRequiresArchiveRetentionTuning;
|
||||
Summary.Entries.Add(Entry);
|
||||
};
|
||||
|
||||
AddBriefEntry(
|
||||
TEXT("coach_queue_primary"),
|
||||
TEXT("primary-brief"),
|
||||
CurrentBrief,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
if (FollowUpBrief.UserId == Summary.UserId && FollowUpBrief.FocusCaseIds.Num() > 0)
|
||||
{
|
||||
AddBriefEntry(
|
||||
TEXT("coach_queue_follow_up"),
|
||||
TEXT("follow-up-brief"),
|
||||
FollowUpBrief,
|
||||
true,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (ArchivePolicySummary.IsStructurallyValid()
|
||||
&& (ArchivePolicySummary.bNeedsArchiveRetentionTuning
|
||||
|| ArchivePolicySummary.RetainedCarryForwardCaseCount > 0))
|
||||
{
|
||||
FHyperTwistCoachBrief ArchiveBrief;
|
||||
ArchiveBrief.UserId = Summary.UserId;
|
||||
ArchiveBrief.ReferenceUtc = Summary.ReferenceUtc;
|
||||
ArchiveBrief.PriorityScore = FMath::Clamp(
|
||||
55.0f + ArchivePolicySummary.ArchiveRetentionTuningScore,
|
||||
20.0f,
|
||||
100.0f
|
||||
);
|
||||
ArchiveBrief.Priority = HyperTwistTrainingCoachLibraryInternal::PriorityFromScore(ArchiveBrief.PriorityScore);
|
||||
ArchiveBrief.Headline = ArchivePolicySummary.bNeedsArchiveRetentionTuning
|
||||
? TEXT("Archive retention queue needs attention")
|
||||
: TEXT("Carry-forward archive queue is still active");
|
||||
ArchiveBrief.SuggestedMode = ArchivePolicySummary.DominantSuggestedMode;
|
||||
ArchiveBrief.SuggestedSelectionPolicy = ArchivePolicySummary.DominantSelectionPolicy;
|
||||
ArchiveBrief.FocusDeckId = ArchivePolicySummary.FocusDeckId;
|
||||
ArchiveBrief.FocusMethodSegmentId = ArchivePolicySummary.FocusMethodSegmentId;
|
||||
ArchiveBrief.FocusCaseIds = ArchivePolicySummary.ReopenRiskArchivedCaseIds.Num() > 0
|
||||
? ArchivePolicySummary.ReopenRiskArchivedCaseIds
|
||||
: (ArchivePolicySummary.ProvisionalArchivedCaseIds.Num() > 0
|
||||
? ArchivePolicySummary.ProvisionalArchivedCaseIds
|
||||
: ArchivePolicySummary.RetainedCarryForwardCaseIds);
|
||||
AddBriefEntry(
|
||||
TEXT("coach_queue_archive"),
|
||||
TEXT("archive-policy"),
|
||||
ArchiveBrief,
|
||||
false,
|
||||
ArchivePolicySummary.bNeedsArchiveRetentionTuning
|
||||
);
|
||||
}
|
||||
|
||||
Summary.Entries.Sort([](const FHyperTwistCoachSessionQueueEntry& Left, const FHyperTwistCoachSessionQueueEntry& Right)
|
||||
{
|
||||
if (!FMath::IsNearlyEqual(Left.PriorityScore, Right.PriorityScore))
|
||||
{
|
||||
return Left.PriorityScore > Right.PriorityScore;
|
||||
}
|
||||
return Left.EntryId < Right.EntryId;
|
||||
});
|
||||
|
||||
for (int32 Index = 0; Index < Summary.Entries.Num(); ++Index)
|
||||
{
|
||||
Summary.Entries[Index].SlotIndex = Index;
|
||||
Summary.FollowUpEntryCount += Summary.Entries[Index].bRequiresFollowUp ? 1 : 0;
|
||||
Summary.ArchiveRetentionEntryCount += Summary.Entries[Index].bRequiresArchiveRetentionTuning ? 1 : 0;
|
||||
Summary.ReviewBacklogEntryCount += Summary.Entries[Index].SourceLabel == TEXT("primary-brief")
|
||||
&& CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::ReviewBacklog
|
||||
? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
Summary.TotalEntryCount = Summary.Entries.Num();
|
||||
Summary.PrimaryQueueLabel = Summary.Entries.Num() > 0
|
||||
? Summary.Entries[0].SourceLabel
|
||||
: FString();
|
||||
Summary.NextQueueLabel = Summary.Entries.Num() > 1
|
||||
? Summary.Entries[1].SourceLabel
|
||||
: FString();
|
||||
Summary.bHasImmediateQueue = Summary.TotalEntryCount > 0;
|
||||
Summary.bNeedsArchiveAwareSequencing =
|
||||
ArchivePolicySummary.bNeedsArchiveRetentionTuning
|
||||
|| Summary.ArchiveRetentionEntryCount > 0
|
||||
|| ArchivePolicySummary.RetainedCarryForwardCaseCount > 0
|
||||
|| ReviewProgramSummary.bNeedsCoachIntervention;
|
||||
|
||||
return Summary;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -301,6 +301,11 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
return UserId + CaseKeySeparator + PlanId;
|
||||
}
|
||||
|
||||
FString MakeCoachSessionQueueKey(const FString& UserId, const FString& QueueId)
|
||||
{
|
||||
return UserId + CaseKeySeparator + QueueId;
|
||||
}
|
||||
|
||||
TArray<FString> DeduplicateCaseIds(const TArray<FString>& CaseIds)
|
||||
{
|
||||
TArray<FString> UniqueCaseIds;
|
||||
|
|
@ -475,6 +480,223 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
return NormalizedPlan;
|
||||
}
|
||||
|
||||
bool IsTerminalCoachQueueEntryState(const EHyperTwistTrainingCoachSessionQueueEntryState EntryState)
|
||||
{
|
||||
return EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Completed
|
||||
|| EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Skipped
|
||||
|| EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Expired;
|
||||
}
|
||||
|
||||
FString MakeCoachSessionQueueEntryFingerprint(
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry& Entry
|
||||
)
|
||||
{
|
||||
const FString CaseFingerprint = FString::Join(Entry.FocusCaseIds, TEXT("|"));
|
||||
return FString::Printf(
|
||||
TEXT("%s|%s|%s|%s|%d|%d|%s"),
|
||||
*Entry.SourceLabel,
|
||||
*Entry.FocusDeckId,
|
||||
*Entry.FocusMethodSegmentId,
|
||||
*Entry.Headline,
|
||||
static_cast<int32>(Entry.SuggestedMode),
|
||||
static_cast<int32>(Entry.SuggestedSelectionPolicy),
|
||||
*CaseFingerprint
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState NormalizeCoachSessionQueueState(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueState NormalizedState = QueueState;
|
||||
if (NormalizedState.QueueId.IsEmpty())
|
||||
{
|
||||
NormalizedState.QueueId = TEXT("coach_queue_active");
|
||||
}
|
||||
|
||||
NormalizedState.ReferenceUtc = ResolveReferenceUtc(NormalizedState.ReferenceUtc);
|
||||
NormalizedState.LastUpdatedAtUtc = ResolveReferenceUtc(
|
||||
NormalizedState.LastUpdatedAtUtc.IsEmpty()
|
||||
? NormalizedState.ReferenceUtc
|
||||
: NormalizedState.LastUpdatedAtUtc
|
||||
);
|
||||
|
||||
TMap<FString, int32> EntryIndexById;
|
||||
TArray<FHyperTwistTrainingCoachSessionQueueStateEntry> NormalizedEntries;
|
||||
for (const FHyperTwistTrainingCoachSessionQueueStateEntry& Entry : QueueState.Entries)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueStateEntry NormalizedEntry = Entry;
|
||||
NormalizedEntry.FocusCaseIds = DeduplicateCaseIds(Entry.FocusCaseIds);
|
||||
if (NormalizedEntry.WorkFingerprint.IsEmpty())
|
||||
{
|
||||
NormalizedEntry.WorkFingerprint = MakeCoachSessionQueueEntryFingerprint(NormalizedEntry);
|
||||
}
|
||||
NormalizedEntry.bPinned = NormalizedEntry.bPinned
|
||||
|| NormalizedEntry.bRequiresFollowUp
|
||||
|| NormalizedEntry.bRequiresArchiveRetentionTuning;
|
||||
if (NormalizedEntry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Ready
|
||||
|| NormalizedEntry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress)
|
||||
{
|
||||
NormalizedEntry.ScheduledAtUtc = !NormalizedEntry.ScheduledAtUtc.IsEmpty()
|
||||
? NormalizedEntry.ScheduledAtUtc
|
||||
: NormalizedState.ReferenceUtc;
|
||||
}
|
||||
if (IsTerminalCoachQueueEntryState(NormalizedEntry.EntryState))
|
||||
{
|
||||
NormalizedEntry.bCanStartImmediately = false;
|
||||
}
|
||||
if (!NormalizedEntry.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const int32* ExistingIndex = EntryIndexById.Find(NormalizedEntry.EntryId))
|
||||
{
|
||||
NormalizedEntries[*ExistingIndex] = NormalizedEntry;
|
||||
}
|
||||
else
|
||||
{
|
||||
EntryIndexById.Add(NormalizedEntry.EntryId, NormalizedEntries.Add(NormalizedEntry));
|
||||
}
|
||||
}
|
||||
|
||||
NormalizedEntries.Sort([](
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry& Left,
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry& Right
|
||||
)
|
||||
{
|
||||
if (Left.SlotIndex != Right.SlotIndex)
|
||||
{
|
||||
return Left.SlotIndex < Right.SlotIndex;
|
||||
}
|
||||
if (!FMath::IsNearlyEqual(Left.PriorityScore, Right.PriorityScore))
|
||||
{
|
||||
return Left.PriorityScore > Right.PriorityScore;
|
||||
}
|
||||
|
||||
return Left.EntryId < Right.EntryId;
|
||||
});
|
||||
|
||||
NormalizedState.ActiveEntryId.Reset();
|
||||
NormalizedState.ActiveTrainingSessionId.Reset();
|
||||
NormalizedState.PendingEntryCount = 0;
|
||||
NormalizedState.ReadyEntryCount = 0;
|
||||
NormalizedState.InProgressEntryCount = 0;
|
||||
NormalizedState.CompletedEntryCount = 0;
|
||||
NormalizedState.SkippedEntryCount = 0;
|
||||
NormalizedState.ExpiredEntryCount = 0;
|
||||
|
||||
bool bHasInProgressEntry = false;
|
||||
bool bHasReadyEntry = false;
|
||||
for (int32 EntryIndex = 0; EntryIndex < NormalizedEntries.Num(); ++EntryIndex)
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueStateEntry& Entry = NormalizedEntries[EntryIndex];
|
||||
Entry.SlotIndex = EntryIndex;
|
||||
|
||||
if (Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress)
|
||||
{
|
||||
if (bHasInProgressEntry)
|
||||
{
|
||||
Entry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
|
||||
Entry.SourceTrainingSessionId.Reset();
|
||||
Entry.StartedAtUtc.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
bHasInProgressEntry = true;
|
||||
Entry.ScheduledAtUtc = !Entry.ScheduledAtUtc.IsEmpty() ? Entry.ScheduledAtUtc : NormalizedState.ReferenceUtc;
|
||||
Entry.bCanStartImmediately = false;
|
||||
NormalizedState.ActiveEntryId = Entry.EntryId;
|
||||
NormalizedState.ActiveTrainingSessionId = Entry.SourceTrainingSessionId;
|
||||
}
|
||||
}
|
||||
|
||||
if (Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Ready)
|
||||
{
|
||||
if (bHasInProgressEntry || bHasReadyEntry)
|
||||
{
|
||||
Entry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
|
||||
Entry.bCanStartImmediately = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bHasReadyEntry = true;
|
||||
Entry.ScheduledAtUtc = !Entry.ScheduledAtUtc.IsEmpty() ? Entry.ScheduledAtUtc : NormalizedState.ReferenceUtc;
|
||||
Entry.bCanStartImmediately = true;
|
||||
NormalizedState.ActiveEntryId = Entry.EntryId;
|
||||
}
|
||||
}
|
||||
|
||||
switch (Entry.EntryState)
|
||||
{
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::Pending:
|
||||
NormalizedState.PendingEntryCount += 1;
|
||||
break;
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::Ready:
|
||||
NormalizedState.ReadyEntryCount += 1;
|
||||
break;
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::InProgress:
|
||||
NormalizedState.InProgressEntryCount += 1;
|
||||
break;
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::Completed:
|
||||
NormalizedState.CompletedEntryCount += 1;
|
||||
break;
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::Skipped:
|
||||
NormalizedState.SkippedEntryCount += 1;
|
||||
break;
|
||||
case EHyperTwistTrainingCoachSessionQueueEntryState::Expired:
|
||||
NormalizedState.ExpiredEntryCount += 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bHasInProgressEntry && !bHasReadyEntry)
|
||||
{
|
||||
for (FHyperTwistTrainingCoachSessionQueueStateEntry& Entry : NormalizedEntries)
|
||||
{
|
||||
if (!IsTerminalCoachQueueEntryState(Entry.EntryState))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
Entry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Ready;
|
||||
Entry.ScheduledAtUtc = !Entry.ScheduledAtUtc.IsEmpty() ? Entry.ScheduledAtUtc : NormalizedState.ReferenceUtc;
|
||||
Entry.bCanStartImmediately = true;
|
||||
NormalizedState.ReadyEntryCount += 1;
|
||||
NormalizedState.ActiveEntryId = Entry.EntryId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NormalizedState.TotalEntryCount = NormalizedEntries.Num();
|
||||
NormalizedState.bHasImmediateQueue = NormalizedState.ReadyEntryCount > 0
|
||||
|| NormalizedState.InProgressEntryCount > 0;
|
||||
NormalizedState.Entries = NormalizedEntries;
|
||||
return NormalizedState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCaseLearnerState DeriveLearnerStateFromReviewHistory(
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
|
|
@ -2680,6 +2902,29 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
SeenCoachActionPlanKeys.Add(ActionPlanKey);
|
||||
}
|
||||
|
||||
Report.TotalCoachSessionQueueCount = RepositoryState.StoredCoachSessionQueues.Num();
|
||||
TSet<FString> SeenCoachSessionQueueKeys;
|
||||
for (const FHyperTwistTrainingCoachSessionQueueState& QueueState : RepositoryState.StoredCoachSessionQueues)
|
||||
{
|
||||
if (!QueueState.IsStructurallyValid())
|
||||
{
|
||||
Report.InvalidCoachSessionQueueCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString QueueKey = HyperTwistTrainingRepositoryLibraryInternal::MakeCoachSessionQueueKey(
|
||||
QueueState.UserId,
|
||||
QueueState.QueueId
|
||||
);
|
||||
if (SeenCoachSessionQueueKeys.Contains(QueueKey))
|
||||
{
|
||||
Report.DuplicateCoachSessionQueueCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
SeenCoachSessionQueueKeys.Add(QueueKey);
|
||||
}
|
||||
|
||||
if (Report.InvalidRunRecordCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
|
|
@ -2781,6 +3026,20 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
TEXT("normalize-and-deduplicate-coach-action-plans")
|
||||
);
|
||||
}
|
||||
if (Report.InvalidCoachSessionQueueCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("drop-invalid-coach-session-queues")
|
||||
);
|
||||
}
|
||||
if (Report.DuplicateCoachSessionQueueCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("normalize-and-deduplicate-coach-session-queues")
|
||||
);
|
||||
}
|
||||
|
||||
return Report;
|
||||
}
|
||||
|
|
@ -3089,6 +3348,60 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RepairR
|
|||
return Left.PlanId < Right.PlanId;
|
||||
});
|
||||
|
||||
TMap<FString, int32> CoachSessionQueueIndexByKey;
|
||||
for (const FHyperTwistTrainingCoachSessionQueueState& QueueState : RepositoryState.StoredCoachSessionQueues)
|
||||
{
|
||||
if (!QueueState.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachSessionQueueState NormalizedQueueState =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
|
||||
if (!NormalizedQueueState.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString QueueKey = HyperTwistTrainingRepositoryLibraryInternal::MakeCoachSessionQueueKey(
|
||||
NormalizedQueueState.UserId,
|
||||
NormalizedQueueState.QueueId
|
||||
);
|
||||
if (const int32* ExistingIndex = CoachSessionQueueIndexByKey.Find(QueueKey))
|
||||
{
|
||||
RepairedState.StoredCoachSessionQueues[*ExistingIndex] = NormalizedQueueState;
|
||||
}
|
||||
else
|
||||
{
|
||||
CoachSessionQueueIndexByKey.Add(
|
||||
QueueKey,
|
||||
RepairedState.StoredCoachSessionQueues.Add(NormalizedQueueState)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RepairedState.StoredCoachSessionQueues.Sort([](
|
||||
const FHyperTwistTrainingCoachSessionQueueState& Left,
|
||||
const FHyperTwistTrainingCoachSessionQueueState& Right
|
||||
)
|
||||
{
|
||||
if (Left.UserId != Right.UserId)
|
||||
{
|
||||
return Left.UserId < Right.UserId;
|
||||
}
|
||||
|
||||
const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
|
||||
Left.LastUpdatedAtUtc,
|
||||
Right.LastUpdatedAtUtc
|
||||
);
|
||||
if (UtcCompare != 0)
|
||||
{
|
||||
return UtcCompare > 0;
|
||||
}
|
||||
|
||||
return Left.QueueId < Right.QueueId;
|
||||
});
|
||||
|
||||
return RepairedState;
|
||||
}
|
||||
|
||||
|
|
@ -4997,6 +5310,311 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertC
|
|||
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.SlotIndex = SummaryEntry.SlotIndex;
|
||||
QueueEntry.PriorityLabel = StaticEnum<EHyperTwistCoachPriority>()->GetNameStringByValue(
|
||||
static_cast<int64>(SummaryEntry.Priority)
|
||||
);
|
||||
QueueEntry.PriorityScore = SummaryEntry.PriorityScore;
|
||||
QueueEntry.Headline = SummaryEntry.Headline;
|
||||
QueueEntry.SuggestedMode = SummaryEntry.SuggestedMode;
|
||||
QueueEntry.SuggestedSelectionPolicy = SummaryEntry.SuggestedSelectionPolicy;
|
||||
QueueEntry.FocusDeckId = SummaryEntry.FocusDeckId;
|
||||
QueueEntry.FocusMethodSegmentId = SummaryEntry.FocusMethodSegmentId;
|
||||
QueueEntry.FocusCaseIds = SummaryEntry.FocusCaseIds;
|
||||
QueueEntry.bRequiresFollowUp = SummaryEntry.bRequiresFollowUp;
|
||||
QueueEntry.bRequiresArchiveRetentionTuning = SummaryEntry.bRequiresArchiveRetentionTuning;
|
||||
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.StartedAtUtc = ExistingEntry->StartedAtUtc;
|
||||
QueueEntry.CompletedAtUtc = ExistingEntry->CompletedAtUtc;
|
||||
QueueEntry.bPinned = QueueEntry.bPinned || ExistingEntry->bPinned;
|
||||
QueueEntry.bCanStartImmediately = ExistingEntry->bCanStartImmediately;
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
{
|
||||
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
|
||||
&& !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.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.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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState* ExistingQueueState =
|
||||
UpdatedRepositoryState.StoredCoachSessionQueues.FindByPredicate(
|
||||
[&NormalizedQueueState](const FHyperTwistTrainingCoachSessionQueueState& Candidate)
|
||||
{
|
||||
return Candidate.UserId == NormalizedQueueState.UserId
|
||||
&& Candidate.QueueId == NormalizedQueueState.QueueId;
|
||||
}
|
||||
);
|
||||
if (ExistingQueueState != nullptr)
|
||||
{
|
||||
*ExistingQueueState = NormalizedQueueState;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatedRepositoryState.StoredCoachSessionQueues.Add(NormalizedQueueState);
|
||||
}
|
||||
|
||||
return UpdatedRepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlanSummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionPlanSummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
|
|
|
|||
|
|
@ -358,6 +358,29 @@ FHyperTwistCoachBrief UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachF
|
|||
return FHyperTwistCoachBrief();
|
||||
}
|
||||
|
||||
FHyperTwistCoachSessionQueueSummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachSessionQueueSummary(
|
||||
UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveCoachSessionQueueSummary();
|
||||
}
|
||||
|
||||
return FHyperTwistCoachSessionQueueSummary();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachSessionQueueState(
|
||||
UObject* WorldContextObject
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveCoachSessionQueueState();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingCoachSessionQueueState();
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingMethodSegment> UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingLearnerMethodSegments(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
|
|
@ -547,6 +570,22 @@ bool UHyperTwistTrainingRuntimeLibrary::TryGetStoredTrainingCoachActionPlan(
|
|||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::TryGetStoredTrainingCoachSessionQueueState(
|
||||
UObject* WorldContextObject,
|
||||
const FString& UserId,
|
||||
const FString& QueueId,
|
||||
FHyperTwistTrainingCoachSessionQueueState& OutQueueState
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->TryGetStoredCoachSessionQueueState(UserId, QueueId, OutQueueState);
|
||||
}
|
||||
|
||||
OutQueueState = FHyperTwistTrainingCoachSessionQueueState();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::TryGetStoredTrainingSessionTemplate(
|
||||
UObject* WorldContextObject,
|
||||
const FString& UserId,
|
||||
|
|
@ -633,6 +672,19 @@ bool UHyperTwistTrainingRuntimeLibrary::SaveMethodDrillBatch(
|
|||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::SaveCoachSessionQueueState(
|
||||
UObject* WorldContextObject,
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->SaveCoachSessionQueueState(QueueState);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::RemoveMethodDrillBatch(
|
||||
UObject* WorldContextObject,
|
||||
const FString& UserId,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@
|
|||
#include "HyperTwistTraining/HyperTwistTrainingPersistenceLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"
|
||||
|
||||
namespace HyperTwistTrainingSubsystemInternal
|
||||
{
|
||||
const TCHAR* ActiveCoachSessionQueueId = TEXT("coach_queue_active");
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::HasActiveRun() const
|
||||
{
|
||||
return bHasActiveRun && ActiveRunState.IsStructurallyValid();
|
||||
|
|
@ -158,6 +163,16 @@ FHyperTwistCoachBrief UHyperTwistTrainingSubsystem::GetActiveCoachFollowUpBrief(
|
|||
return ActiveCoachFollowUpBrief;
|
||||
}
|
||||
|
||||
FHyperTwistCoachSessionQueueSummary UHyperTwistTrainingSubsystem::GetActiveCoachSessionQueueSummary() const
|
||||
{
|
||||
return ActiveCoachSessionQueueSummary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingSubsystem::GetActiveCoachSessionQueueState() const
|
||||
{
|
||||
return ActiveCoachSessionQueueState;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingMethodSegment> UHyperTwistTrainingSubsystem::GetActiveLearnerMethodSegments() const
|
||||
{
|
||||
return ActiveLearnerMethodSegments;
|
||||
|
|
@ -293,6 +308,20 @@ bool UHyperTwistTrainingSubsystem::TryGetStoredCoachActionPlan(
|
|||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::TryGetStoredCoachSessionQueueState(
|
||||
const FString& UserId,
|
||||
const FString& QueueId,
|
||||
FHyperTwistTrainingCoachSessionQueueState& OutQueueState
|
||||
) const
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::TryGetCoachSessionQueueState(
|
||||
TrainingRepositoryState,
|
||||
UserId,
|
||||
QueueId,
|
||||
OutQueueState
|
||||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::TryGetStoredTrainingSessionTemplate(
|
||||
const FString& UserId,
|
||||
const FString& TemplateId,
|
||||
|
|
@ -394,6 +423,23 @@ bool UHyperTwistTrainingSubsystem::SaveMethodDrillBatch(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::SaveCoachSessionQueueState(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState
|
||||
)
|
||||
{
|
||||
if (!QueueState.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState(
|
||||
TrainingRepositoryState,
|
||||
QueueState
|
||||
);
|
||||
RefreshRepositoryViews();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::RemoveMethodDrillBatch(
|
||||
const FString& UserId,
|
||||
const FString& BatchId
|
||||
|
|
@ -819,6 +865,7 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachRecommendedR
|
|||
}
|
||||
|
||||
const FString UserId = ActiveRunState.Session.UserId.IsEmpty() ? TEXT("local-user") : ActiveRunState.Session.UserId;
|
||||
const FHyperTwistTrainingCoachSessionQueueState QueueStateBeforeStart = ActiveCoachSessionQueueState;
|
||||
const FHyperTwistTrainingCoachActionPlan DraftActionPlan =
|
||||
UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan(
|
||||
ActiveCoachBrief,
|
||||
|
|
@ -845,6 +892,24 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachRecommendedR
|
|||
TrainingRepositoryState,
|
||||
ActiveCoachActionPlan
|
||||
);
|
||||
if (QueueStateBeforeStart.IsStructurallyValid())
|
||||
{
|
||||
const FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
|
||||
UHyperTwistTrainingRepositoryLibrary::StartCoachSessionQueueEntryBySourceLabel(
|
||||
QueueStateBeforeStart,
|
||||
TEXT("primary-brief"),
|
||||
RunState.Session.TrainingSessionId,
|
||||
RunState.Session.StartedAtUtc
|
||||
);
|
||||
if (UpdatedQueueState.IsStructurallyValid())
|
||||
{
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState(
|
||||
TrainingRepositoryState,
|
||||
UpdatedQueueState
|
||||
);
|
||||
ActiveCoachSessionQueueState = UpdatedQueueState;
|
||||
}
|
||||
}
|
||||
RefreshRepositoryViews();
|
||||
return RunState;
|
||||
}
|
||||
|
|
@ -861,6 +926,7 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachFollowUpRun(
|
|||
}
|
||||
|
||||
const FHyperTwistCoachBrief FollowUpBrief = ActiveCoachFollowUpBrief;
|
||||
const FHyperTwistTrainingCoachSessionQueueState QueueStateBeforeStart = ActiveCoachSessionQueueState;
|
||||
const FHyperTwistTrainingCoachActionPlan DraftActionPlan =
|
||||
UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan(
|
||||
FollowUpBrief,
|
||||
|
|
@ -887,6 +953,24 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachFollowUpRun(
|
|||
TrainingRepositoryState,
|
||||
ActiveCoachActionPlan
|
||||
);
|
||||
if (QueueStateBeforeStart.IsStructurallyValid())
|
||||
{
|
||||
const FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
|
||||
UHyperTwistTrainingRepositoryLibrary::StartCoachSessionQueueEntryBySourceLabel(
|
||||
QueueStateBeforeStart,
|
||||
TEXT("follow-up-brief"),
|
||||
RunState.Session.TrainingSessionId,
|
||||
RunState.Session.StartedAtUtc
|
||||
);
|
||||
if (UpdatedQueueState.IsStructurallyValid())
|
||||
{
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState(
|
||||
TrainingRepositoryState,
|
||||
UpdatedQueueState
|
||||
);
|
||||
ActiveCoachSessionQueueState = UpdatedQueueState;
|
||||
}
|
||||
}
|
||||
RefreshRepositoryViews();
|
||||
return RunState;
|
||||
}
|
||||
|
|
@ -1134,6 +1218,22 @@ FHyperTwistTrainingSession UHyperTwistTrainingSubsystem::CompleteActiveRun(bool
|
|||
ActiveCoachActionPlan
|
||||
);
|
||||
}
|
||||
if (ActiveCoachSessionQueueState.IsStructurallyValid())
|
||||
{
|
||||
ActiveCoachSessionQueueState = UHyperTwistTrainingRepositoryLibrary::FinalizeCoachSessionQueueEntry(
|
||||
ActiveCoachSessionQueueState,
|
||||
ActiveRunState.Session.TrainingSessionId,
|
||||
ActiveRunState.Session.EndedAtUtc,
|
||||
bAbortSession
|
||||
);
|
||||
if (ActiveCoachSessionQueueState.IsStructurallyValid())
|
||||
{
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState(
|
||||
TrainingRepositoryState,
|
||||
ActiveCoachSessionQueueState
|
||||
);
|
||||
}
|
||||
}
|
||||
RefreshRepositoryViews();
|
||||
return ActiveRunState.Session;
|
||||
}
|
||||
|
|
@ -1213,6 +1313,8 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
|
|||
ActiveCoachCarryForwardPolicySummary = FHyperTwistTrainingCoachCarryForwardPolicySummary();
|
||||
ActiveCoachArchivePolicySummary = FHyperTwistTrainingCoachArchivePolicySummary();
|
||||
ActiveCoachFollowUpBrief = FHyperTwistCoachBrief();
|
||||
ActiveCoachSessionQueueSummary = FHyperTwistCoachSessionQueueSummary();
|
||||
ActiveCoachSessionQueueState = FHyperTwistTrainingCoachSessionQueueState();
|
||||
ActiveLearnerMethodSegments.Reset();
|
||||
ActiveLearnerPresets.Reset();
|
||||
ActiveTrainingSessionTemplates.Reset();
|
||||
|
|
@ -1382,6 +1484,44 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
|
|||
ActiveCoachActionPlanOutcomeSummary
|
||||
)
|
||||
: FHyperTwistCoachBrief();
|
||||
ActiveCoachSessionQueueSummary = UHyperTwistTrainingCoachLibrary::DeriveCoachSessionQueueSummary(
|
||||
ActiveCoachBrief,
|
||||
ActiveCoachFollowUpBrief,
|
||||
ActiveCoachArchivePolicySummary,
|
||||
ActiveReviewProgramSummary
|
||||
);
|
||||
if (ActiveCoachSessionQueueSummary.IsStructurallyValid())
|
||||
{
|
||||
FHyperTwistTrainingCoachSessionQueueState StoredQueueState;
|
||||
const bool bHasStoredQueueState = UHyperTwistTrainingRepositoryLibrary::TryGetCoachSessionQueueState(
|
||||
TrainingRepositoryState,
|
||||
ActiveCoachSessionQueueSummary.UserId,
|
||||
HyperTwistTrainingSubsystemInternal::ActiveCoachSessionQueueId,
|
||||
StoredQueueState
|
||||
);
|
||||
ActiveCoachSessionQueueState = bHasStoredQueueState
|
||||
? UHyperTwistTrainingRepositoryLibrary::ResolveCoachSessionQueueState(
|
||||
StoredQueueState,
|
||||
ActiveCoachSessionQueueSummary,
|
||||
ActiveLearnerDeckStateSummary.ReferenceUtc
|
||||
)
|
||||
: UHyperTwistTrainingRepositoryLibrary::BuildCoachSessionQueueState(
|
||||
ActiveCoachSessionQueueSummary,
|
||||
HyperTwistTrainingSubsystemInternal::ActiveCoachSessionQueueId,
|
||||
ActiveLearnerDeckStateSummary.ReferenceUtc
|
||||
);
|
||||
if (ActiveCoachSessionQueueState.IsStructurallyValid())
|
||||
{
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState(
|
||||
TrainingRepositoryState,
|
||||
ActiveCoachSessionQueueState
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveCoachSessionQueueState = FHyperTwistTrainingCoachSessionQueueState();
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingSessionTemplate> UHyperTwistTrainingSubsystem::BuildTrainingSessionTemplatesForUser(
|
||||
|
|
|
|||
|
|
@ -206,6 +206,12 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistCoachBrief MakeSampleTrainingCoachFollowUpBrief();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistCoachSessionQueueSummary MakeSampleTrainingCoachSessionQueueSummary();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistTrainingCoachSessionQueueState MakeSampleTrainingCoachSessionQueueState();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Validation")
|
||||
static bool IsPuzzleStateStructurallyValid(const FHyperTwistPuzzleState& State);
|
||||
|
||||
|
|
|
|||
|
|
@ -142,6 +142,101 @@ struct FHyperTwistCoachBrief
|
|||
bool bPreferRecognitionReplayReview = false;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistCoachSessionQueueEntry
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EntryId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SourceLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 SlotIndex = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistCoachPriority Priority = EHyperTwistCoachPriority::Informational;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float PriorityScore = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Headline;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingDeliveryMode SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusDeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusMethodSegmentId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> FocusCaseIds;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bRequiresFollowUp = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bRequiresArchiveRetentionTuning = false;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistCoachSessionQueueSummary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString UserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ReferenceUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusDeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusMethodSegmentId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PrimaryQueueLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString NextQueueLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistCoachSessionQueueEntry> Entries;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 FollowUpEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ArchiveRetentionEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ReviewBacklogEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bHasImmediateQueue = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bNeedsArchiveAwareSequencing = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !UserId.IsEmpty() && Entries.Num() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class UNREALHYPERTWIST_API UHyperTwistTrainingCoachLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
|
|
@ -178,4 +273,12 @@ public:
|
|||
const FHyperTwistTrainingCoachActionPlan& ActionPlan,
|
||||
const FHyperTwistTrainingCoachActionPlanOutcomeSummary& OutcomeSummary
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistCoachSessionQueueSummary DeriveCoachSessionQueueSummary(
|
||||
const FHyperTwistCoachBrief& CurrentBrief,
|
||||
const FHyperTwistCoachBrief& FollowUpBrief,
|
||||
const FHyperTwistTrainingCoachArchivePolicySummary& ArchivePolicySummary,
|
||||
const FHyperTwistTrainingReviewProgramSummary& ReviewProgramSummary
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -347,6 +347,56 @@ public:
|
|||
const FHyperTwistTrainingCoachActionPlan& ActionPlan
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static FHyperTwistTrainingCoachSessionQueueState BuildCoachSessionQueueState(
|
||||
const FHyperTwistCoachSessionQueueSummary& QueueSummary,
|
||||
const FString& QueueId,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static FHyperTwistTrainingCoachSessionQueueState ResolveCoachSessionQueueState(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& ExistingQueueState,
|
||||
const FHyperTwistCoachSessionQueueSummary& QueueSummary,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static FHyperTwistTrainingCoachSessionQueueState StartCoachSessionQueueEntryBySourceLabel(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FString& SourceLabel,
|
||||
const FString& TrainingSessionId,
|
||||
const FString& StartedAtUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static FHyperTwistTrainingCoachSessionQueueState FinalizeCoachSessionQueueEntry(
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
|
||||
const FString& TrainingSessionId,
|
||||
const FString& CompletedAtUtc,
|
||||
bool bAbortEntry
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static TArray<FHyperTwistTrainingCoachSessionQueueState> ListCoachSessionQueueStatesForUser(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static bool TryGetCoachSessionQueueState(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& QueueId,
|
||||
FHyperTwistTrainingCoachSessionQueueState& OutQueueState
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static FHyperTwistTrainingRepositoryState UpsertCoachSessionQueueState(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static FHyperTwistTrainingCoachActionPlanSummary DeriveCoachActionPlanSummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
|
|
|
|||
|
|
@ -117,6 +117,14 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistCoachBrief GetActiveTrainingCoachFollowUpBrief(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistCoachSessionQueueSummary GetActiveTrainingCoachSessionQueueSummary(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingCoachSessionQueueState GetActiveTrainingCoachSessionQueueState(
|
||||
UObject* WorldContextObject
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static TArray<FHyperTwistTrainingMethodSegment> GetActiveTrainingLearnerMethodSegments(UObject* WorldContextObject);
|
||||
|
||||
|
|
@ -197,6 +205,14 @@ public:
|
|||
FHyperTwistTrainingCoachActionPlan& OutActionPlan
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool TryGetStoredTrainingCoachSessionQueueState(
|
||||
UObject* WorldContextObject,
|
||||
const FString& UserId,
|
||||
const FString& QueueId,
|
||||
FHyperTwistTrainingCoachSessionQueueState& OutQueueState
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool TryGetStoredTrainingSessionTemplate(
|
||||
UObject* WorldContextObject,
|
||||
|
|
@ -239,6 +255,12 @@ public:
|
|||
const FHyperTwistTrainingMethodDrillBatch& DrillBatch
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool SaveCoachSessionQueueState(
|
||||
UObject* WorldContextObject,
|
||||
const FHyperTwistTrainingCoachSessionQueueState& QueueState
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool RemoveMethodDrillBatch(
|
||||
UObject* WorldContextObject,
|
||||
|
|
|
|||
|
|
@ -104,6 +104,12 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistCoachBrief GetActiveCoachFollowUpBrief() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistCoachSessionQueueSummary GetActiveCoachSessionQueueSummary() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistTrainingCoachSessionQueueState GetActiveCoachSessionQueueState() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
TArray<FHyperTwistTrainingMethodSegment> GetActiveLearnerMethodSegments() const;
|
||||
|
||||
|
|
@ -176,6 +182,13 @@ public:
|
|||
FHyperTwistTrainingCoachActionPlan& OutActionPlan
|
||||
) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
|
||||
bool TryGetStoredCoachSessionQueueState(
|
||||
const FString& UserId,
|
||||
const FString& QueueId,
|
||||
FHyperTwistTrainingCoachSessionQueueState& OutQueueState
|
||||
) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
bool TryGetStoredTrainingSessionTemplate(
|
||||
const FString& UserId,
|
||||
|
|
@ -206,6 +219,9 @@ public:
|
|||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
|
||||
bool SaveMethodDrillBatch(const FHyperTwistTrainingMethodDrillBatch& DrillBatch);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
|
||||
bool SaveCoachSessionQueueState(const FHyperTwistTrainingCoachSessionQueueState& QueueState);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
|
||||
bool RemoveMethodDrillBatch(
|
||||
const FString& UserId,
|
||||
|
|
@ -453,6 +469,12 @@ private:
|
|||
UPROPERTY()
|
||||
FHyperTwistCoachBrief ActiveCoachFollowUpBrief;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistCoachSessionQueueSummary ActiveCoachSessionQueueSummary;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingCoachSessionQueueState ActiveCoachSessionQueueState;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FHyperTwistTrainingMethodSegment> ActiveLearnerMethodSegments;
|
||||
|
||||
|
|
|
|||
|
|
@ -2718,6 +2718,17 @@ enum class EHyperTwistTrainingCoachActionPlanState : uint8
|
|||
Aborted UMETA(DisplayName = "Aborted")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistTrainingCoachSessionQueueEntryState : uint8
|
||||
{
|
||||
Pending UMETA(DisplayName = "Pending"),
|
||||
Ready UMETA(DisplayName = "Ready"),
|
||||
InProgress UMETA(DisplayName = "In Progress"),
|
||||
Completed UMETA(DisplayName = "Completed"),
|
||||
Skipped UMETA(DisplayName = "Skipped"),
|
||||
Expired UMETA(DisplayName = "Expired")
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingCoachActionPlanEntry
|
||||
{
|
||||
|
|
@ -2859,6 +2870,152 @@ struct FHyperTwistTrainingCoachActionPlan
|
|||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingCoachSessionQueueStateEntry
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EntryId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SourceLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString WorkFingerprint;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SourceTrainingSessionId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 SlotIndex = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingCoachSessionQueueEntryState EntryState =
|
||||
EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PriorityLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float PriorityScore = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Headline;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingDeliveryMode SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusDeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusMethodSegmentId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> FocusCaseIds;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ScheduledAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString StartedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString CompletedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bRequiresFollowUp = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bRequiresArchiveRetentionTuning = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bPinned = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bCanStartImmediately = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !EntryId.IsEmpty() && !Headline.IsEmpty() && !WorkFingerprint.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingCoachSessionQueueState
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString QueueId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString UserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ReferenceUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LastUpdatedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusDeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusMethodSegmentId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PrimaryQueueLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString NextQueueLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ActiveEntryId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ActiveTrainingSessionId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingCoachSessionQueueStateEntry> Entries;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 PendingEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ReadyEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 InProgressEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 CompletedEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 SkippedEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ExpiredEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bHasImmediateQueue = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bNeedsArchiveAwareSequencing = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !QueueId.IsEmpty() && !UserId.IsEmpty() && Entries.Num() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingCoachActionPlanSummary
|
||||
{
|
||||
|
|
@ -3524,6 +3681,9 @@ struct FHyperTwistTrainingRepositoryState
|
|||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingCoachActionPlan> StoredCoachActionPlans;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingCoachSessionQueueState> StoredCoachSessionQueues;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
|
|
@ -3594,6 +3754,15 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateCoachActionPlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalCoachSessionQueueCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 InvalidCoachSessionQueueCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateCoachSessionQueueCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> RepairActions;
|
||||
|
||||
|
|
@ -3612,6 +3781,8 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
|
|||
|| InvalidMethodDrillBatchCount > 0
|
||||
|| DuplicateMethodDrillBatchCount > 0
|
||||
|| InvalidCoachActionPlanCount > 0
|
||||
|| DuplicateCoachActionPlanCount > 0;
|
||||
|| DuplicateCoachActionPlanCount > 0
|
||||
|| InvalidCoachSessionQueueCount > 0
|
||||
|| DuplicateCoachSessionQueueCount > 0;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue