Tighten coach scheduling recovery and repository integrity
This commit is contained in:
parent
552413e438
commit
88f8a8d7f8
6 changed files with 2075 additions and 66 deletions
|
|
@ -3043,6 +3043,70 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::DeriveCoachBrief(
|
|||
const FHyperTwistCoachSignal& PrimarySignal = Brief.Signals[0];
|
||||
HyperTwistTrainingCoachLibraryInternal::ApplyPrimarySignalToBrief(Brief, PrimarySignal);
|
||||
|
||||
const bool bScheduleFacingBrief =
|
||||
CoachMemorySnapshot.bNeedsScheduleFrictionRecovery
|
||||
&& (PrimarySignal.SignalId == TEXT("schedule_friction")
|
||||
|| Brief.FocusLane == EHyperTwistCoachFocusLane::Cadence
|
||||
|| Brief.FocusLane == EHyperTwistCoachFocusLane::Stability
|
||||
|| Brief.bNeedsCadenceRecovery);
|
||||
if (bScheduleFacingBrief)
|
||||
{
|
||||
const bool bPreferFollowUpRecovery =
|
||||
CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp;
|
||||
const bool bPreferRecommendedRecovery =
|
||||
CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended;
|
||||
const bool bPromoteDeferredRecovery =
|
||||
CoachMemorySnapshot.PreferredQueueRecoveryAction
|
||||
== EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred;
|
||||
const bool bLaunchRecovery =
|
||||
CoachMemorySnapshot.PreferredQueueRecoveryAction
|
||||
== EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery;
|
||||
|
||||
if (CoachMemorySnapshot.SchedulePressureCaseIds.Num() > 0
|
||||
&& (Brief.FocusCaseIds.Num() == 0
|
||||
|| PrimarySignal.SignalId == TEXT("schedule_friction")
|
||||
|| Brief.FocusLane == EHyperTwistCoachFocusLane::Cadence
|
||||
|| Brief.FocusLane == EHyperTwistCoachFocusLane::Stability))
|
||||
{
|
||||
Brief.FocusCaseIds = CoachMemorySnapshot.SchedulePressureCaseIds;
|
||||
}
|
||||
|
||||
if (bPreferFollowUpRecovery)
|
||||
{
|
||||
Brief.SuggestedMode = EHyperTwistTrainingDeliveryMode::CoachReviewed;
|
||||
Brief.SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Scripted;
|
||||
Brief.PrimaryActionLabel = bPromoteDeferredRecovery
|
||||
? TEXT("promote_deferred_follow_up_recovery")
|
||||
: (bLaunchRecovery
|
||||
? TEXT("launch_follow_up_recovery")
|
||||
: TEXT("recover_follow_up_schedule_friction"));
|
||||
}
|
||||
else if (bPreferRecommendedRecovery)
|
||||
{
|
||||
Brief.SuggestedMode = CoachMemorySnapshot.DominantCoachSuggestedMode;
|
||||
Brief.SuggestedSelectionPolicy = CoachMemorySnapshot.bHasQueueExecutionCompletionGap
|
||||
? EHyperTwistTrainingSelectionPolicy::Weighted
|
||||
: CoachMemorySnapshot.DominantCoachSelectionPolicy;
|
||||
Brief.PrimaryActionLabel = bPromoteDeferredRecovery
|
||||
? TEXT("promote_deferred_recommended_recovery")
|
||||
: (bLaunchRecovery
|
||||
? TEXT("launch_recommended_recovery")
|
||||
: TEXT("recover_recommended_schedule_friction"));
|
||||
}
|
||||
else if (CoachMemorySnapshot.bHasArchiveAwareScheduleFriction)
|
||||
{
|
||||
Brief.PrimaryActionLabel = TEXT("stabilize_archive_aware_schedule");
|
||||
}
|
||||
else
|
||||
{
|
||||
Brief.PrimaryActionLabel = bPromoteDeferredRecovery
|
||||
? TEXT("promote_deferred_schedule_recovery")
|
||||
: (bLaunchRecovery
|
||||
? TEXT("launch_schedule_recovery")
|
||||
: TEXT("recover_schedule_friction"));
|
||||
}
|
||||
}
|
||||
|
||||
if (Brief.FocusCaseIds.Num() == 0)
|
||||
{
|
||||
if (CoachMemorySnapshot.bNeedsScheduleFrictionRecovery
|
||||
|
|
@ -4243,6 +4307,7 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithQueue
|
|||
const FString OriginalDeckId = Brief.FocusDeckId;
|
||||
const FString OriginalMethodSegmentId = Brief.FocusMethodSegmentId;
|
||||
const TArray<FString> OriginalCaseIds = Brief.FocusCaseIds;
|
||||
const FString OriginalPrimaryActionLabel = Brief.PrimaryActionLabel;
|
||||
HyperTwistTrainingCoachLibraryInternal::ApplyPrimarySignalToBrief(Brief, Brief.Signals[0]);
|
||||
if (Brief.FocusDeckId.IsEmpty())
|
||||
{
|
||||
|
|
@ -4256,6 +4321,15 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithQueue
|
|||
{
|
||||
Brief.FocusCaseIds = OriginalCaseIds;
|
||||
}
|
||||
if (bShouldPromoteSuppressionSignal
|
||||
&& Brief.PrimaryActionLabel == QueueSuppressionSignal.SignalId
|
||||
&& (!OriginalPrimaryActionLabel.IsEmpty()
|
||||
&& (OriginalPrimaryActionLabel.Contains(TEXT("schedule"))
|
||||
|| OriginalPrimaryActionLabel.Contains(TEXT("recovery"))
|
||||
|| OriginalPrimaryActionLabel.Contains(TEXT("promote_deferred")))))
|
||||
{
|
||||
Brief.PrimaryActionLabel = OriginalPrimaryActionLabel;
|
||||
}
|
||||
}
|
||||
|
||||
return Brief;
|
||||
|
|
@ -4447,6 +4521,12 @@ FHyperTwistCoachSignal UHyperTwistTrainingCoachLibrary::DeriveLaunchBudgetHistor
|
|||
}
|
||||
|
||||
const bool bBroadenPreferred = CoachMemorySnapshot.PreferredLaunchBudgetBias > 0;
|
||||
const bool bScheduleTighteningSource =
|
||||
!bBroadenPreferred
|
||||
&& CoachMemorySnapshot.PreferredLaunchBudgetSourceLabel.StartsWith(TEXT("coach-memory-schedule-"));
|
||||
const bool bFollowUpScheduleTightening =
|
||||
bScheduleTighteningSource
|
||||
&& CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp;
|
||||
const bool bCompetingGuidancePressure =
|
||||
CoachMemorySnapshot.RecommendedGuidanceContinueBias > 0
|
||||
&& CoachMemorySnapshot.FollowUpGuidanceContinueBias > 0;
|
||||
|
|
@ -4458,19 +4538,30 @@ FHyperTwistCoachSignal UHyperTwistTrainingCoachLibrary::DeriveLaunchBudgetHistor
|
|||
+ (bCompetingGuidancePressure ? 4.0f : 0.0f)
|
||||
+ ((CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::Cadence
|
||||
|| CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::Stability
|
||||
|| CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::ReviewBacklog) ? 3.0f : 0.0f),
|
||||
|| CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::ReviewBacklog) ? 3.0f : 0.0f)
|
||||
+ (bScheduleTighteningSource ? 4.0f : 0.0f),
|
||||
0.0f,
|
||||
100.0f
|
||||
);
|
||||
const FString SignalId = bBroadenPreferred
|
||||
? TEXT("launch_budget_history_broaden")
|
||||
: TEXT("launch_budget_history_tighten");
|
||||
: (bFollowUpScheduleTightening
|
||||
? TEXT("launch_budget_schedule_follow_up_tighten")
|
||||
: (bScheduleTighteningSource
|
||||
? TEXT("launch_budget_schedule_recommended_tighten")
|
||||
: TEXT("launch_budget_history_tighten")));
|
||||
const EHyperTwistCoachFocusLane FocusLane = bBroadenPreferred
|
||||
? EHyperTwistCoachFocusLane::ReviewBacklog
|
||||
: EHyperTwistCoachFocusLane::Stability;
|
||||
: (bScheduleTighteningSource
|
||||
? EHyperTwistCoachFocusLane::Cadence
|
||||
: EHyperTwistCoachFocusLane::Stability);
|
||||
const FString Headline = bBroadenPreferred
|
||||
? TEXT("Launch history favors a broader next coach run")
|
||||
: TEXT("Launch history favors a tighter next coach run");
|
||||
: (bScheduleTighteningSource
|
||||
? (CoachMemorySnapshot.bHasBlockedFollowUpScheduleFriction
|
||||
? TEXT("Schedule friction favors a tighter next coach run before broader guidance resumes")
|
||||
: TEXT("Schedule recovery favors a tighter next coach run before broader guidance resumes"))
|
||||
: TEXT("Launch history favors a tighter next coach run"));
|
||||
const FString PreferredGuidanceLaneLabel =
|
||||
CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp
|
||||
? TEXT("follow-up")
|
||||
|
|
@ -4490,14 +4581,22 @@ FHyperTwistCoachSignal UHyperTwistTrainingCoachLibrary::DeriveLaunchBudgetHistor
|
|||
: *HyperTwistTrainingCoachLibraryInternal::DescribeCoachMemorySourceLabel(
|
||||
CoachMemorySnapshot.PreferredLaunchBudgetSourceLabel)
|
||||
);
|
||||
const EHyperTwistTrainingDeliveryMode SuggestedMode =
|
||||
CurrentBrief.SuggestedMode != EHyperTwistTrainingDeliveryMode::Timer
|
||||
const EHyperTwistTrainingDeliveryMode SuggestedMode = bScheduleTighteningSource
|
||||
? (bFollowUpScheduleTightening
|
||||
? EHyperTwistTrainingDeliveryMode::CoachReviewed
|
||||
: CoachMemorySnapshot.DominantCoachSuggestedMode)
|
||||
: (CurrentBrief.SuggestedMode != EHyperTwistTrainingDeliveryMode::Timer
|
||||
? CurrentBrief.SuggestedMode
|
||||
: CoachMemorySnapshot.DominantCoachSuggestedMode;
|
||||
const EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy =
|
||||
CurrentBrief.SuggestedSelectionPolicy != EHyperTwistTrainingSelectionPolicy::Weighted
|
||||
: CoachMemorySnapshot.DominantCoachSuggestedMode);
|
||||
const EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy = bScheduleTighteningSource
|
||||
? (bFollowUpScheduleTightening
|
||||
? EHyperTwistTrainingSelectionPolicy::Scripted
|
||||
: (CoachMemorySnapshot.bHasQueueExecutionCompletionGap
|
||||
? EHyperTwistTrainingSelectionPolicy::Weighted
|
||||
: CoachMemorySnapshot.DominantCoachSelectionPolicy))
|
||||
: (CurrentBrief.SuggestedSelectionPolicy != EHyperTwistTrainingSelectionPolicy::Weighted
|
||||
? CurrentBrief.SuggestedSelectionPolicy
|
||||
: CoachMemorySnapshot.DominantCoachSelectionPolicy;
|
||||
: CoachMemorySnapshot.DominantCoachSelectionPolicy);
|
||||
|
||||
FHyperTwistCoachSignal Signal = HyperTwistTrainingCoachLibraryInternal::MakeSignal(
|
||||
SignalId,
|
||||
|
|
@ -4514,11 +4613,13 @@ FHyperTwistCoachSignal UHyperTwistTrainingCoachLibrary::DeriveLaunchBudgetHistor
|
|||
Signal.SuggestedMethodSegmentId = !CurrentBrief.FocusMethodSegmentId.IsEmpty()
|
||||
? CurrentBrief.FocusMethodSegmentId
|
||||
: CoachMemorySnapshot.FocusMethodSegmentId;
|
||||
Signal.SuggestedCaseIds = CurrentBrief.FocusCaseIds.Num() > 0
|
||||
? CurrentBrief.FocusCaseIds
|
||||
: (CoachMemorySnapshot.WeakCaseIds.Num() > 0
|
||||
? CoachMemorySnapshot.WeakCaseIds
|
||||
: CoachMemorySnapshot.DueCaseIds);
|
||||
Signal.SuggestedCaseIds = bScheduleTighteningSource && CoachMemorySnapshot.SchedulePressureCaseIds.Num() > 0
|
||||
? CoachMemorySnapshot.SchedulePressureCaseIds
|
||||
: (CurrentBrief.FocusCaseIds.Num() > 0
|
||||
? CurrentBrief.FocusCaseIds
|
||||
: (CoachMemorySnapshot.WeakCaseIds.Num() > 0
|
||||
? CoachMemorySnapshot.WeakCaseIds
|
||||
: CoachMemorySnapshot.DueCaseIds));
|
||||
HyperTwistTrainingCoachLibraryInternal::ApplyGuidancePreferenceToSignal(
|
||||
Signal,
|
||||
CoachMemorySnapshot.PreferredGuidanceLane,
|
||||
|
|
@ -4580,6 +4681,7 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithLaunc
|
|||
const FString OriginalDeckId = Brief.FocusDeckId;
|
||||
const FString OriginalMethodSegmentId = Brief.FocusMethodSegmentId;
|
||||
const TArray<FString> OriginalCaseIds = Brief.FocusCaseIds;
|
||||
const FString OriginalPrimaryActionLabel = Brief.PrimaryActionLabel;
|
||||
HyperTwistTrainingCoachLibraryInternal::ApplyPrimarySignalToBrief(Brief, Brief.Signals[0]);
|
||||
if (Brief.FocusDeckId.IsEmpty())
|
||||
{
|
||||
|
|
@ -4593,6 +4695,25 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithLaunc
|
|||
{
|
||||
Brief.FocusCaseIds = OriginalCaseIds;
|
||||
}
|
||||
if (bShouldPromoteLaunchSignal)
|
||||
{
|
||||
if (Brief.PrimaryActionLabel == TEXT("launch_budget_schedule_follow_up_tighten"))
|
||||
{
|
||||
Brief.PrimaryActionLabel = TEXT("launch_follow_up_recovery");
|
||||
}
|
||||
else if (Brief.PrimaryActionLabel == TEXT("launch_budget_schedule_recommended_tighten"))
|
||||
{
|
||||
Brief.PrimaryActionLabel = TEXT("launch_recommended_recovery");
|
||||
}
|
||||
else if (Brief.PrimaryActionLabel == LaunchBudgetHistorySignal.SignalId
|
||||
&& (!OriginalPrimaryActionLabel.IsEmpty()
|
||||
&& (OriginalPrimaryActionLabel.Contains(TEXT("schedule"))
|
||||
|| OriginalPrimaryActionLabel.Contains(TEXT("recovery"))
|
||||
|| OriginalPrimaryActionLabel.Contains(TEXT("promote_deferred")))))
|
||||
{
|
||||
Brief.PrimaryActionLabel = OriginalPrimaryActionLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Brief;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingLibrary.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
|
||||
namespace HyperTwistTrainingRepositoryLibraryInternal
|
||||
{
|
||||
|
|
@ -4353,6 +4354,251 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
}
|
||||
}
|
||||
|
||||
template <typename TStruct>
|
||||
FString SerializeStructToJson(const TStruct& Value)
|
||||
{
|
||||
FString Json;
|
||||
FJsonObjectConverter::UStructToJsonObjectString(TStruct::StaticStruct(), &Value, Json, 0, 0);
|
||||
return Json;
|
||||
}
|
||||
|
||||
template <typename TStruct>
|
||||
bool DoSerializedStructsMatch(const TStruct& Left, const TStruct& Right)
|
||||
{
|
||||
return SerializeStructToJson(Left) == SerializeStructToJson(Right);
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingCaseLearnerState> NormalizeLearnerStates(
|
||||
const TArray<FHyperTwistTrainingCaseLearnerState>& LearnerStates
|
||||
)
|
||||
{
|
||||
TArray<FHyperTwistTrainingCaseLearnerState> NormalizedStates;
|
||||
TMap<FString, int32> LearnerStateIndexByKey;
|
||||
for (const FHyperTwistTrainingCaseLearnerState& LearnerState : LearnerStates)
|
||||
{
|
||||
if (!LearnerState.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString LearnerStateKey = MakeCaseKey(
|
||||
LearnerState.UserId,
|
||||
LearnerState.DeckId,
|
||||
LearnerState.CaseId
|
||||
);
|
||||
if (const int32* ExistingIndex = LearnerStateIndexByKey.Find(LearnerStateKey))
|
||||
{
|
||||
NormalizedStates[*ExistingIndex] = LearnerState;
|
||||
}
|
||||
else
|
||||
{
|
||||
LearnerStateIndexByKey.Add(LearnerStateKey, NormalizedStates.Add(LearnerState));
|
||||
}
|
||||
}
|
||||
|
||||
NormalizedStates.Sort([](
|
||||
const FHyperTwistTrainingCaseLearnerState& Left,
|
||||
const FHyperTwistTrainingCaseLearnerState& Right
|
||||
)
|
||||
{
|
||||
if (Left.UserId != Right.UserId)
|
||||
{
|
||||
return Left.UserId < Right.UserId;
|
||||
}
|
||||
if (Left.DeckId != Right.DeckId)
|
||||
{
|
||||
return Left.DeckId < Right.DeckId;
|
||||
}
|
||||
return Left.CaseId < Right.CaseId;
|
||||
});
|
||||
return NormalizedStates;
|
||||
}
|
||||
|
||||
bool IsTerminalReviewPlanEntryState(const EHyperTwistTrainingReviewPlanEntryState EntryState)
|
||||
{
|
||||
return EntryState == EHyperTwistTrainingReviewPlanEntryState::Completed
|
||||
|| EntryState == EHyperTwistTrainingReviewPlanEntryState::Skipped;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingReviewPlanEntry NormalizeReviewPlanEntry(
|
||||
const FHyperTwistTrainingReviewPlanEntry& Entry
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingReviewPlanEntry NormalizedEntry = Entry;
|
||||
NormalizedEntry.AttemptCount = FMath::Max(0, NormalizedEntry.AttemptCount);
|
||||
NormalizedEntry.SuccessCount = FMath::Clamp(
|
||||
NormalizedEntry.SuccessCount,
|
||||
0,
|
||||
NormalizedEntry.AttemptCount
|
||||
);
|
||||
if (NormalizedEntry.PromptLabel.IsEmpty())
|
||||
{
|
||||
NormalizedEntry.PromptLabel = NormalizedEntry.CaseId;
|
||||
}
|
||||
|
||||
return NormalizedEntry;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingReviewPlanState NormalizeReviewPlanState(
|
||||
const FHyperTwistTrainingReviewPlanState& ReviewPlan
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingReviewPlanState NormalizedPlan = ReviewPlan;
|
||||
const FString ReferenceSeed = !ReviewPlan.ReferenceUtc.IsEmpty()
|
||||
? ReviewPlan.ReferenceUtc
|
||||
: (!ReviewPlan.CreatedAtUtc.IsEmpty()
|
||||
? ReviewPlan.CreatedAtUtc
|
||||
: (!ReviewPlan.StartedAtUtc.IsEmpty() ? ReviewPlan.StartedAtUtc : ReviewPlan.CompletedAtUtc));
|
||||
NormalizedPlan.ReferenceUtc = ResolveReferenceUtc(ReferenceSeed);
|
||||
if (NormalizedPlan.CreatedAtUtc.IsEmpty())
|
||||
{
|
||||
NormalizedPlan.CreatedAtUtc = NormalizedPlan.ReferenceUtc;
|
||||
}
|
||||
if (NormalizedPlan.StartedAtUtc.IsEmpty())
|
||||
{
|
||||
NormalizedPlan.StartedAtUtc = NormalizedPlan.CreatedAtUtc;
|
||||
}
|
||||
NormalizedPlan.Policy.MaxCases = FMath::Max(0, NormalizedPlan.Policy.MaxCases);
|
||||
|
||||
TMap<FString, int32> EntryIndexByCaseId;
|
||||
TArray<FHyperTwistTrainingReviewPlanEntry> NormalizedEntries;
|
||||
for (const FHyperTwistTrainingReviewPlanEntry& Entry : ReviewPlan.Entries)
|
||||
{
|
||||
const FHyperTwistTrainingReviewPlanEntry NormalizedEntry = NormalizeReviewPlanEntry(Entry);
|
||||
if (!NormalizedEntry.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const int32* ExistingIndex = EntryIndexByCaseId.Find(NormalizedEntry.CaseId))
|
||||
{
|
||||
NormalizedEntries[*ExistingIndex] = NormalizedEntry;
|
||||
}
|
||||
else
|
||||
{
|
||||
EntryIndexByCaseId.Add(NormalizedEntry.CaseId, NormalizedEntries.Add(NormalizedEntry));
|
||||
}
|
||||
}
|
||||
|
||||
NormalizedPlan.Entries = NormalizedEntries;
|
||||
if (!NormalizedPlan.IsStructurallyValid())
|
||||
{
|
||||
NormalizedPlan.CurrentEntryIndex = INDEX_NONE;
|
||||
return NormalizedPlan;
|
||||
}
|
||||
|
||||
int32 ResolvedCurrentEntryIndex = INDEX_NONE;
|
||||
if (!NormalizedPlan.bCompleted
|
||||
&& NormalizedPlan.Entries.IsValidIndex(NormalizedPlan.CurrentEntryIndex)
|
||||
&& !IsTerminalReviewPlanEntryState(
|
||||
NormalizedPlan.Entries[NormalizedPlan.CurrentEntryIndex].EntryState))
|
||||
{
|
||||
ResolvedCurrentEntryIndex = NormalizedPlan.CurrentEntryIndex;
|
||||
}
|
||||
if (ResolvedCurrentEntryIndex == INDEX_NONE && !NormalizedPlan.bCompleted)
|
||||
{
|
||||
ResolvedCurrentEntryIndex = NormalizedPlan.Entries.IndexOfByPredicate(
|
||||
[](const FHyperTwistTrainingReviewPlanEntry& Entry)
|
||||
{
|
||||
return Entry.EntryState == EHyperTwistTrainingReviewPlanEntryState::Current;
|
||||
}
|
||||
);
|
||||
if (ResolvedCurrentEntryIndex >= 0
|
||||
&& IsTerminalReviewPlanEntryState(
|
||||
NormalizedPlan.Entries[ResolvedCurrentEntryIndex].EntryState))
|
||||
{
|
||||
ResolvedCurrentEntryIndex = INDEX_NONE;
|
||||
}
|
||||
}
|
||||
if (ResolvedCurrentEntryIndex == INDEX_NONE && !NormalizedPlan.bCompleted)
|
||||
{
|
||||
ResolvedCurrentEntryIndex = NormalizedPlan.Entries.IndexOfByPredicate(
|
||||
[](const FHyperTwistTrainingReviewPlanEntry& Entry)
|
||||
{
|
||||
return Entry.EntryState == EHyperTwistTrainingReviewPlanEntryState::NeedsRepeat
|
||||
|| Entry.EntryState == EHyperTwistTrainingReviewPlanEntryState::Pending;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
int32 TerminalCount = 0;
|
||||
FString LatestCompletedAtUtc;
|
||||
for (const FHyperTwistTrainingReviewPlanEntry& Entry : NormalizedPlan.Entries)
|
||||
{
|
||||
if (IsTerminalReviewPlanEntryState(Entry.EntryState))
|
||||
{
|
||||
TerminalCount += 1;
|
||||
}
|
||||
if (!Entry.LastCompletedAtUtc.IsEmpty()
|
||||
&& (LatestCompletedAtUtc.IsEmpty()
|
||||
|| CompareUtcStrings(Entry.LastCompletedAtUtc, LatestCompletedAtUtc) > 0))
|
||||
{
|
||||
LatestCompletedAtUtc = Entry.LastCompletedAtUtc;
|
||||
}
|
||||
}
|
||||
if (TerminalCount >= NormalizedPlan.Entries.Num())
|
||||
{
|
||||
NormalizedPlan.bCompleted = true;
|
||||
}
|
||||
|
||||
if (NormalizedPlan.bCompleted)
|
||||
{
|
||||
NormalizedPlan.CurrentEntryIndex = INDEX_NONE;
|
||||
for (FHyperTwistTrainingReviewPlanEntry& Entry : NormalizedPlan.Entries)
|
||||
{
|
||||
if (Entry.EntryState == EHyperTwistTrainingReviewPlanEntryState::Current)
|
||||
{
|
||||
Entry.EntryState = Entry.LastOutcome == EHyperTwistTrainingAttemptResult::Aborted
|
||||
? EHyperTwistTrainingReviewPlanEntryState::Skipped
|
||||
: (Entry.SuccessCount > 0
|
||||
? EHyperTwistTrainingReviewPlanEntryState::Completed
|
||||
: (Entry.AttemptCount > 0
|
||||
? EHyperTwistTrainingReviewPlanEntryState::NeedsRepeat
|
||||
: EHyperTwistTrainingReviewPlanEntryState::Pending));
|
||||
}
|
||||
}
|
||||
if (NormalizedPlan.CompletedAtUtc.IsEmpty())
|
||||
{
|
||||
NormalizedPlan.CompletedAtUtc = !LatestCompletedAtUtc.IsEmpty()
|
||||
? LatestCompletedAtUtc
|
||||
: ResolveReferenceUtc(
|
||||
!NormalizedPlan.StartedAtUtc.IsEmpty()
|
||||
? NormalizedPlan.StartedAtUtc
|
||||
: NormalizedPlan.ReferenceUtc
|
||||
);
|
||||
}
|
||||
return NormalizedPlan;
|
||||
}
|
||||
|
||||
NormalizedPlan.CompletedAtUtc.Reset();
|
||||
NormalizedPlan.CurrentEntryIndex = ResolvedCurrentEntryIndex;
|
||||
for (int32 EntryIndex = 0; EntryIndex < NormalizedPlan.Entries.Num(); ++EntryIndex)
|
||||
{
|
||||
FHyperTwistTrainingReviewPlanEntry& Entry = NormalizedPlan.Entries[EntryIndex];
|
||||
if (IsTerminalReviewPlanEntryState(Entry.EntryState))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (EntryIndex == ResolvedCurrentEntryIndex)
|
||||
{
|
||||
Entry.EntryState = EHyperTwistTrainingReviewPlanEntryState::Current;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Entry.EntryState == EHyperTwistTrainingReviewPlanEntryState::Current)
|
||||
{
|
||||
Entry.EntryState = Entry.LastOutcome == EHyperTwistTrainingAttemptResult::Aborted
|
||||
? EHyperTwistTrainingReviewPlanEntryState::Skipped
|
||||
: (Entry.AttemptCount > 0
|
||||
? EHyperTwistTrainingReviewPlanEntryState::NeedsRepeat
|
||||
: EHyperTwistTrainingReviewPlanEntryState::Pending);
|
||||
}
|
||||
}
|
||||
|
||||
return NormalizedPlan;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingMethodDrillFavoriteEntry NormalizeMethodDrillFavoriteEntry(
|
||||
const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry
|
||||
)
|
||||
|
|
@ -7334,6 +7580,82 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
SeenLearnerStateKeys.Add(LearnerStateKey);
|
||||
}
|
||||
|
||||
if (Report.TotalRunRecordCount > 0)
|
||||
{
|
||||
TArray<FHyperTwistTrainingRunRecord> ValidRunRecords;
|
||||
TMap<FString, int32> RunRecordIndexBySessionId;
|
||||
for (const FHyperTwistTrainingRunRecord& RunRecord : RepositoryState.RunRecords)
|
||||
{
|
||||
if (!RunRecord.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString& SessionId = RunRecord.Session.TrainingSessionId;
|
||||
if (const int32* ExistingIndex = RunRecordIndexBySessionId.Find(SessionId))
|
||||
{
|
||||
ValidRunRecords[*ExistingIndex] = RunRecord;
|
||||
}
|
||||
else
|
||||
{
|
||||
RunRecordIndexBySessionId.Add(SessionId, ValidRunRecords.Add(RunRecord));
|
||||
}
|
||||
}
|
||||
ValidRunRecords = HyperTwistTrainingRepositoryLibraryInternal::SortRunRecordsByUpdateUtc(
|
||||
ValidRunRecords
|
||||
);
|
||||
|
||||
const TArray<FHyperTwistTrainingCaseLearnerState> ExpectedLearnerStates =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::DeriveLearnerStatesFromRunRecords(ValidRunRecords);
|
||||
const TArray<FHyperTwistTrainingCaseLearnerState> ActualLearnerStates =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeLearnerStates(RepositoryState.LearnerStates);
|
||||
|
||||
TMap<FString, FHyperTwistTrainingCaseLearnerState> ExpectedLearnerStateByKey;
|
||||
for (const FHyperTwistTrainingCaseLearnerState& LearnerState : ExpectedLearnerStates)
|
||||
{
|
||||
ExpectedLearnerStateByKey.Add(
|
||||
HyperTwistTrainingRepositoryLibraryInternal::MakeCaseKey(
|
||||
LearnerState.UserId,
|
||||
LearnerState.DeckId,
|
||||
LearnerState.CaseId
|
||||
),
|
||||
LearnerState
|
||||
);
|
||||
}
|
||||
|
||||
TMap<FString, FHyperTwistTrainingCaseLearnerState> ActualLearnerStateByKey;
|
||||
for (const FHyperTwistTrainingCaseLearnerState& LearnerState : ActualLearnerStates)
|
||||
{
|
||||
ActualLearnerStateByKey.Add(
|
||||
HyperTwistTrainingRepositoryLibraryInternal::MakeCaseKey(
|
||||
LearnerState.UserId,
|
||||
LearnerState.DeckId,
|
||||
LearnerState.CaseId
|
||||
),
|
||||
LearnerState
|
||||
);
|
||||
}
|
||||
|
||||
for (const TPair<FString, FHyperTwistTrainingCaseLearnerState>& Pair : ExpectedLearnerStateByKey)
|
||||
{
|
||||
const FHyperTwistTrainingCaseLearnerState* ActualLearnerState = ActualLearnerStateByKey.Find(Pair.Key);
|
||||
if (ActualLearnerState == nullptr
|
||||
|| !HyperTwistTrainingRepositoryLibraryInternal::DoSerializedStructsMatch(
|
||||
*ActualLearnerState,
|
||||
Pair.Value))
|
||||
{
|
||||
Report.OutOfSyncLearnerStateCount += 1;
|
||||
}
|
||||
}
|
||||
for (const TPair<FString, FHyperTwistTrainingCaseLearnerState>& Pair : ActualLearnerStateByKey)
|
||||
{
|
||||
if (!ExpectedLearnerStateByKey.Contains(Pair.Key))
|
||||
{
|
||||
Report.OutOfSyncLearnerStateCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Report.TotalReviewPlanCount = RepositoryState.ReviewPlans.Num();
|
||||
TSet<FString> SeenReviewPlanIds;
|
||||
for (const FHyperTwistTrainingReviewPlanState& ReviewPlan : RepositoryState.ReviewPlans)
|
||||
|
|
@ -7352,6 +7674,37 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
SeenReviewPlanIds.Add(ReviewPlan.PlanId);
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingReviewPlanState> ValidReviewPlans;
|
||||
TSet<FString> UniqueValidReviewPlanIds;
|
||||
for (const FHyperTwistTrainingReviewPlanState& ReviewPlan : RepositoryState.ReviewPlans)
|
||||
{
|
||||
if (!ReviewPlan.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingReviewPlanState NormalizedReviewPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeReviewPlanState(ReviewPlan);
|
||||
if (!NormalizedReviewPlan.IsStructurallyValid())
|
||||
{
|
||||
Report.InvalidReviewPlanCount += 1;
|
||||
continue;
|
||||
}
|
||||
if (UniqueValidReviewPlanIds.Contains(NormalizedReviewPlan.PlanId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UniqueValidReviewPlanIds.Add(NormalizedReviewPlan.PlanId);
|
||||
if (!HyperTwistTrainingRepositoryLibraryInternal::DoSerializedStructsMatch(
|
||||
ReviewPlan,
|
||||
NormalizedReviewPlan))
|
||||
{
|
||||
Report.OutOfSyncReviewPlanCount += 1;
|
||||
}
|
||||
ValidReviewPlans.Add(NormalizedReviewPlan);
|
||||
}
|
||||
|
||||
Report.TotalStoredSessionTemplateCount = RepositoryState.StoredSessionTemplates.Num();
|
||||
TSet<FString> SeenStoredTemplateKeys;
|
||||
for (const FHyperTwistTrainingSessionTemplate& SessionTemplate : RepositoryState.StoredSessionTemplates)
|
||||
|
|
@ -7484,6 +7837,12 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
Report.DuplicateCoachSessionQueueHistoryCount += 1;
|
||||
continue;
|
||||
}
|
||||
if (!HyperTwistTrainingRepositoryLibraryInternal::DoSerializedStructsMatch(
|
||||
HistoryEntry,
|
||||
NormalizedHistoryEntry))
|
||||
{
|
||||
Report.OutOfSyncCoachSessionQueueHistoryCount += 1;
|
||||
}
|
||||
|
||||
SeenCoachSessionQueueHistoryIds.Add(NormalizedHistoryEntry.HistoryEntryId);
|
||||
}
|
||||
|
|
@ -7498,7 +7857,7 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
}
|
||||
|
||||
TSet<FString> ValidReviewPlanIds;
|
||||
for (const FHyperTwistTrainingReviewPlanState& ReviewPlan : RepositoryState.ReviewPlans)
|
||||
for (const FHyperTwistTrainingReviewPlanState& ReviewPlan : ValidReviewPlans)
|
||||
{
|
||||
if (ReviewPlan.IsStructurallyValid())
|
||||
{
|
||||
|
|
@ -7519,6 +7878,7 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
if (!NormalizedPlan.IsStructurallyValid())
|
||||
{
|
||||
Report.InvalidCoachActionPlanCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -7531,6 +7891,12 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!HyperTwistTrainingRepositoryLibraryInternal::DoSerializedStructsMatch(
|
||||
ActionPlan,
|
||||
NormalizedPlan))
|
||||
{
|
||||
Report.OutOfSyncCoachActionPlanCount += 1;
|
||||
}
|
||||
ValidCoachActionPlanIndexByKey.Add(ActionPlanKey, ValidCoachActionPlans.Add(NormalizedPlan));
|
||||
}
|
||||
|
||||
|
|
@ -7575,17 +7941,33 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
TSet<FString> UniqueCoachSessionQueueKeys;
|
||||
for (const FHyperTwistTrainingCoachSessionQueueState& QueueState : RepositoryState.StoredCoachSessionQueues)
|
||||
{
|
||||
if (QueueState.IsStructurallyValid())
|
||||
if (!QueueState.IsStructurallyValid())
|
||||
{
|
||||
const FString QueueKey = HyperTwistTrainingRepositoryLibraryInternal::MakeCoachSessionQueueKey(
|
||||
QueueState.UserId,
|
||||
QueueState.QueueId
|
||||
);
|
||||
if (!UniqueCoachSessionQueueKeys.Contains(QueueKey))
|
||||
continue;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachSessionQueueState NormalizedQueueState =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
|
||||
if (!NormalizedQueueState.IsStructurallyValid())
|
||||
{
|
||||
Report.InvalidCoachSessionQueueCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString QueueKey = HyperTwistTrainingRepositoryLibraryInternal::MakeCoachSessionQueueKey(
|
||||
NormalizedQueueState.UserId,
|
||||
NormalizedQueueState.QueueId
|
||||
);
|
||||
if (!UniqueCoachSessionQueueKeys.Contains(QueueKey))
|
||||
{
|
||||
UniqueCoachSessionQueueKeys.Add(QueueKey);
|
||||
if (!HyperTwistTrainingRepositoryLibraryInternal::DoSerializedStructsMatch(
|
||||
QueueState,
|
||||
NormalizedQueueState))
|
||||
{
|
||||
UniqueCoachSessionQueueKeys.Add(QueueKey);
|
||||
ValidCoachSessionQueues.Add(QueueState);
|
||||
Report.OutOfSyncCoachSessionQueueCount += 1;
|
||||
}
|
||||
ValidCoachSessionQueues.Add(NormalizedQueueState);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -7660,14 +8042,17 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
if (Report.TotalRunRecordCount > 0
|
||||
&& (Report.TotalLearnerStateCount == 0
|
||||
|| Report.InvalidLearnerStateCount > 0
|
||||
|| Report.DuplicateLearnerStateCount > 0))
|
||||
|| Report.DuplicateLearnerStateCount > 0
|
||||
|| Report.OutOfSyncLearnerStateCount > 0))
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("rebuild-learner-states-from-run-records")
|
||||
);
|
||||
}
|
||||
else if (Report.InvalidLearnerStateCount > 0 || Report.DuplicateLearnerStateCount > 0)
|
||||
else if (Report.InvalidLearnerStateCount > 0
|
||||
|| Report.DuplicateLearnerStateCount > 0
|
||||
|| Report.OutOfSyncLearnerStateCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
|
|
@ -7688,6 +8073,13 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
TEXT("deduplicate-review-plans-by-plan-id")
|
||||
);
|
||||
}
|
||||
if (Report.OutOfSyncReviewPlanCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("normalize-review-plans")
|
||||
);
|
||||
}
|
||||
if (Report.InvalidStoredSessionTemplateCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
|
|
@ -7737,7 +8129,7 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
TEXT("drop-invalid-coach-action-plans")
|
||||
);
|
||||
}
|
||||
if (Report.DuplicateCoachActionPlanCount > 0)
|
||||
if (Report.DuplicateCoachActionPlanCount > 0 || Report.OutOfSyncCoachActionPlanCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
|
|
@ -7775,7 +8167,7 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
TEXT("drop-invalid-coach-session-queues")
|
||||
);
|
||||
}
|
||||
if (Report.DuplicateCoachSessionQueueCount > 0)
|
||||
if (Report.DuplicateCoachSessionQueueCount > 0 || Report.OutOfSyncCoachSessionQueueCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
|
|
@ -7804,7 +8196,8 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
TEXT("drop-invalid-coach-session-queue-history")
|
||||
);
|
||||
}
|
||||
if (Report.DuplicateCoachSessionQueueHistoryCount > 0)
|
||||
if (Report.DuplicateCoachSessionQueueHistoryCount > 0
|
||||
|| Report.OutOfSyncCoachSessionQueueHistoryCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
|
|
@ -7858,28 +8251,9 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RepairR
|
|||
}
|
||||
else
|
||||
{
|
||||
TMap<FString, int32> LearnerStateIndexByKey;
|
||||
for (const FHyperTwistTrainingCaseLearnerState& LearnerState : RepositoryState.LearnerStates)
|
||||
{
|
||||
if (!LearnerState.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString LearnerStateKey = HyperTwistTrainingRepositoryLibraryInternal::MakeCaseKey(
|
||||
LearnerState.UserId,
|
||||
LearnerState.DeckId,
|
||||
LearnerState.CaseId
|
||||
);
|
||||
if (const int32* ExistingIndex = LearnerStateIndexByKey.Find(LearnerStateKey))
|
||||
{
|
||||
RepairedState.LearnerStates[*ExistingIndex] = LearnerState;
|
||||
}
|
||||
else
|
||||
{
|
||||
LearnerStateIndexByKey.Add(LearnerStateKey, RepairedState.LearnerStates.Add(LearnerState));
|
||||
}
|
||||
}
|
||||
RepairedState.LearnerStates = HyperTwistTrainingRepositoryLibraryInternal::NormalizeLearnerStates(
|
||||
RepositoryState.LearnerStates
|
||||
);
|
||||
}
|
||||
|
||||
TMap<FString, int32> ReviewPlanIndexById;
|
||||
|
|
@ -7890,16 +8264,50 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RepairR
|
|||
continue;
|
||||
}
|
||||
|
||||
if (const int32* ExistingIndex = ReviewPlanIndexById.Find(ReviewPlan.PlanId))
|
||||
const FHyperTwistTrainingReviewPlanState NormalizedReviewPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeReviewPlanState(ReviewPlan);
|
||||
if (!NormalizedReviewPlan.IsStructurallyValid())
|
||||
{
|
||||
RepairedState.ReviewPlans[*ExistingIndex] = ReviewPlan;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const int32* ExistingIndex = ReviewPlanIndexById.Find(NormalizedReviewPlan.PlanId))
|
||||
{
|
||||
RepairedState.ReviewPlans[*ExistingIndex] = NormalizedReviewPlan;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReviewPlanIndexById.Add(ReviewPlan.PlanId, RepairedState.ReviewPlans.Add(ReviewPlan));
|
||||
ReviewPlanIndexById.Add(
|
||||
NormalizedReviewPlan.PlanId,
|
||||
RepairedState.ReviewPlans.Add(NormalizedReviewPlan)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RepairedState.ReviewPlans.Sort([](
|
||||
const FHyperTwistTrainingReviewPlanState& Left,
|
||||
const FHyperTwistTrainingReviewPlanState& Right
|
||||
)
|
||||
{
|
||||
if (Left.UserId != Right.UserId)
|
||||
{
|
||||
return Left.UserId < Right.UserId;
|
||||
}
|
||||
|
||||
const FString LeftSortUtc = !Left.CreatedAtUtc.IsEmpty() ? Left.CreatedAtUtc : Left.ReferenceUtc;
|
||||
const FString RightSortUtc = !Right.CreatedAtUtc.IsEmpty() ? Right.CreatedAtUtc : Right.ReferenceUtc;
|
||||
const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
|
||||
LeftSortUtc,
|
||||
RightSortUtc
|
||||
);
|
||||
if (UtcCompare != 0)
|
||||
{
|
||||
return UtcCompare > 0;
|
||||
}
|
||||
|
||||
return Left.PlanId < Right.PlanId;
|
||||
});
|
||||
|
||||
TMap<FString, int32> StoredTemplateIndexByKey;
|
||||
for (const FHyperTwistTrainingSessionTemplate& SessionTemplate : RepositoryState.StoredSessionTemplates)
|
||||
{
|
||||
|
|
@ -19057,12 +19465,24 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der
|
|||
Snapshot.ReferenceUtc
|
||||
) > 3.0;
|
||||
|
||||
const bool bHasGenericPreferredGuidanceSource =
|
||||
Snapshot.PreferredGuidanceSourceLabel.IsEmpty()
|
||||
|| Snapshot.PreferredGuidanceSourceLabel == TEXT("coach-memory-default")
|
||||
|| Snapshot.PreferredGuidanceSourceLabel == TEXT("coach-memory-follow-up")
|
||||
|| Snapshot.PreferredGuidanceSourceLabel.StartsWith(TEXT("coach-memory-schedule-"));
|
||||
if (Snapshot.bHasBlockedFollowUpScheduleFriction
|
||||
|| Snapshot.ScheduleBlockedFollowUpEventCount > 0
|
||||
|| Snapshot.FollowUpReadyCoachSequenceCount > 0)
|
||||
{
|
||||
Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::FollowUp;
|
||||
Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-follow-up");
|
||||
if (bHasGenericPreferredGuidanceSource)
|
||||
{
|
||||
Snapshot.PreferredGuidanceSourceLabel =
|
||||
Snapshot.bHasBlockedFollowUpScheduleFriction
|
||||
|| Snapshot.ScheduleBlockedFollowUpEventCount > 0
|
||||
? TEXT("coach-memory-schedule-follow-up")
|
||||
: TEXT("coach-memory-follow-up");
|
||||
}
|
||||
}
|
||||
else if (Snapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::None)
|
||||
{
|
||||
|
|
@ -19070,7 +19490,10 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der
|
|||
|| Snapshot.RecurringCoachCaseIds.Num() > 0
|
||||
? EHyperTwistTrainingCoachGuidanceLane::Recommended
|
||||
: EHyperTwistTrainingCoachGuidanceLane::FollowUp;
|
||||
Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-default");
|
||||
if (bHasGenericPreferredGuidanceSource)
|
||||
{
|
||||
Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-default");
|
||||
}
|
||||
}
|
||||
if (Snapshot.PreferredQueueRecoveryAction == EHyperTwistTrainingQueueRecoveryAction::None
|
||||
&& Snapshot.bNeedsScheduleFrictionRecovery)
|
||||
|
|
@ -19081,6 +19504,74 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der
|
|||
: EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery;
|
||||
Snapshot.PreferredQueueRecoveryActionSourceLabel = TEXT("coach-memory-schedule-default");
|
||||
}
|
||||
if (Snapshot.bNeedsScheduleFrictionRecovery)
|
||||
{
|
||||
const int32 ScheduleTighteningBias = FMath::Clamp(
|
||||
(Snapshot.bHasBlockedFollowUpScheduleFriction ? 2 : 0)
|
||||
+ (Snapshot.ScheduleBlockedFollowUpEventCount > 0 ? 1 : 0)
|
||||
+ (Snapshot.bHasOutstandingDeferredScheduleWork ? 1 : 0)
|
||||
+ (Snapshot.bHasQueueExecutionCompletionGap ? 1 : 0)
|
||||
+ (Snapshot.bHasRepeatManualScheduleFriction ? 1 : 0),
|
||||
1,
|
||||
4
|
||||
);
|
||||
const bool bPreferFollowUpScheduleRecovery =
|
||||
Snapshot.bHasBlockedFollowUpScheduleFriction
|
||||
|| Snapshot.ScheduleBlockedFollowUpEventCount > 0
|
||||
|| Snapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp;
|
||||
const bool bPreferRecommendedScheduleRecovery =
|
||||
!bPreferFollowUpScheduleRecovery
|
||||
&& (Snapshot.bHasQueueExecutionCompletionGap
|
||||
|| Snapshot.bHasOutstandingDeferredScheduleWork
|
||||
|| Snapshot.PreferredGuidanceLane
|
||||
== EHyperTwistTrainingCoachGuidanceLane::Recommended);
|
||||
const bool bHasGenericLaunchBudgetSource =
|
||||
Snapshot.PreferredLaunchBudgetSourceLabel.IsEmpty()
|
||||
|| Snapshot.PreferredLaunchBudgetSourceLabel.StartsWith(TEXT("coach-memory-schedule-"));
|
||||
|
||||
Snapshot.PreferredLaunchBudgetBias =
|
||||
FMath::Min(Snapshot.PreferredLaunchBudgetBias, -ScheduleTighteningBias);
|
||||
if (bHasGenericLaunchBudgetSource)
|
||||
{
|
||||
Snapshot.PreferredLaunchBudgetSourceLabel = bPreferFollowUpScheduleRecovery
|
||||
? TEXT("coach-memory-schedule-follow-up-tightened-launch")
|
||||
: TEXT("coach-memory-schedule-tightened-launch");
|
||||
}
|
||||
|
||||
if (bPreferFollowUpScheduleRecovery)
|
||||
{
|
||||
Snapshot.FollowUpGuidanceContinueBias += ScheduleTighteningBias;
|
||||
Snapshot.SuppressedPrimaryQueuePressureCount += ScheduleTighteningBias;
|
||||
Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::FollowUp;
|
||||
if (bHasGenericPreferredGuidanceSource)
|
||||
{
|
||||
Snapshot.PreferredGuidanceSourceLabel =
|
||||
Snapshot.bHasBlockedFollowUpScheduleFriction
|
||||
|| Snapshot.ScheduleBlockedFollowUpEventCount > 0
|
||||
? TEXT("coach-memory-schedule-follow-up")
|
||||
: TEXT("coach-memory-follow-up");
|
||||
}
|
||||
}
|
||||
else if (bPreferRecommendedScheduleRecovery)
|
||||
{
|
||||
const int32 RecommendedScheduleRecoveryBias = FMath::Max(
|
||||
1,
|
||||
ScheduleTighteningBias - (Snapshot.bHasArchiveAwareScheduleFriction ? 1 : 0)
|
||||
);
|
||||
Snapshot.RecommendedGuidanceContinueBias += RecommendedScheduleRecoveryBias;
|
||||
Snapshot.SuppressedFollowUpQueuePressureCount += RecommendedScheduleRecoveryBias;
|
||||
if (Snapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::None
|
||||
|| Snapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended
|
||||
|| bHasGenericPreferredGuidanceSource)
|
||||
{
|
||||
Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::Recommended;
|
||||
if (bHasGenericPreferredGuidanceSource)
|
||||
{
|
||||
Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-schedule-recommended");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Snapshot;
|
||||
}
|
||||
|
|
@ -28828,7 +29319,7 @@ FHyperTwistTrainingReviewPlanState UHyperTwistTrainingRepositoryLibrary::BuildRe
|
|||
ReviewPlan.Entries[0].EntryState = EHyperTwistTrainingReviewPlanEntryState::Current;
|
||||
}
|
||||
|
||||
return ReviewPlan;
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeReviewPlanState(ReviewPlan);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertReviewPlan(
|
||||
|
|
@ -28837,25 +29328,27 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertR
|
|||
)
|
||||
{
|
||||
FHyperTwistTrainingRepositoryState UpdatedState = RepositoryState;
|
||||
if (!ReviewPlan.IsStructurallyValid())
|
||||
const FHyperTwistTrainingReviewPlanState NormalizedReviewPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeReviewPlanState(ReviewPlan);
|
||||
if (!NormalizedReviewPlan.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedState;
|
||||
}
|
||||
|
||||
const int32 ExistingIndex = UpdatedState.ReviewPlans.IndexOfByPredicate(
|
||||
[&ReviewPlan](const FHyperTwistTrainingReviewPlanState& ExistingPlan)
|
||||
[&NormalizedReviewPlan](const FHyperTwistTrainingReviewPlanState& ExistingPlan)
|
||||
{
|
||||
return ExistingPlan.PlanId == ReviewPlan.PlanId;
|
||||
return ExistingPlan.PlanId == NormalizedReviewPlan.PlanId;
|
||||
}
|
||||
);
|
||||
|
||||
if (ExistingIndex >= 0)
|
||||
{
|
||||
UpdatedState.ReviewPlans[ExistingIndex] = ReviewPlan;
|
||||
UpdatedState.ReviewPlans[ExistingIndex] = NormalizedReviewPlan;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatedState.ReviewPlans.Add(ReviewPlan);
|
||||
UpdatedState.ReviewPlans.Add(NormalizedReviewPlan);
|
||||
}
|
||||
|
||||
return UpdatedState;
|
||||
|
|
@ -28885,7 +29378,7 @@ bool UHyperTwistTrainingRepositoryLibrary::TryGetReviewPlan(
|
|||
return false;
|
||||
}
|
||||
|
||||
OutReviewPlan = *ExistingPlan;
|
||||
OutReviewPlan = HyperTwistTrainingRepositoryLibraryInternal::NormalizeReviewPlanState(*ExistingPlan);
|
||||
return OutReviewPlan.IsStructurallyValid();
|
||||
}
|
||||
|
||||
|
|
@ -28954,7 +29447,7 @@ FHyperTwistTrainingReviewPlanState UHyperTwistTrainingRepositoryLibrary::UpdateR
|
|||
UpdatedPlan.CompletedAtUtc = !StepResult.UpdatedRunState.Session.EndedAtUtc.IsEmpty()
|
||||
? StepResult.UpdatedRunState.Session.EndedAtUtc
|
||||
: Attempt.CompletedAtUtc;
|
||||
return UpdatedPlan;
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeReviewPlanState(UpdatedPlan);
|
||||
}
|
||||
|
||||
FString NextCaseId;
|
||||
|
|
@ -28997,7 +29490,7 @@ FHyperTwistTrainingReviewPlanState UHyperTwistTrainingRepositoryLibrary::UpdateR
|
|||
UpdatedPlan.Entries[NextEntryIndex].EntryState = EHyperTwistTrainingReviewPlanEntryState::Current;
|
||||
}
|
||||
|
||||
return UpdatedPlan;
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeReviewPlanState(UpdatedPlan);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingReviewFlowStatus UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(
|
||||
|
|
|
|||
|
|
@ -205,6 +205,22 @@ namespace HyperTwistTrainingSubsystemInternal
|
|||
: TEXT("successful deferred tightened-launch history is narrowing the next launch to continue deferred coach work"))
|
||||
);
|
||||
}
|
||||
else if (CoachMemorySnapshot.PreferredLaunchBudgetSourceLabel.StartsWith(TEXT("coach-memory-schedule-")))
|
||||
{
|
||||
BudgetReasons.Add(
|
||||
CoachMemorySnapshot.bHasBlockedFollowUpScheduleFriction
|
||||
? (DeckLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp
|
||||
? TEXT("blocked follow-up schedule friction is keeping the continuation slice narrow enough to unblock follow-up queue work")
|
||||
: (bQueuedLaunch
|
||||
? TEXT("blocked follow-up schedule friction is keeping queued continuation work narrow enough to reduce deferred queue pressure")
|
||||
: TEXT("blocked follow-up schedule friction is narrowing the next launch before broader guidance resumes")))
|
||||
: (DeckLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp
|
||||
? TEXT("schedule-friction recovery is keeping the continuation slice narrow enough to stabilize follow-up queue work")
|
||||
: (bQueuedLaunch
|
||||
? TEXT("schedule-friction recovery is keeping queued continuation work narrow enough to reduce deferred queue pressure")
|
||||
: TEXT("schedule-friction recovery is narrowing the next launch before broader guidance resumes")))
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
BudgetReasons.Add(TEXT("successful tightened-launch history is narrowing the next launch"));
|
||||
|
|
|
|||
|
|
@ -6456,6 +6456,9 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateLearnerStateCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 OutOfSyncLearnerStateCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalReviewPlanCount = 0;
|
||||
|
||||
|
|
@ -6465,6 +6468,9 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateReviewPlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 OutOfSyncReviewPlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalStoredSessionTemplateCount = 0;
|
||||
|
||||
|
|
@ -6519,6 +6525,9 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 BrokenCoachActionPlanDepthCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 OutOfSyncCoachActionPlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalCoachSessionQueueCount = 0;
|
||||
|
||||
|
|
@ -6528,6 +6537,9 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateCoachSessionQueueCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 OutOfSyncCoachSessionQueueCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 MissingCoachSessionQueueSourceSessionCount = 0;
|
||||
|
||||
|
|
@ -6546,6 +6558,9 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateCoachSessionQueueHistoryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 OutOfSyncCoachSessionQueueHistoryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 MissingCoachSessionQueueHistorySourceSessionCount = 0;
|
||||
|
||||
|
|
@ -6558,8 +6573,10 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
|
|||
|| DuplicateRunRecordCount > 0
|
||||
|| InvalidLearnerStateCount > 0
|
||||
|| DuplicateLearnerStateCount > 0
|
||||
|| OutOfSyncLearnerStateCount > 0
|
||||
|| InvalidReviewPlanCount > 0
|
||||
|| DuplicateReviewPlanCount > 0
|
||||
|| OutOfSyncReviewPlanCount > 0
|
||||
|| InvalidStoredSessionTemplateCount > 0
|
||||
|| DuplicateStoredSessionTemplateCount > 0
|
||||
|| InvalidMethodDrillFavoriteCount > 0
|
||||
|
|
@ -6574,13 +6591,16 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
|
|||
|| OrphanedCoachActionPlanRootCount > 0
|
||||
|| CyclicCoachActionPlanLineageCount > 0
|
||||
|| BrokenCoachActionPlanDepthCount > 0
|
||||
|| OutOfSyncCoachActionPlanCount > 0
|
||||
|| InvalidCoachSessionQueueCount > 0
|
||||
|| DuplicateCoachSessionQueueCount > 0
|
||||
|| OutOfSyncCoachSessionQueueCount > 0
|
||||
|| MissingCoachSessionQueueSourceSessionCount > 0
|
||||
|| InvalidCoachSessionQueueActiveEntryCount > 0
|
||||
|| MissingCoachSessionQueueActiveSessionCount > 0
|
||||
|| InvalidCoachSessionQueueHistoryCount > 0
|
||||
|| DuplicateCoachSessionQueueHistoryCount > 0
|
||||
|| OutOfSyncCoachSessionQueueHistoryCount > 0
|
||||
|| MissingCoachSessionQueueHistorySourceSessionCount > 0;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ Evaluate providers second.
|
|||
|
||||
Integrate providers only after the roles and feature-set map exist.
|
||||
|
||||
Current correction call from the source-exposed audit:
|
||||
Current correction call from the source-exposed audit and current execution refresh:
|
||||
|
||||
- preserve the roadmap direction
|
||||
- update the canonical planning docs so they reflect:
|
||||
|
|
@ -64,6 +64,17 @@ Current correction call from the source-exposed audit:
|
|||
- manifest and custody governance gaps
|
||||
- pre-Phase-5 speech governance gates
|
||||
- retained benchmark-oracle and mirror-intake reservations
|
||||
- current code reality in which training/coaching/catalog integration is materially ahead of recognition and hypercube runtime implementation
|
||||
- the imported generated-mode gap, where request/config/selection plumbing exists but the actual clean-room executor is still absent
|
||||
- the current execution-discipline rule that broad refactor / monolith-splitting work should not interrupt the active bounded roadmap packet unless structure is actually blocking it
|
||||
|
||||
## Current execution-reality references
|
||||
|
||||
For the current code-reality synthesis and active phase-discipline note, also read:
|
||||
|
||||
- `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse\116-hypertwist-handoff.md`
|
||||
- `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse\119-hypertwist-current-execution-reality-and-phase-discipline.md`
|
||||
- `C:\HyperTwist\docs\arch\HYPERTWIST_PI_PRIMING_FINDINGS_2026-05-04.md`
|
||||
|
||||
## Higher-detail planning references
|
||||
|
||||
|
|
|
|||
1348
docs/arch/HYPERTWIST_PI_PRIMING_FINDINGS_2026-05-04.md
Normal file
1348
docs/arch/HYPERTWIST_PI_PRIMING_FINDINGS_2026-05-04.md
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue